Spring中bean的生命周期(最详细)

Spring中bean的生命周期(最详细)SpringBean的生命周期是Spring面试热点问题。SpringBean的生命周期指的是从一个普通的Java类变成Bean的过程,深知Spring源码的人都知道这个给面试官讲的话大可讲30分钟以上,如果你不没有学习过Spring的源码,可能就知道Aware接口和调用init方法这样的生命周期,所以这个问题既考察对Spring的微观了解,又考察对Spring的宏观认识,想要答好并不容易!本文希望能够从源码角度入手,帮助面试者彻底搞定SpringBean的生命周期。首先你要明白一点,Sp

大家好,又见面了,我是你们的朋友全栈君。

       Spring Bean的生命周期是Spring面试热点问题。Spring Bean的生命周期指的是从一个普通的Java类变成Bean的过程,深知Spring源码的人都知道这个给面试官讲的话大可讲30分钟以上,如果你不没有学习过Spring的源码,可能就知道Aware接口和调用init方法这样的生命周期,所以这个问题既考察对Spring的微观了解,又考察对Spring的宏观认识,想要答好并不容易!本文希望能够从源码角度入手,帮助面试者彻底搞定Spring Bean的生命周期。

总体的创建过程

      首先你要明白一点,Spring Bean总体的创建过程如下:

                                  Spring中bean的生命周期(最详细)

        以注解类变成Spring Bean为例,Spring会扫描指定包下面的Java类,然后根据Java类构建beanDefinition对象,然后再根据beanDefinition来创建Spring的bean,特别要记住一点,Spring是根据beanDefinition来创建Spring bean的,关于beanDefinition下文会进行分析。

beanDefinition

                                    Spring中bean的生命周期(最详细)

        上图就是beanDefinition所包含的内容,看到这些属性如果对Spring有所了解都应该知道几个,如lazyInit懒加载,在Spring中有一个@Lazy注解,用来标识其这个bean是否为懒加载的,scope属性,相应的也有@scope注解来标识这个bean其作用域,是单例还是多例,beanClass属性,在对象实例化时,会根据beanClass来创建对象、又比如autowireMode注入模型这个属性,这个属性用于记录使用怎样的注入模型,注入模型常用的有根据名称和根据类型、不注入三种注入模型。

        autowireMode虽然看似我们没有使用到过,但是知道这个对于查看Spring和其他框架整合的时候很有帮助,如果我们在beanDefinition指定其根据类型进行属性注入,那么在创建这个bean时,会将其beanClass中的所有属性都拿到,然后排出调基本属性(如类型String、Double、Boolean、Object),然后对于剩下的进行属性注入,记住一点,在beanDefinition指定其根据类型进行属性注入,即使不在属性上面使用@Autowired注解也会对其进行属性注入。在我们写注解类的时候为什么不使用@Autowired时,其属性就注入不进来呢?那是因为注解类在变成beanDefinition时,其注入类型是不注入,所以此时只有使用@Autowired注解进行标记的属性,才会完成依赖注入。

      说了这么多,总之大家要记住Spring会根据beanDefinition来完成bean的创建,为什么不直接使用对象的class对象来创建bean呢?因为在class对象仅仅能描述一个对象的创建,它不足以用来描述一个Spring bean,而对于是否为懒加载、是否是首要的、初始化方法是哪个、销毁方法是哪个,这个Spring中特有的属性在class对象中并没有,所有Spring就定义了beanDefinition来完成bean的创建。

