從Spring中Bean的產生談到SpringBoot的核心原理

從Spring中Bean的產生談到SpringBoot的核心原理

以Bean的“產生”爲核心的 AutoConfiguration 機制

1. Bean的標識

正如每個人都有自己的名字,對於Spring來說,每個Bean也有對應的標識,這是Spring辨別這些Bean的依據。

/**
 * A BeanDefinition describes a bean instance.
 * This is just a minimal interface:
 * 抽取的部分註釋
 */
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    /**
    * 抽取的部分代碼
    */
    void setBeanClassName(String beanClassName);
    String getBeanClassName();
    void setParentName(String parentName);
    String getParentName();
    boolean isAutowireCandidate();
    void setAutowireCandidate(boolean autowireCandidate);
    void setDependsOn(String... dependsOn);
    String[] getDependsOn();
}

由此可見,每個Bean都有一個極其簡略的描述信息,稱爲BeanDefinition,它不僅描述了這個Bean的 標識,更描述了它的父類的 標識 和它所依賴的類的 標識 。以及一個非常重要的屬性,是否是自動裝配的候選項。

2. Bean 的掃描

Spring對所有的Bean都有一個描述信息,但Spring需要找到這些Bean,並抽取它的信息。

  • Spring可以通過4種方式配置bean

    註解方式 解析對象
    基於xml的配置 XmlBeanDefinitionReader
    基於xml+註解的配置 XmlBeanDefinitionReader
    基於java+註解的配置 AnnotatedBeanDefinitionReader
    基於property文件的配置 PropertiesBeanDefinitionReader

拋開Spring4以上的版本,在Spring早期版本中,大都以xml文件配置Bean(SpringBoot的習慣優於配置,其實是使用了默認的配置),所以,要找到Bean,最關鍵是需要解析XML(其他方式類似),其核心類是 XmlBeanDefinitionReader

/**
 * Bean definition reader for XML bean definitions.
 * 抽取的部分註釋
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    
}

可見 XmlBeanDefinitionReader 的功能是解析xml配置信息,並把這些信息轉換爲BeanDefinition,之後存儲到某地(見後面)。它繼承了 AbstractBeanDefinitionReader ,而 AbstractBeanDefinitionReader 又實現了 EnvironmentCapableBeanDefinitionReader 接口,其中實現了以下方法:

int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;

以上方法返回的顯然不是 BeanDefinition 本身,而是當前資源下 BeanDefinition 的個數。至此,回到Spring的容器,直接使用了 XmlBeanDefinitionReader 對象的容器有 XmlWebApplicationContextClassPathXmlApplicationContextFileSystemXmlApplicationContext 等三個容器,以 XmlWebApplicationContext 爲例,其解析Xml配置文件的過程分以下兩個部分:

  • 創建並初始化 XmlBeanDefinitionReader 對象
@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

上述方法定義了一個 XmlBeanDefinitionReader 對象,並進行了一系列的初始化操作,包括 EnvironmenttResourceLoadeEntityResolver 等。

  • 使用 XmlBeanDefinitionReader 對象提供的 loadBeanDefinitions 接口方法來加載 BeanDefinition 對象
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}

可見,上述方法首先獲得配置的路徑,之後遍歷所有路徑,將 BeanDefinition 解析出來。查看源碼可知,這裏所調用的 loadBeanDefinitions 方法繼承自 XmlBeanDefinitionReader 的父類 AbstractBeanDefinitionReader :

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		//......
		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				//......
		}
		//......
	}

這段代碼主要做的事情是,使用 BeanDefinitionReader 對象所持有的 ResourceLoader 來生成 Resource 對象。然後調用 BeanDefinitionReader 的重載方法 loadBeanDefinitions(在 XmlBeanDefinitionReader 實現) 方法來執行加載 BeanDefinition 。如下:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		/*抽取部分代碼*/
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
            //......
		}
		//......
	}

Resource 對象中獲取xml文件輸入流,並用它來創建 InputSource 對象。然後調用 XmlBeanDefinitionReaderdoLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
    /*抽取部分代碼*/
		try {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}
		//......
	}

