Spring 源码学习笔记(一)对象创建流程梳理

脉络思路:学习Spring的两大特性IOC与AOP,都离不开容器,那么就要先了解容器是什么?如何使用?容器是对象从创建到使用,再到销毁整个流程的控制。下面梳理下创建对象过程,加载配置文件 ——> 解析——> 封装BeanDefinition对象 ——> 实例化 ——> 完整对象。。。

下面从加载配置文件入手:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd ">

<context:property-placeholder location="classpath:org/springframework/web/context/WEB-INF/myplaceholder.properties"/>

  <bean id="myBean" class="org.springframework.beans.factory.access.TestBean">
	  <property name="name" value="${myBeanValue}"></property>
  </bean>

</beans>
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.access.TestBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SimpleDemoTest {

	@Test
	public void test(){
		ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/web/context/beans1.xml");
		TestBean bean = (TestBean) context.getBean("mybean");
		System.out.println(bean.getName());
	}
}

1、加载配置文件,封装成beanDefinition对象

为了可以扩展加载更多类型的配置文件,提供一层BeanDefinitionReader接口层(让不同类型的加载Reader去实现)。加载XML配置文件,实际上用XmlBeanDefinitionReader读取配置文件,统一封装成BeanDefinition对象。

在启动过程是一个核心方法refresh(),refresh()方法实现是在AbstractApplicationContext.java中

/**
	 * Create a new ClassPathXmlApplicationContext with the given parent,
	 * loading the definitions from the given XML files.
	 * @param configLocations array of resource locations
	 * @param refresh whether to automatically refresh the context,
	 * loading all bean definitions and creating all singletons.
	 * Alternatively, call refresh manually after further configuring the context.
	 * @param parent the parent context
	 * @throws BeansException if context creation failed
	 * @see #refresh()
	 */
	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

下面对refresh()中各个方法做个简单的介绍:

@Override
public void refresh() throws BeansException, IllegalStateException {
    //startupShutdownMonitor对象在spring环境刷新和销毁的时候都会用到,确保刷新和销毁不会同时执行
    synchronized (this.startupShutdownMonitor) {
        // 准备工作,例如记录事件,设置标志,检查环境变量等,并有留给子类扩展的位置,用来将属性加入到applicationContext中
        prepareRefresh();

        // 创建beanFactory,这个对象作为applicationContext的成员变量,可以被applicationContext拿来用,
        // 并且解析资源(例如xml文件),取得bean的定义,放在beanFactory中
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 对beanFactory做一些设置,例如类加载器、spel解析器、指定bean的某些类型的成员变量对应某些对象等
        prepareBeanFactory(beanFactory);

        try {
            // 子类扩展用,可以设置bean的后置处理器(bean在实例化之后这些后置处理器会执行)
            postProcessBeanFactory(beanFactory);

            // 执行beanFactory后置处理器(有别于bean后置处理器处理bean实例,beanFactory后置处理器处理bean定义)
            invokeBeanFactoryPostProcessors(beanFactory);

            // 将所有的bean的后置处理器排好序,但不会马上用,bean实例化之后会用到
            registerBeanPostProcessors(beanFactory);

            // 初始化国际化服务
            initMessageSource();

            // 创建事件广播器
            initApplicationEventMulticaster();

            // 空方法,留给子类自己实现的,在实例化bean之前做一些ApplicationContext相关的操作
            onRefresh();

            // 注册一部分特殊的事件监听器,剩下的只是准备好名字,留待bean实例化完成后再注册
            registerListeners();

            // 单例模式的bean的实例化、成员变量注入、初始化等工作都在此完成
            finishBeanFactoryInitialization(beanFactory);

            // applicationContext刷新完成后的处理,例如生命周期监听器的回调,广播通知等
            finishRefresh();
        }

        catch (BeansException ex) {
            logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

            // 刷新失败后的处理,主要是将一些保存环境信息的集合做清理
            destroyBeans();

            // applicationContext是否已经激活的标志,设置为false
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }
    }
}

跟进方法obtainFreshBeanFactory(),可以看到核心方法AbstractRefreshableApplicationContext.java中refreshBeanFactory(),创建beanFactory,同时加载全部beanDefinition对象,此时只做简单加载(其中配置占位符不会被替换,替换是在beanFactoryPostProcessor中完成)

