您的位置:首页 > 编程语言 > Java开发

从Spring中Bean的产生谈到SpringBoot的核心原理

2018-09-25 11:03 399 查看
版权声明:原创博文,若需转载,请与博主联系 https://blog.csdn.net/qq_38958113/article/details/82836400

从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 又实现了 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;
[/code]

以上方法返回的显然不是 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);
}
[/code]

上述方法定义了一个 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);
}
}
}
[/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 对象。然后调用 XmlBeanDefinitionReaderdoLoadBeanDefinitions(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

在处理具体的节点时,会执行 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);
}
}
[/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 对象、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);
//......
}
}
}
[/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 之间的依赖关系图(有向图)
  • 根据 @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;
}
[/code]

此部分略过,重点讨论 @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 {};
}
[/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]

利用 SpringFactoriesLoaderloadFactoryNames 从类加载路径下获取一个资源

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 @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;
}
[/code]

抛开实现,先观察其传入参数 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);
}
[/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]

该部分利用二分法,分两个 OutcomesResolverresolveOutcomes

@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 手动实现一个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
[/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 对于自动配置的实现,除了使用了反射机制,更使用了字节码操作。

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: