從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 又實現了 EnvironmentCapable 和 BeanDefinitionReader 接口,其中實現了以下方法:
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 對象的容器有 XmlWebApplicationContext 、ClassPathXmlApplicationContext、FileSystemXmlApplicationContext 等三個容器,以 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 對象,並進行了一系列的初始化操作,包括 Environment,tResourceLoade,EntityResolver 等。
- 使用 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 對象。然後調用 XmlBeanDefinitionReader 的 doLoadBeanDefinitions(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;
}
以上代碼流程圖如下:
在處理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.
/*忽略具體實現*/
}
在獲取元素時,流程大致如下:
在處理具體的節點時,會執行 DefaultBeanDefinitionDocumentReader 的 parseDefaultElement(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 對象、bean 的 id 和 bean 的別名。
第二步,調用代理對象的 decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) 來對 BeanDefinition 對象再加工,主要是解析 bean 標籤中自定義屬性和自定義標籤。
第三步,調用工具類 BeanDefinitionReaderUtils 的 registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 方法,這個方法用於註冊創建好的 BeanDefinition。
至此,XmlBeanDefinitionReader 解析 Xml 文件,主要的步驟可以概括爲以下幾個過程:
- 調用 *ResourceLoader *從入口(默認是 /WEB-INF/applicationContext.xml)開始獲取xml文檔,並把它交給 DocumentLoader
- DocumentLoader 把 Resource 對象中的 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 之間的依賴關係圖(有向圖)
- 根據 @XXXConditional 對 Bean 進行篩選,去除不需要實例化的 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 (或者是 繼承了 AutoConfigurationImportSelector 的 EnableAutoConfigurationImportSelector)選擇器給 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;
}
利用 SpringFactoriesLoader 的 loadFactoryNames 從類加載路徑下獲取一個資源
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;
}
拋開實現,先觀察其傳入參數 autoConfigurationClasses 和 autoConfigurationMetadata :
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;
}
該部分利用二分法,分兩個 OutcomesResolver 去 resolveOutcomes :
@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 對於自動配置的實現,除了使用了反射機制,更使用了字節碼操作。