从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(); }[/code]
由此可见,每个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 { }[/code]
可见 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;[/code]
以上方法返回的显然不是 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); }[/code]
上述方法定义了一个 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); } } }[/code]
可见,上述方法首先获得配置的路径,之后遍历所有路径,将 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); //...... } //...... }[/code]
这段代码主要做的事情是,使用 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()); } //...... } //...... }[/code]
从 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); } //...... }[/code]
其过程是读取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; }[/code]
以上代码流程图如下:
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); }[/code]
protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. /*忽略具体实现*/ }[/code]
在获取元素时,流程大致如下:
Created with Raphaël 2.2.0Start创建BeanDefinitionParserDelegate对象检查bean标签的profile属性值是否与环境的匹配执行parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法Endyesno在处理具体的节点时,会执行 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); } }[/code]
这段代码是处理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)); } }[/code]
这段代码分成三步。第一步,根据传入的 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); //...... } } }[/code]
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())); } }[/code]
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; }[/code]
此部分略过,重点讨论 @Conditional 相关注解的生效过程
5. SpringBoot的核心
5.1 @EnableAutoConfigurationSpringBoot 在启动时,加载了 @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 {}; }[/code]
在 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); } }[/code]
最关键的地方,就是 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; }[/code]
利用 SpringFactoriesLoader 的 loadFactoryNames 从类加载路径下获取一个资源
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }[/code]
在 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; } //...... }[/code]
其中 FACTORIES_RESOURCE_LOCATION :
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";[/code]
SpringFactoriesLoader 加载的是 META-INF/spring.factories 文件,其作用是扫描这个文件,遍历其所有 url,整合成 Properties,并加入到 MultiValueMap 中。返会的对象中包含了要交给Spring容器中的所有组件,所以容器中最终会添加很多的类,而这些类在 META-INF/spring.factories 文件中被定义,每一个类都是自动配置的开始。再看 SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()) 方法中传进去的参数:
protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }[/code]
返回的是 EnableAutoConfiguration 的class文件,这个时候,毫不犹豫想到了 java的反射机制。
5.2 @ConditionalSpringBoot 将所有的 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; }[/code]
抛开实现,先观察其传入参数 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); }[/code]
可见,其中一个参数来自于方法 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()]); } //...... }[/code]
可见,最终的数据是从 getCandidateConfigurations() 方法中获取的所有配置的候选项,即每个开启自动配置的类,再看第二个参数:
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { //...... try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); //...... } //...... }[/code]
来源于 AutoConfigurationMetadata.loadMetadata(this.beanClassLoader) 方法:
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) { return loadMetadata(classLoader, PATH); }[/code]
其中 PATH 的值是
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";[/code]
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); } }[/code]
可见第二个参数 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; }[/code]
该部分利用二分法,分两个 OutcomesResolver 去 resolveOutcomes :
@Override public ConditionOutcome[] resolveOutcomes() { return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata); }[/code]
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; }[/code]
具体实现如上所述:从配置文件中寻找条件注解上对应的类,并返回结果。那么,至此match()方法的作用则是从两个文件中的url关系中,寻找是否满足条件的结果。
5.3 手动实现一个starterA :业务项目
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[/code]
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][/code]
所以,SpringBoot 的实现不仅使用了反射机制,更维护了一个 spring-autoconfigure-metadata.properties 文件。
其实 SpringBoot 对于自动配置的实现,除了使用了反射机制,更使用了字节码操作。
- SpringBoot 核心模块原理剖析
- SpringBoot 核心模块原理剖析
- Spring Boot核心原理-自动配置
- Spring Boot核心原理-自动配置 以及@ConfigurationProperties 注解
- Spring Boot核心原理-自动配置
- (精)Spring IOC核心源码学习III:bean标签和自定义标签实现原理
- Spring Boot实战:Spring Boot核心原理剖析
- Spring Boot核心原理-自动配置
- 牛班师兄系列之--Springboot核心原理
- Spring Boot核心原理-自动配置
- Spring应用、原理以及粗读源码系列(一)--框架总述、以Bean为核心的机制(IoC容器初始化以及依赖注入)
- Spring Boot实战:Spring Boot核心原理剖析
- springboot自动配置的核心原理
- SpringBoot核心原理:自动化配置1
- SpringBoot核心原理:自动化配置2
- Spring Boot核心原理-自动配置
- SpringBoot 核心模块原理剖析已经出炉
- SpringBoot15-springboot核心-Spring Boot运行原理
- 【SpringBoot】的核心配置和运行原理
- 20-SpringBoot——核心-运行原理