其過程是讀取XML內容並創建 Document 對象,然後調用 registerBeanDefinitions(Document doc, Resource resource) 方法來處理剛創建的 Document 對象。registerBeanDefinitions 如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

以上代碼流程圖如下:

Created with Raphaël 2.2.0Start創建BeanDefinitionDocumentReader對象創建XmlReaderContext上下文對象執行DefaultBeanDefinitionDocumentReader的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法。End

在處理Document時,找到文檔根節點,然後遞歸獲取所有元素,代碼如下:

@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		Element root = doc.getDocumentElement();
		doRegisterBeanDefinitions(root);
	}
protected void doRegisterBeanDefinitions(Element root) {
		// Any nested <beans> elements will cause recursion in this method. 
		/*忽略具體實現*/
	}

在獲取元素時,流程大致如下:

Created with Raphaël 2.2.0Start創建BeanDefinitionParserDelegate對象檢查bean標籤的profile屬性值是否與環境的匹配執行parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法Endyesno

在處理具體的節點時,會執行 DefaultBeanDefinitionDocumentReaderparseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法,如下:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			doRegisterBeanDefinitions(ele);
		}
	}

這段代碼是處理import、beans、alias、bean標籤的入口方法。

  • import標籤是引入其它spring配置文件;
  • beans標籤是對bean進行分類配置,比如用一個beans來管理測試環境的bean,用另一個beans來管理生產環境的bean;
  • alias標籤是爲一個已定義了的bean取別名,它的name屬性值是bean的id,alias屬性值是要取的別名,多個別名用英文逗號、分號或者空格隔開;
  • bean標籤的信息就是spring要實例化的對象。

不管是什麼標籤,只有利用bean標籤纔會生成BeanDefinition對象,接下來纔是生產 BeanDefinition 的關鍵,解析 bean 節點,並註冊 BeanDefinition 對象:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

這段代碼分成三步。第一步,根據傳入的 Element 對象(bean 標籤的)調用代理對象的***parseBeanDefinitionElement(Element ele)*** 方法創建 BeanDefinitionHolder 對象,這個對象持有創建好的 BeanDefinition 對象、beanidbean 的別名。

第二步,調用代理對象的 decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) 來對 BeanDefinition 對象再加工,主要是解析 bean 標籤中自定義屬性和自定義標籤。

第三步,調用工具類 BeanDefinitionReaderUtilsregisterBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 方法,這個方法用於註冊創建好的 BeanDefinition

至此,XmlBeanDefinitionReader 解析 Xml 文件,主要的步驟可以概括爲以下幾個過程:

  • 調用 *ResourceLoader *從入口(默認是 /WEB-INF/applicationContext.xml)開始獲取xml文檔,並把它交給 DocumentLoader
  • DocumentLoaderResource 對象中的 XML 文件內容轉換爲 Document 對象。默認使用 DocumentLoader 的實現類 DefaultDocumentLoader 來加載 Document 對象。
  • BeanDefinitionDocumentReader,它把 Document 對象中包含的配置信息轉換成 BeanDefinition 對象並把它註冊到 BeanDefintionRegistry 對象中。默認使用 DefaultBeanDefinitionDocumentReader 來操作***Document*** 對象。在 DefaultBeanDefinitionDocumentReader 的實現中,它的責任是遍歷 xml 根節點下的子節點,並把處理 bean 標籤細節委託給 BeanDefinitionParserDelegate 對象 。
  • BeanDefinitionParserDelegate 纔是真正解析配置文件的地方。
  • 解析出來的 BeanDefinition 則註冊到 BeanDefinitionRegistry 註冊表中。
3. Bean 的預處理
3.1 refresh 方法

refresh 方法的實現類是抽象類 AbstractApplicationContext,繼承了 ConfigurableApplicationContext 等接口

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
            //......
			try {
				//......
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);
                //......
            }
		}
	}

refresh() 方法的主要作用是對 Ioc 容器的刷新,在此過程中,會調用前面所說的Bean的掃描中的一系列操作。對於Bean的實例化來說,其中關鍵的一個環節是 invokeBeanFactoryPostProcessors(beanFactory)

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

		// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
		// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
		if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}
}

