一、auto-configuration introduction
自动配置是springboot的一大特性,它能在我们添加相应的jar包依赖的时候,自动为我们配置了这些组件的相关配置,我们无需配置或者只需要少量的配置就能运行我们编写的项目。官网也对自动配置作了详细说明:
Spring Boot auto-configuration attempts to automatically configure your Spring application based on the jar dependencies that you have added. For example, if HSQLDB is on your classpath, and you have not manually configured any database connection beans, then Spring Boot auto-configures an in-memory database.
上诉语句翻译为中文为:SpringBoot自动配置尝试根据您添加的JAR依赖性自动配置您的Spring应用程序。例如,如果HSQLDB在您的类路径上,并且您没有手动配置任何数据库连接bean,那么spring boot会自动配置内存中的数据库。
如果需要设置自动配置的话,方法是将@EnableAutoconfiguration注解添加到您的@configuration类中。
二、How to Realize Auto-Configuration(底层)
1、要研究springboot的自动配置需要从主程序类开始入手,请看下面代码:
@SpringBootApplication
public class MMVirusScanApplication {
public static void main(String[] args) {
SpringApplication.run(MMVirusScanApplication.class, args);
}
}
2、这就是主程序类,这个类最主要的部分是@SpringBootApplication,正是通过添加了这个注解,springboot应用才能正常启动。再继续查看@SpringbootApplication注解组成部分:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}
这个注解最主要部分是:
- @SpringBootConfiguration:这个注解标注在某类上,说明这个类是一个springboot配置类
- @EnableAutoConfiguration:这个注解就是springboot能实现自动配置的关键
- @ComponentScan:这个注解是组件扫描这个是我们最熟悉的注解,即使没有使用过注解也经常在spring的配置文件中使用过
<context:component-scan base-package="com.xxx.xxx"/>
, 组件扫描就是扫描指定的包下的类,并加载符合条件的组件。
3、继续研究@EnableAutoConfiguration注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以发现它是一个组合注解,Spring 中有很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的bean,并加载到IoC容器。@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。里面最主要注解是:
- @AutoConfigurationPackage:自动配置包,它也是一个组合注解,其中最重要的注解是@Import(AutoConfigurationPackages.Registrar.class),它是spring框架的底层注解,它的作用就是给容器中导入某个组件类,例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中,可查看Registrar类中registerBeanDefinitions方法,这个方法就是导入组件类的具体实现。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//将注解标注的元信息传入,获取到相应的包名
register(registry, new PackageImport(metadata).getPackageName());
}
通过对registerBeanDefinitions方法进行DeBug,运行结果如下:
可以看到AnnotationMetadata(注解标注注的元信息中包含了使用了哪些注解,相应的注解作用在哪个类上)
我们对new PackageImport(metadata).getPackageName()进行检索(idea工具可以圈出需要查询的值,使用快捷键“Ctrl+U”),看看其结果是什么?
因此可以得知使用@AutoConfigurationPackage注解就是将主程序类所在包及所有子包下的组件到扫描到spring容器中
- @Import({AutoConfigurationImportSelector.class}):将AutoConfigurationImportSelector这个类导入到spring容器中,AutoConfigurationImportSelector可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中。
4、继续研究AutoConfigurationImportSelector这个类,通过源码分析这个类中是通过selectImports这个方法告诉springboot都需要导入那些组件:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//获得自动配置元信息,需要传入beanClassLoader这个类加载器
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
4.1、深入研究loadMetadata方法
protected static final String PATH = "META-INF/"
+ "spring-autoconfigure-metadata.properties"; //文件中为需要加载的配置类的类路径
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
//读取spring-boot-autoconfigure-2.1.5.RELEASE.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
//解析urls枚举对象中的信息封装成properties对象并加载
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils
.loadProperties(new UrlResource(urls.nextElement())));
}
//根据封装好的properties对象生成AutoConfigurationMetadata对象返回
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException(
"Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
4.2、深入研究getAutoConfigurationEntry方法
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);
}
4.2.1、深入getCandidateConfigurations方法
这个方法中有一个重要方法loadFactoryNames,这个方法是让SpringFactoryLoader去加载一些组件的名字。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
/**
* 这个方法需要传入两个参数getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
* getSpringFactoriesLoaderFactoryClass()这个方法返回的是EnableAutoConfiguration.class
* getBeanClassLoader()这个方法返回的是beanClassLoader(类加载器)
*/
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;
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
继续点开loadFactory方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
//获取出入的键
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装 为Enumeration类对象
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
//循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
5、总结
因此springboot底层实现自动配置的步骤是:
- springboot应用启动;
- @SpringBootApplication起作用;
- @EnableAutoConfiguration;
- @AutoConfigurationPackage:这个组合注解主要是@Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;
- @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程,具体实现可查看上面贴附的源码。
三、Custom Project to Realize Auto-Configuration
如果突发奇想刚好项目需求需要使用自动配置作一些操作,那么接下来的环节将对你特别有帮助,精彩环节请不要离开。
1、new创建maven项目
1.1、我这里以IntelliJ IDEA创建Project为例,其实很简单,创建一个Maven项目,但是注意创建的时候选择quickstart,步骤如下:
1.2、点击next,输入GroupId和ArtifactId,继续字母都为小写,一般以“com”开头:
找到next继续下一步。
1.3、配置好maven相关配置
1.4、输入project name和设置好项目存放位置点击finish即可。
2、引入依赖
创建完项目后在pom文件中引入spring-boot-autoconfigure 和httpclient的依赖,具体pom文件如下所示:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>customproject</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging> <!--记住打包方式为jar-->
<name>customproject Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<!--引入httpclient依赖-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<!--引入spring-boot-autoconfigure依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
<!--lombok依赖 简化实体类,封装了set和get等方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
</dependencies>
<!--添加此配置能在maven使用install等命令时将resources目录下文件都引入-->
<build>
<finalName>customproject</finalName>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>
</project>
3、HttpClient 配置信息的配置类 HttpClientProperties
@Data
@ConfigurationProperties(prefix = "spring.httpclient")
class HttpClientProperties {
private Integer connectTimeout = 1000;//创建连接的最长时间
private Integer socketTimeout = 10000;//数据传输的最长时间
private String agent = "agent";
private Integer maxPerRoute = 10;//设置每个路由的并发数
private Integer maxTotal = 50;//最大连接数
}
@ConfigurationProperties注解作用:读取application.properties文件中的内容,根据配置的prefix属性,将prefix属性
对应的内容生成键值对复制给使用了@ConfigurationProperties注解类中
当我们创建好配置信息类之后,使用@ConfigurationProperties注解是会出错,出错内容如图所示:
原因是我们没有使用到@EnableConfigurationProperties注解,当我们使用了之后就不会出错了。
4、配置自动配置类
@Configuration
@ConditionalOnClass({HttpClient.class})
@EnableConfigurationProperties(HttpClientProperties.class)
public class HttpClientAutoConfiguration {
private HttpClientProperties httpClientProperties;
public HttpClientAutoConfiguration(HttpClientProperties httpClientProperties) {
this.httpClientProperties = httpClientProperties;
}
@Bean
@ConditionalOnMissingBean(HttpClient.class)
public HttpClient httpClient() {
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(httpClientProperties.getConnectTimeout())
.setSocketTimeout(httpClientProperties.getSocketTimeout()).build();
HttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
.setUserAgent(httpClientProperties.getAgent()).setMaxConnPerRoute(httpClientProperties.getMaxPerRoute())
.setConnectionReuseStrategy(new NoConnectionReuseStrategy()).build();
return httpClient;
}
}
@Configuration:标注该类是一个配置类
@Bean:相当于原始的在xml文件中配置<bean id="">,声明在方法上用于将实例对象注入Spring上下文中。
@ConditionalOnClass:该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类;
@ConditionalOnMissingBean:该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean;可以给该注解传入参数例如@ConditionOnMissingBean(name = “example”),这个表示如果name为“example”的bean存在,这该注解修饰的代码块不执行。
@EnableConfigurationProperties:会将会HttpClientProperties 作为一个Bean引入HttpClientAutoConfiguration 中。
5、注册配置
正常情况下我们按步骤一创建出来的Project是没有resources这个文件夹的,在IntelliJ IDEA这个工具中,我们需要先创建一个directory,然后将之设置为resources root即可,设置方式如下:选中resources目录右键单击–》Mark Directory as–》Resource Root。
在resources目录下新建META-INF目录,然后在META-INF目录下创建spring.factories文件,文件内容如下,表示设置自动配置类的位置,若有多个配置类用”,”隔开即可。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.lijunkui.autoconfig.HttpClientAutoConfiguration #此处记得一定要配置HttpClientAutoConfiguration类所在包路径
6、项目中使用
使用maven的install安装到本地仓库后,在创建好springboot之后,添加上依赖,就可以很方便的使用了。