java类 -> beanDefinition对象

        所以说如果要说Bean的生命周期,你不能不说Java类是在那一步变成beanDefinition的,我们先来看一下Spring中AbstractApplicationContext类中的refresh方法,这个方法就可以总结上面的图。

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			准备工作包括设置启动时间,是否激活标识位,
			// 初始化属性源(property source)配置
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			//返回一个factory 为什么需要返回一个工厂
			//因为要对工厂进行初始化
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			//准备工厂
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				//这个方法在当前版本的spring是没用任何代码的
				//可能spring期待在后面的版本中去扩展吧
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				//在spring的环境中去执行已经被注册的 factory processors
				//设置执行自定义的ProcessBeanFactory 和spring内部自己定义的
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				//注册beanPostProcessor
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				//初始化应用事件广播器
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

       有关Spring Bean生命周期最主要的方法有三个invokeBeanFactoryPostProcessors、registerBeanPostProcessors和finishBeanFactoryInitialization。

       其中invokeBeanFactoryPostProcessors方法会执行BeanFactoryPostProcessors后置处理器及其子接口BeanDefinitionRegistryPostProcessor,执行顺序先是执行BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法,然后执行BeanFactoryPostProcessor接口的postProcessBeanFactory方法。

      对于BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法,该步骤会扫描到指定包下面的标有注解的类,然后将其变成BeanDefinition对象,然后放到一个Spring中的Map中,用于下面创建Spring bean的时候使用这个BeanDefinition

       其中registerBeanPostProcessors方法根据实现了PriorityOrdered、Ordered接口,排序后注册所有的BeanPostProcessor后置处理器,主要用于Spring Bean创建时,执行这些后置处理器的方法,这也是Spring中提供的扩展点,让我们能够插手Spring bean创建过程。

Spring中bean的生命周期(最详细)

beanDefinition对象 -> Spring中的bean

        finishBeanFactoryInitialization是完成非懒加载的Spring bean的创建的工作,你要想说Spring的生命周期,不要整其他没用的,直接告诉他在该步骤中会有8个后置处理的方法4个后置处理器的类贯穿在对象的实例化、赋值、初始化、和销毁的过程中,这4个后置处理器出现的地方分别为:

Spring中bean的生命周期(最详细)

   关于每个后置处理的作用如下:

               Spring中bean的生命周期(最详细)

         首先看一下继承关系,InstantiationAwareBeanPostProcessor、SmartInstantiationAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor直接或间接的继承BeanPostProcessor,而SmartInitializingSingleton则是单独的一个接口。

一、InstantiationAwareBeanPostProcessor

InstantiationAwareBeanPostProcessor接口继承BeanPostProcessor接口,它内部提供了3个方法,再加上BeanPostProcessor接口内部的2个方法,所以实现这个接口需要实现5个方法。InstantiationAwareBeanPostProcessor接口的主要作用在于目标对象的实例化过程中需要处理的事情,包括实例化对象的前后过程以及实例的属性设置

在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean()方法的Object bean = resolveBeforeInstantiation(beanName, mbdToUse);方法里面执行了这个后置处理器

二、SmartInstantiationAwareBeanPostProcessor

智能实例化Bean后置处理器(继承InstantiationAwareBeanPostProcessor)

三、MergedBeanDefinitionPostProcessor

四、SmartInitializingSingleton

智能初始化Singleton,该接口只有一个方法,是在所有的非懒加载单实例bean都成功创建并且放到Spring IOC容器之后进行执行的。

下面按照图中的顺序进行说明每个后置处理的作用:

1、InstantiationAwareBeanPostProcessor# postProcessBeforeInstantiation

在目标对象实例化之前调用,方法的返回值类型是Object,我们可以返回任何类型的值。由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成的目标对象的实例(一般都是代理对象)。如果该方法的返回值代替原本该生成的目标对象,后续只有postProcessAfterInitialization方法会调用,其它方法不再调用;否则按照正常的流程走

2、SmartInstantiationAwareBeanPostProcessor# determineCandidateConstructors

检测Bean的构造器,可以检测出多个候选构造器

3、MergedBeanDefinitionPostProcessor# postProcessMergedBeanDefinition

缓存bean的注入信息的后置处理器,仅仅是缓存或者干脆叫做查找更加合适,没有完成注入,注入是另外一个后置处理器的作用

4、SmartInstantiationAwareBeanPostProcessor# getEarlyBeanReference

