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,其实就是让<font color=red>SpringFactoriesLoader去加载指定类所在位置的资源文件!</font>
接下来我们看看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,又会是什么结果?大家可以先猜,猜完可以再试 ^ _^
其实SpringBoot的自动装配核心内容并不复杂,调试跟进源代码是法宝。
本演示项目源代码在这里
网友评论