@Override
	protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
            //解析资源(例如xml文件),取得bean的定义,放在beanFactory中
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

 

2、创建beanFactory,执行后置处理器

BeanDefinition对象——>实例化,这个过程不只是new/invoke,还做了哪些操作?

BeanFactory(Interface)Spring的本质是一个bean工厂(beanFactory)或者bean容器。解决bean之间的依赖问题,达到了松耦合的效果。 在没有spring这个beanFactory之前,我们都是直接通过new来实例化各种对象,现在各种对象bean的生产都是通过beanFactory来实例化的,这样的话,spring这个beanFactory就可以在实例化bean的各个阶段进行一些额外的处理。在bean的生命周期的各个阶段对bean进行管理,并且spring将这些阶段通过各种接口暴露给我们。 我们只要让bean实现对应的接口,那么spring就会在bean的生命周期调用我们实现的接口来处理该bean

BeanFactoryPostProcessor(Interface): BeanFactory后置处理器,是对BeanDefinition对象进行修改。

BeanPostProcessor(Interface):Bean后置处理器,是对生成的Bean对象进行修改。 (初始化中使用)

后置处理器也被抽象出来,通过实现接口类BeanFactoryPostProcessor就可以自定义创建处理器。下面加入一个MyPostProcessor

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd ">

<context:property-placeholder location="classpath:org/springframework/web/context/WEB-INF/myplaceholder.properties"/>

  <bean id="myPostProcessor" class="org.springframework.MyPostProcessor"/>

  <bean id="myBean" class="org.springframework.beans.factory.access.TestBean">
	  <property name="name" value="${myBeanValue}"></property>
  </bean>

</beans>
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

public class MyPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("==> come to myPostProcessor ...");
	}
}
控制台打印:
---------------------------------
==> come to myPostProcessor ...
this is a test
BUILD SUCCESSFUL in 32s

注解是继配置文件之后的扩展,也是在beanFactory后置处理器位置中做了处理。(同样放在后置处理器的还有,Springboot中注解自动装配功能实现方法,跟进ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法。可以仿照@autowire注解处理器AutowiredAnnotationBeanPostProcessor添加自定义注解,完成注解自动装配需求的实现)

/**
	 * Derive further bean definitions from the configuration classes in the registry.
	 */
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);

		processConfigBeanDefinitions(registry);
	}


	/**
	 * Build and validate a configuration model based on the registry of
	 * {@link Configuration} classes.
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        ...
        // Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			parser.parse(candidates);
			parser.validate();
                        ...
                }
        }
class ConfigurationClassParser {
        protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
        ...
	      processConfigurationClass(new ConfigurationClass(metadata, beanName));
        ...
        }


        protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        ...
        do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
        ...
        }

        protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		// Process any @ComponentScan annotations
		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}


		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), true);

		// Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}
                return null;

        }

}

 

3、对象实例化

猜测一下,通过反射的方式进行实例化代码如下(在堆空间中开启空间,属性都是默认的)

Constructor ctor = clazz.getDeclareConsturctor();
Object obj = ctor.newinstance();

下面开始验证,跟进下refresh()中 finishBeanFactoryInitialization(beanFactory)

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
		// Initialize conversion service for this context.
		if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
				beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
			beanFactory.setConversionService(
					beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
		}

		// Register a default embedded value resolver if no bean post-processor
		// (such as a PropertyPlaceholderConfigurer bean) registered any before:
		// at this point, primarily for resolution in annotation attribute values.
		if (!beanFactory.hasEmbeddedValueResolver()) {
			beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
		}

		// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
		String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
		for (String weaverAwareName : weaverAwareNames) {
			getBean(weaverAwareName);
		}

		// Stop using the temporary ClassLoader for type matching.
		beanFactory.setTempClassLoader(null);

		// Allow for caching all bean definition metadata, not expecting further changes.
		beanFactory.freezeConfiguration();

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

beanFactory.preInstantiateSingletons()默认创建单例的对象,继续跟进。

@Override
	public void preInstantiateSingletons() throws BeansException {
		if (logger.isTraceEnabled()) {
			logger.trace("Pre-instantiating singletons in " + this);
		}

		// 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...
		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);
				}
			}
		}

		...
	}

去查找beanName,跟进getBean(beanName)——>doGetBean(name)——>createBean(beanName, mbd,  args)——>doCreateBean(beanName, mdbToUse, args)——>createBeanInstance(beanName, mbd, args)可以看到有构造器的创建——>instantiateBean(beanName, mbd)方法——>.instantiate(mbd, beanName, parent)有在创建构造器,那么期待接下来创建实例new instance()——>BeanUtils.instantiateClass(constructorToUse)果然有new instance

Constructor<?> constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
   Assert.notNull(ctor, "Constructor must not be null");
   try {
      ReflectionUtils.makeAccessible(ctor);
      if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
         return KotlinDelegate.instantiateClass(ctor, args);
      }
      else {
         Class<?>[] parameterTypes = ctor.getParameterTypes();
         Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
         Object[] argsWithDefaultValues = new Object[args.length];
         for (int i = 0 ; i < args.length; i++) {
            if (args[i] == null) {
               Class<?> parameterType = parameterTypes[i];
               argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
            }
            else {
               argsWithDefaultValues[i] = args[i];
            }
         }
         return ctor.newInstance(argsWithDefaultValues);
      }
   }
   catch (InstantiationException ex) {
      throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
   }
   catch (IllegalAccessException ex) {
      throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
   }
   catch (IllegalArgumentException ex) {
      throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
   }
   catch (InvocationTargetException ex) {
      throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
   }
}

这就是实例化的老巢了...

 

4、对象的初始化

spring中一般分为自定义bean对象和容器容器对象,当在自定义对象中调用容器对象,可以通过aware接口去代替get/set方法。

/**
 * Interface to be implemented by beans that wish to be aware of their
 * owning {@link BeanFactory}.
 */