循环引用的后置处理器,这个东西比较复杂, 获得提前暴露的bean引用。主要用于解决循环引用的问题,只有单例对象才会调用此方法,以后如果有时间,我会写一篇关于Spring是怎么处理循环依赖的,会将这个,现在你就只需要知道他能提前暴露bean就行了。

5、InstantiationAwareBeanPostProcessor# postProcessAfterInstantiation

方法在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。如果该方法返回false,会忽略属性值的设置;如果返回true,会按照正常流程设置属性值。方法不管postProcessBeforeInstantiation方法的返回值是什么都会执行

6、InstantiationAwareBeanPostProcessor# postProcessPropertie

方法对属性值进行修改(这个时候属性值还未被设置,但是我们可以修改原本该设置进去的属性值)。如果postProcessAfterInstantiation方法返回false,该方法不会被调用。可以在该方法内完成对属性的自动注入(注意:在Spring5.1之前,完成属性自动注入的方法是postProcessPropertyValues方法,在5.1就开始使用postProcessPropertie方法了,如果发现不同也不必惊慌,就是改了一下方法名,内部逻辑一样的)(在这一步,会完成标注了@Autowired、@Resource注解的自动装配,如果有兴趣的同学可以看一下:Spring源码分析@Autowired、@Resource注解的区别,是从源码角度分析他们之间装配的不同,不过需要一点Spring的功底

调用invokeInitMethods方法 

     先看代码invokeInitMethods方法的代码

private void invokeAwareMethods(final String beanName, final Object bean) {
		if (bean instanceof Aware) {
			if (bean instanceof BeanNameAware) {
				((BeanNameAware) bean).setBeanName(beanName);
			}
			if (bean instanceof BeanClassLoaderAware) {
				ClassLoader bcl = getBeanClassLoader();
				if (bcl != null) {
					((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
				}
			}
			if (bean instanceof BeanFactoryAware) {
				((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
			}
		}
	}

    该方法就是查看这个bean是否实现应该的Aware接口,如果实现了,则调用相应的接口。 

7.BeanPostProcessor# postProcessBeforeInitialization

该方法会在初始化之前进行执行,其中有一个实现类比较重要ApplicationContextAwareProcessor,该后置处理的一个作用就是当应用程序定义的Bean实现ApplicationContextAware接口时注入ApplicationContext对象:

class ApplicationContextAwareProcessor implements BeanPostProcessor {

	private final ConfigurableApplicationContext applicationContext;

	private final StringValueResolver embeddedValueResolver;


	/**
	 * Create a new ApplicationContextAwareProcessor for the given context.
	 */
	public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
	}


	@Override
	@Nullable
	public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
		AccessControlContext acc = null;

		if (System.getSecurityManager() != null &&
				(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
						bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
						bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
			acc = this.applicationContext.getBeanFactory().getAccessControlContext();
		}

		if (acc != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareInterfaces(bean);
				return null;
			}, acc);
		}
		else {
			invokeAwareInterfaces(bean);
		}

		return bean;
	}

	private void invokeAwareInterfaces(Object bean) {
		if (bean instanceof Aware) {  //判断该类是否是Aware的子类
			if (bean instanceof EnvironmentAware) {

				((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
			}
			if (bean instanceof EmbeddedValueResolverAware) {
				((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
			}
			if (bean instanceof ResourceLoaderAware) {
				((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
			}
			if (bean instanceof ApplicationEventPublisherAware) {
				((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
			}
			if (bean instanceof MessageSourceAware) {
				((MessageSourceAware) bean).setMessageSource(this.applicationContext);
			}
			//spring帮你set一个applicationContext对象
			//所以当我们自己的一个对象实现了ApplicationContextAware对象只需要提供setter就能得到applicationContext对象
			//此处应该有点赞加收藏
			if (bean instanceof ApplicationContextAware) {
				if (!bean.getClass().getSimpleName().equals("IndexDao"))
				((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
			}
		}
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		return bean;
	}

}

       看到这里你可能恍然大悟,原来调用BeanFactoryAware和ApplicationContextAware时,相应的处理是不同的,这个你可能看别人写的博客时,完全就没有介绍,,所以,你如果把这个不同点说出来,那么面试官就会认为你是看过源码的人。ApplicationContextAware是在一个BeanPostProcessor后置处理器的postProcessBeforeInitialization方法中进行执行的。

      回调执行bean的声明周期回调中的init方法,该步骤不做介绍,就是执行bean的init方法

      8.BeanPostProcessor# postProcessAfterInitialization

      该后置处理器的执行是在调用init方法后面进行执行,主要是判断该bean是否需要被AOP代理增强,如果需要的话,则会在该步骤返回一个代理对象。

     9.SmartInitializingSingleton# afterSingletonsInstantiated

      该方法会在所有的非懒加载单实例bean都成功创建并且放到Spring IOC容器之后,依次遍历所有的bean,如果当前这个bean是SmartInitializingSingleton的子类,那么就强转成SmartInitializingSingleton类,然后调用SmartInitializingSingleton的afterSingletonsInstantiated方法。

    查看一下DefaultListableBeanFactory#preInstantiateSingletons方法

public void preInstantiateSingletons() throws BeansException {
		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
        //1.遍历所有的beanDefinition对象,然后根据beanDefinition去创建对象,此步骤会完成当前
        //Spring容器中所有的bean,getBean方法会查看容器中是否有该bean,如果没有则去创建
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						final FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
											((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				else {
					getBean(beanName);
				}
			}
		}

		// Trigger post-initialization callback for all applicable beans...
                //2.走到这里的时候,说明所有的bean都已经被成功创建,依次遍历所有的bean
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
                      //3.判断该bean是否为SmartInitializingSingleton接口的子类,如果是,则执行afterSingletonsInstantiated方法
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
						smartSingleton.afterSingletonsInstantiated();
						return null;
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}

       对于SmartInitializingSingleton# afterSingletonsInstantiated方法,是在所有的bean完成之后进行调用的,这个扩展点可以让我们自己很轻松的自定义注解,完成我们想要实现的逻辑,比如使用@EventListener注解实现事件监听,其底层就是童年各国SmartInitializingSingleton# afterSingletonsInstantiated来实现的,关于@EventListener可以自行百度,以后有时间再进行分析,而且我在看公司内部基于Spring开发的框架时,也用到了SmartInitializingSingleton进行定义注解,实现自己的逻辑,对于Spring提供的这么多扩展点,感觉就这个我能用到一点(还是太辣鸡o(╥﹏╥)o),所以对于这个重点说一下。

     此时,Spring Bean的生命周期就算说完了,如果你能把上面的步骤将给面试官听,那么保证是没有问题的,在回答这个问题的时候可以从下面几个点思考:

     1.普通Java类是在哪一步变成beanDefinition的

     2.有8个后置处理器方法4个后置处理器,贯穿了对象的创建->属性赋值->初始化->销毁

     3.然后再说处理器方法执行的时机和作用

     4.invokeInitMethods执行的Aware和BeanPostProcessor# postProcessBeforeInitialization方法执行的aware

总结

Spring Bean的生命周期分为四个阶段多个扩展点。扩展点又可以分为影响多个Bean影响单个Bean。整理如下:
四个阶段

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

多个扩展点

  • 影响多个Bean
    • BeanPostProcessor 
    1. postProcessBeforeInitialization
    2. postProcessAfterInitialization
    • InstantiationAwareBeanPostProcessor
    1. postProcessBeforeInstantiation
    2. postProcessAfterInstantiation
    3. postProcessPropertyValues
    • MergedBeanDefinitionPostProcessor
    1. postProcessMergedBeanDefinition
    • SmartInstantiationAwareBeanPostProcessor
    1. determineCandidateConstructors
    2. getEarlyBeanReference
  • 影响单个Bean
    • Aware
      • Aware Group1(调用invokeInitMethods方法
        • BeanNameAware
        • BeanClassLoaderAware
        • BeanFactoryAware
      • Aware Group2(调用Aware和BeanPostProcessor# postProcessBeforeInitialization方法
        • EnvironmentAware
        • EmbeddedValueResolverAware
        • ApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware)
    • 生命周期
      • InitializingBean
      • DisposableBean

最后建议:

   其实想要清楚Spring Bean的周期,那么还是要看一下Spring的源码,看过源码后你会知道Spring中是怎么解决属性的循环依赖的(现在好多博客都说过循环依赖,基本都是你抄我,我抄你,没有领悟循环依赖的精髓,只知道Spring使用3个集合来解决,却不知道为啥是3个,为啥不能是2个)、@Configuration实现的原理等等,这些问题只有真正看多Spring源码的人才能很好的回答,所以建议大家还是需要看看Spring源码的。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/159869.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Bilateral Filters(双边滤波算法)原理及实现

    Bilateral Filters(双边滤波算法)原理及实现双边滤波算法原理:双边滤波算法实现:双边滤波算法实例:参考:http://people.csail.mit.edu/sparis/bf/http://blog.csdn.net/fightingforcv/article/details/52723376http://blog.csdn.net/mumu

  • springboot到底是什么_Springboot注解

    springboot到底是什么_Springboot注解SpringBoot是干哈的介绍:springboot是由Pivotal团队提供的全新框架。spring的出现是为了解决企业级开发应用的复杂性,spring的通过注册bean的方式来管理类,但是随着业务的增加,使用xml配置bean的方式也显得相当繁琐,所以springboot就是为了解决spring配置繁琐的问题而诞生的,并且近几年来非常流行开启我的第一个HelloSpringBoot!开启方式根据https://start.spring.io网址创建一个springboot项目

  • ip地址的组成(网络位+主机位)

    ip地址的组成(网络位+主机位)IP地址采用分层结构;IP地址是由网络号(netID)与主机号(hostID)两部分组成的。根据不同的取值范围,IP地址可以分为五类;IP地址中的前5位用于标识IP地址的类别:实际大多通过子

  • kali不能使用arpspoof命令_kali不能使用arpspoof命令_ARP欺骗工具arpspoof的用法「建议收藏」

    kali不能使用arpspoof命令_kali不能使用arpspoof命令_ARP欺骗工具arpspoof的用法「建议收藏」arpspoof是一个好用的ARP欺骗工具,Kalilinux中自带了该工具,在ubuntu中,安装它只需运行命令:sudoapt-getinstalldsniff安装完成后,输入命令:manarpspoof可以查看使用手册,2.4版本的手册内容如下(自己翻译的,非官方):名字arpspoof-截获交换局域网中的数据包用法arpspoof[-iinterface][-cow…

  • SM4加密算法(JAVA语言实现)

    SM4加密算法(JAVA语言实现)1、SM4算法简介中国国家密码管理局于2006年1月6日发布第7号公告,将我国无线局域网产品的加密算法确定为SM4算法(原SMS4)。这是国内官方公布的第一个商用密码算法。SM4分组密码算法是一个迭代分钟密码算法,由加解密算法和密钥扩展算法组成,SM4分组密码算法采用非平衡Feistel结构,明文分组长度为128bit,密钥长度为128bit。加密算法与密钥扩展算法都采用32轮非线性迭代结…

  • [BIM]BIM中IFD介绍[通俗易懂]

    [BIM]BIM中IFD介绍[通俗易懂]第三大支柱IFD-确定交换的信息和你要的信息是同一个东西 IFD的全称是InternationalFrameworkforDictionaries,中文可以叫“国际字典框架”,和前两者IFC/IDM一样,作为BIM用户,能记住IFD的名字和记住IFD是字典就行了。  用BIM创建和管理信息,通过IFC和IDM实现信息在不同项目成员和不同软件产品之间的交换,从而使BIM价值最大化。…

    2022年10月22日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号