从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.0 Start 创建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.0 Start 创建BeanDefinitionParserDelegate对象 检查bean标签的profile属性值是否与环境的匹配 执行parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法 End yes no

在处理具体的节点时,会执行 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 对于自动配置的实现,除了使用了反射机制,更使用了字节码操作。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值