public interface BeanFactoryAware extends Aware {

	void setBeanFactory(BeanFactory beanFactory) throws BeansException;

}
/**
 * Interface to be implemented by any object that wishes to be notified
 * of the {@link ApplicationContext} that it runs in.
 */
public interface ApplicationContextAware extends Aware {

	void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

before、after对bean进行扩展beanPostProcessor,例如:AOP动态代理有cglib和jdk两种方式,下面跟进一下BeanPostProcessor接口

public interface BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

可以找一下动态代理的实现类AbstractAutoProxyCreator,before方法直接返回bean,after方法中跟进wrapIfNecessary

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

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

        @Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

        /**
	 * Create an AOP proxy for the given bean.
	 * @param beanClass the class of the bean
	 * @param beanName the name of the bean
	 * @param specificInterceptors the set of interceptors that is
	 * specific to this bean (may be empty, but not null)
	 * @param targetSource the TargetSource for the proxy,
	 * already pre-configured to access the bean
	 * @return the AOP proxy for the bean
	 * @see #buildAdvisors
	 */
	protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {

		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}

		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);

		if (!proxyFactory.isProxyTargetClass()) {
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}

		return proxyFactory.getProxy(getProxyClassLoader());
	}

init方法,执行配置文件中init-method方法

下面来找一个初始化的老巢,同之前的实例化refresh()——>finishBeanFactoryInitialization(beanFactory)——>getBean(beanName)——>doGetBean(name)——>createBean(beanName, mbd,  args)——>doCreateBean(beanName, mdbToUse, args),在这个方法里,createBeanInstance(beanName, mbd, args)之后返回实例化对象,然后执行——>populateBean(beanName, mbd, instanceWrapper)填充属性和initializeBean(beanName, exposedObject, mbd)调用aware方法初始化

====================================================

梳理下容器对象的创建流程:(搞懂Refresh()里面的13个方法,后面将会细化学习)

  1. 创建容器 —— prepareRefresh()
  2. 加载配置文件,封装成 BeanDefinition —— obtainFreshBeanFactory()
  3. 调用 BeanFactoryPostProcessor —— invokeBeanFactoryPostProcessors(beanFactory)
  4. 准备工作BeanPostProcessor —— registerBeanPostProcessors(beanFactory)
  5. 准备工作② 监听器、事件、广播器等 —— initApplicationEventMulticaster()、registerListeners()
  6. 实例化 —— finishBeanFactoryInitialization(beanFactory)
  7. 初始化—— finishBeanFactoryInitialization(beanFactory)
  8. 获取完整对象 —— finishBeanFactoryInitialization(beanFactory)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章