Bean 的預處理中會將對 BeanDefinition 進行處理,主要如下:

  • 根據依賴關係構建 Bean 之間的依賴關係圖(有向圖)
  • 根據 @XXXConditionalBean 進行篩選,去除不需要實例化的 Bean
4. Bean的實例化
package org.springframework.beans.factory.support;
/**
 * Interface responsible for creating instances corresponding to a root bean definition.
 */
public interface InstantiationStrategy {
	/**
	 * Return an instance of the bean with the given name in this factory.
	 */
	Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner)
			throws BeansException;
	Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner,
			Constructor<?> ctor, Object... args) throws BeansException;
	Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner,
			Object factoryBean, Method factoryMethod, Object... args) throws BeansException;
}

此部分略過,重點討論 @Conditional 相關注解的生效過程

5. SpringBoot的核心
5.1 @EnableAutoConfiguration

SpringBoot 在啓動時,加載了 @SpringBootApplication 註解主配置類,這個 @SpringBootApplication 註解主配置類裏邊最主要的功能就是 SpringBoot 開啓了一個 @EnableAutoConfiguration 註解的自動配置功能。

@EnableAutoConfiguration 主要利用了一個 AutoConfigurationImportSelector (或者是 繼承了 AutoConfigurationImportSelectorEnableAutoConfigurationImportSelector)選擇器給 Spring 容器中來導入一些組件。

@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

AutoConfigurationImportSelector 中,有一個 selectImports 方法:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }

最關鍵的地方,就是 configurations ,獲取候選的配置,調用的方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

利用 SpringFactoriesLoaderloadFactoryNames 從類加載路徑下獲取一個資源

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

loadSpringFactories 方法中,

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		//......
	}

其中 FACTORIES_RESOURCE_LOCATION

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

SpringFactoriesLoader 加載的是 META-INF/spring.factories 文件,其作用是掃描這個文件,遍歷其所有 url,整合成 Properties,並加入到 MultiValueMap 中。返會的對象中包含了要交給Spring容器中的所有組件,所以容器中最終會添加很多的類,而這些類在 META-INF/spring.factories 文件中被定義,每一個類都是自動配置的開始。再看 SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())
方法中傳進去的參數:

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

返回的是 EnableAutoConfiguration 的class文件,這個時候,毫不猶豫想到了 java的反射機制

5.2 @Conditional

SpringBoot 將所有的 XXXAutoConfiguration 加入到容器中後,便開始自動裝配。再來研究其裝配過程,在 META-INF/spring.factories 所包含的大部分 XXXAutoConfiguration 類中,都有 @XXXConditional 註解。

抽絲剝繭,找到 OnClassCondition 中的一個關鍵方法 match

@Override
	public boolean[] match(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		ConditionEvaluationReport report = getConditionEvaluationReport();
		ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses,
				autoConfigurationMetadata);
		boolean[] match = new boolean[outcomes.length];
		for (int i = 0; i < outcomes.length; i++) {
			match[i] = (outcomes[i] == null || outcomes[i].isMatch());
			if (!match[i] && outcomes[i] != null) {
				logOutcome(autoConfigurationClasses[i], outcomes[i]);
				if (report != null) {
					report.recordConditionEvaluation(autoConfigurationClasses[i], this,
							outcomes[i]);
				}
			}
		}
		return match;
	}

拋開實現,先觀察其傳入參數 autoConfigurationClassesautoConfigurationMetadata

private List<String> filter(List<String> configurations,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		//......
		String[] candidates = configurations.toArray(new String[configurations.size()]);
		//......
		for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
			//......
			boolean[] match = filter.match(candidates, autoConfigurationMetadata);
			//......
		}
		//......
		return new ArrayList<String>(result);
	}

可見,其中一個參數來自於方法 filter 的一個參數 configuration

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		//......
		try {
			//......
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
			//......
			configurations = filter(configurations, autoConfigurationMetadata);
			//......
			return configurations.toArray(new String[configurations.size()]);
		}
		//......
	}

可見,最終的數據是從 getCandidateConfigurations() 方法中獲取的所有配置的候選項,即每個開啓自動配置的類,再看第二個參數:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		//......
		try {
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			//......
		}
		//......
	}

