SpringBoot2.2.5自动装配分析及手动实现

1.新建sprigboot项目

项目pox.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.demo.springboot</groupId>
    <artifactId>autoconfig-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>autoconfig-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

除了项目的GAV之外都是默认的。

2.自撸基本思路

SpringBoot启动类,默认是@SpringBootApplication注解,而该注解主要有三个注解构成,即:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。

各注解的作用参考官方文档"3.6. Using the @SpringBootApplication Annotation"

参考对标@EnableAutoConfiguration注解,我们可以理出如下思路:

  • 该注解首先通过@Import导入AutoConfigurationImportSelector.class
@Import(AutoConfigurationImportSelector.class)
  • AutoConfigurationImportSelector的核心函数selectImports调用getAutoConfigurationEntry,返回的是String[]。
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
  • 跟进getAutoConfigurationEntry函数,核心调用的是getCandidateConfigurations。
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

  • 继续跟进getCandidateConfigurations,核心调用的是SpringFactoriesLoader.loadFactoryNames。
	/**
	 * Return the auto-configuration class names that should be considered. By default
	 * this method will load candidates using {@link SpringFactoriesLoader} with
	 * {@link #getSpringFactoriesLoaderFactoryClass()}.
	 * @param metadata the source metadata
	 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
	 * attributes}
	 * @return a list of candidate 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,loadFactoryNames调用楼下的loadSpringFactories,返回的结果再链式调用getOrDefault,真正干活的出现了,就是loadSpringFactories!
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

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()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

这里重点先看下面代码,即若classLoader参数不为空,就是说有传类加载器进来,而在本例中其实就是AutoConfigurationImportSelector的类加载器。

Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

若有传如类加载器进来就获得本类所在的资源文件,否则获得系统的资源文件,即"Finds all resources of the specified name from the search path used to load classes."
本例中传入的是org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.class的类加载器,所以自然就是找该类所在的包里的资源文件。

常量FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”

这样就真相大白了,原来springboot的autoconfigure,其实就是让SpringFactoriesLoader去加载指定类所在位置的资源文件!

接下来我们看看org.springframework.boot.autoconfigure包里的spring.factories文件

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
中间省略N行...总共有100+行
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

特别注意# Auto Configure部分有个

org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider

的键,说明这个键跟后面100多个类有关联。

接着看代码,发现loadSpringFactories将将配置文件加载进来,生成一个MultiValueMap,放入缓存并返回。

接着看链式调用的getOrDefault函数,逻辑简单说就是以传入的类名为参数,返回的MultiValueMap类型变量中存在以本类名为键值的就返回对应的类List,否则返回空List。

最终,这个List返回给了selectImports,然后交由org.springframework.beans.factory.support.DefaultListableBeanFactory通过反射生成一个又一个的单例对象,类的自动扫描装配到此结束。

3.依葫芦画瓢
3.1 建立自动装配注解@DemoEnableAutoConfig
package com.demo.springboot.autoconfigdemo.autoconfig;

import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author : Alex Hu
 * date : 2020/3/20 下午13:27
 * description : 自定义个注解,模拟springboot的@EnableAutoConfiguration
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(value = {AutoConfigurationImportSelector.class})
public @interface DemoEnableAutoConfig {
}
3.2 实现AutoConfigurationImportSelector.class
package com.demo.springboot.autoconfigdemo.autoconfig;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * @author : Alex Hu
 * date : 2020/3/20 上午08:55
 * description : springboot 自动装配方式,从properties文件批量导入
 */
public class AutoConfigurationImportSelector implements ImportSelector {
    //返回注解的class, META-INF/spring.factories 中的键名
    private Class<?> getSpringFactoriesLoaderFactoryClass() {
        return DemoEnableAutoConfig.class;
    }

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        List<String> configurations = getCandidateConfigurations();
        System.out.println("configurations: " + configurations);
        return StringUtils.toStringArray(configurations);
    }


    protected List<String> getCandidateConfigurations() {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                //返回注解类的class
                getSpringFactoriesLoaderFactoryClass(),
                //当前类的classloader
                this.getClass().getClassLoader()
        );
        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;
    }

}

3.3 建立配置文件

文件位于resources/META-INF/目录,文件名spring.factories

com.demo.springboot.autoconfigdemo.autoconfig.DemoEnableAutoConfig=\
com.demo.springboot.autoconfigdemo.classes.ClassA,\
com.demo.springboot.autoconfigdemo.classes.ClassB

表示扫描ClassA与ClassB这两个类

3.4 修改启动程序
package com.demo.springboot.autoconfigdemo;

import com.demo.springboot.autoconfigdemo.autoconfig.DemoEnableAutoConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author Alex Hu
 */
@DemoEnableAutoConfig
//@ComponentScan
public class AutoconfigDemoApplication {

    public static void main(String[] args) {
      SpringApplication.run(AutoconfigDemoApplication.class, args);
    }

}

3.5 检查成果

运行启动程序,结果如下:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.5.RELEASE)

2020-03-20 17:04:50.745  INFO 18192 --- [           main] c.d.s.a.AutoconfigDemoApplication        : Starting AutoconfigDemoApplication on huqiupingdeMacBook-Pro.local with PID 18192 (/Users/alexhu/IdeaProjects/autoconfig-demo/target/classes started by alexhu in /Users/alexhu/IdeaProjects/boot-demo)
2020-03-20 17:04:50.747  INFO 18192 --- [           main] c.d.s.a.AutoconfigDemoApplication        : No active profile set, falling back to default profiles: default
configurations: [com.demo.springboot.autoconfigdemo.classes.ClassA, com.demo.springboot.autoconfigdemo.classes.ClassB]
ClassA.ClassA  构造器被执行
ClassB.ClassB  构造器被执行
2020-03-20 17:04:50.863  INFO 18192 --- [           main] c.d.s.a.AutoconfigDemoApplication        : Started AutoconfigDemoApplication in 0.5 seconds (JVM running for 0.92)

Process finished with exit code 0

目标两个类的构造函数被执行,说明spring帮我们实例化了这两个类。

3.6 题外话

若本例中的spring.factories,配置中的键直接修改为:

org.springframework.boot.autoconfigure.EnableAutoConfiguration

启动程序的注解直接用系统自带的@EnableAutoConfiguration,又会是什么结果?大家可以先猜,猜完可以再试 ^ _^

4.总结

其实SpringBoot的自动装配核心内容并不复杂,调试跟进源代码是法宝。
本演示项目源代码在这里

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值