來源於 AutoConfigurationMetadata.loadMetadata(this.beanClassLoader) 方法:

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

其中 PATH 的值是

protected static final String PATH = "META-INF/"
			+ "spring-autoconfigure-metadata.properties";
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path));
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils
						.loadProperties(new UrlResource(urls.nextElement())));
			}
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException(
					"Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

可見第二個參數 autoConfigurationMetadata 來源於 META-INF/spring-autoconfigure-metadata.properties 文件,找一份該文件抽取一部分展示如下:

org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.ConditionalOnClass=com.couchbase.client.java.CouchbaseBucket,com.couchbase.client.java.Cluster
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.core.RabbitTemplate,com.rabbitmq.client.Channel
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureOrder=-2147483648
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseConfigurerAdapterConfiguration.ConditionalOnClass=org.springframework.data.couchbase.config.CouchbaseConfigurer

可見,第二個參數也是類信息,只不過它是當前正在加載類中的信息。

再返回分析match方法中實現的功能,首先看看 getOutcomes() 方法:

private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		int split = autoConfigurationClasses.length / 2;
		OutcomesResolver firstHalfResolver = createOutcomesResolver(
				autoConfigurationClasses, 0, split, autoConfigurationMetadata);
		OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
				autoConfigurationClasses, split, autoConfigurationClasses.length,
				autoConfigurationMetadata, this.beanClassLoader);
		ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
		ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
		ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
		System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
		System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
		return outcomes;
	}

該部分利用二分法,分兩個 OutcomesResolverresolveOutcomes

@Override
		public ConditionOutcome[] resolveOutcomes() {
			return getOutcomes(this.autoConfigurationClasses, this.start, this.end,
					this.autoConfigurationMetadata);
		}
private ConditionOutcome[] getOutcomes(final String[] autoConfigurationClasses,
				int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
			ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
			for (int i = start; i < end; i++) {
				String autoConfigurationClass = autoConfigurationClasses[i];
				Set<String> candidates = autoConfigurationMetadata
						.getSet(autoConfigurationClass, "ConditionalOnClass");
				if (candidates != null) {
					outcomes[i - start] = getOutcome(candidates);
				}
			}
			return outcomes;
		}

具體實現如上所述:從配置文件中尋找條件註解上對應的類,並返回結果。那麼,至此match()方法的作用則是從兩個文件中的url關係中,尋找是否滿足條件的結果。

5.3 手動實現一個starter

A :業務項目

B :starter

C :service提供者

A 的pom文件中,有B,但是沒有C,開啓debug,運行程序:

============================
CONDITIONS EVALUATION REPORT
============================

Positive matches:
-----------------

   ExampleAutoConfigure matched:
      - @ConditionalOnClass found required class 'com.ncuwen.server.ExampleService'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

   ExampleAutoConfigure#exampleService matched:
      - @ConditionalOnProperty (example.server.enable=true) matched (OnPropertyCondition)
          
Negative matches:
-----------------
ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition)

AopAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required classes 'org.aspectj.lang.annotation.Aspect', 'org.aspectj.lang.reflect.Advice', 'org.aspectj.weaver.AnnotatedElement' (OnClassCondition)
Exclusions:
-----------
    None
Unconditional classes:
----------------------
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration  
16:57:08.629 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.ncuwen.studyspring.Application for test class com.ncuwen.studyspring.ApplicationTests

16:57:08.895 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
16:57:08.915 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [javax/servlet/ServletContext]

16:57:08.919 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@62150f9e, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@1a451d4d, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@7fa98a66, org.springframework.test.context.support.DirtiesContextTestExecutionListener@15ff3e9e, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@5fdcaa40, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@6dc17b83, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@5e0826e7, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@32eff876, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@8dbdac1]

16:57:08.926 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.ncuwen.studyspring.ApplicationTests]

所以,SpringBoot 的實現不僅使用了反射機制,更維護了一個 spring-autoconfigure-metadata.properties 文件。
其實 SpringBoot 對於自動配置的實現,除了使用了反射機制,更使用了字節碼操作。

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