前言
我们使用SpringBoot的时候,只需要引入跟SpringBoot的start包,比如mybatis-spring-boot-starter就可实现与Mybatis的结合,DataSource的配置就可以在application.properties或者application.yml中配置。那到底为什么只要引入了跟SpringBoot的starter包依赖就可以实现自动装配呢?今天就来简单的聊聊其中的原理。
解析
一个SpringBoot项目,一般都会有一个启动类,只要运行启动类中的main方法,就可以启动SpringBoot项目。比如下面这段代码,标准的SpringBoot启动类1
2
3
4
5
6
7
8
9
10
11
12
13package com.lnw;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
//@Configuration
//@EnableAutoConfiguration
//@ComponentScan({"com.lnw","com.lnw"})
public class SpringbootSwaggerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSwaggerApplication.class, args);
}
}
简单的说run方法就是启动一个Spring容器,然后刷新容器,这里就不再深入,我们只要知道, 如果一个@Configuration的配置类如果会被Spring扫描到,那么这个配置类里面的配置就会被Spring所加载 。这里最重要的就是@SpringBootApplication注解,它是SpringBoot自动装配的密码所在。
我们来看下这个注解
@SpringBootApplication
1 | @Target(ElementType.TYPE) |
我们看到注解里面还有注解,分别是@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。其中@SpringBootCofuration这个注解是没什么作用的,@ComponentScan注解主要是做扫描当前包下的加了注解的类, @EnableAutoConfiguration 才是关键,也就是说只要加了这个@EnableAutoConfiguration注解就可以打开SpringBoot的自动配置的功能。我们看它的源码
@EnableAutoConfiguration
1 | package org.springframework.boot.autoconfigure; |
这里引用了一个@Import注解,里面注入了一个AutoConfigurationImportSelector类。
AutoConfigurationImportSelector
SpringBoot运行后,就会创建Spring的容器,容器的创建需要一些配置类做初始化,比如@Configuration或者@Import注解的类,所以Spring容器创建之初会执行AutoConfigurationImportSelector里面的代码,最重要的方法就是org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations1
2
3
4
5
6
7
8
9protected 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就是扫描当前项目以及项目依赖的所有Configuration
SpringFactoriesLoader.loadFactoryNames方法是这样的,它会扫描所有的jar包,如果这个jar中有META-INF/spring.factories文件,就把spring.factories文件的内容(描述配置类的全路路径)扫描到List集合中,稍后给Spring-context包处理
在spring-boot-autoconfigure包中的META-INF/spring.factories文件中,定义了差不多100多个配置
这些配置只要添加了跟SpringBoot的starter依赖,就会被加载
比如添加了Redis的starter包,spring.factories文件中已经定义了
所以这个类会被Spring扫描到,这个类就是一个@Configuration类
如果SpringBoot的spring.factories没有它规定的starter,比如Mybatis,那怎么办?那就自己开发一个autoconfigure,只要在你自己的META/INF里面声明一个spring.factories文件,把配置类写上,就可以了。比如mybatis就开发了一个跟SpringBoot整合的autoconfigure(它是自己开发的,不是Spring开发的)
里面的内容是
而这个MybatisAutoConfiguration类,就是一般的配置类,就跟我们平时的@Configuration配置类一样
这个配置类说明了@Configuration生效的条件,比如要有SqlSessionFactory类,要有DataSource这个Bean,要在DataSourceAutoConfiguration这个配置文件执行后
@EnableConfigurationProperties 这个注解可以从application.properties或者application.yml中读取需要的配置
比如里面的MybatisProperties
这个类会读取mybatis前缀的属性,比如
模拟
我们可以自己写一个autoconfiguration,实现自动装配,项目的结构如下图,项目可以是普通项目也可以是springboot项目1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.lnw.config;
@EnableConfigurationProperties(LNWProperties.class)
@Configuration
@ConditionalOnClass(LNWService.class) //前提:LNWService类存在
@ConditionalOnProperty(prefix = "lnw", value = "enabled", matchIfMissing = true)
public class LNWAutoConfiguration {
@Autowired
private LNWProperties lnwProperties;
@Bean
@ConditionalOnMissingBean(LNWService.class) //前提:LNWService Bean不存在
public LNWService getLNWService(){
LNWService service = new LNWService();
// 设置message属性,这个Service里面的message就会被改变
service.setMessage(lnwProperties.getMessage());
return service;
}
}
1 | package com.lnw.properties; |
1 | package com.lnw.service; |
最后是spring.factories,我们要把LNWAutoConfiguration这个配置文件交给springboot1
2org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lnw.config.LNWAutoConfiguration
接下来写一个SpringBoot项目(先添加SpringBoot依赖)
在pom文件中,添加我们上面写的那个项目
然后在项目的application.properties中,添加
我们开始测试,SpringBoot默认是有添加test依赖的,我们直接使用SpringBoot的test来测试1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27package com.lnw;
import com.lnw.controller.HelloController;
import com.lnw.service.LNWService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootSwaggerApplicationTests {
@Autowired
private LNWService lnwService;
@Test
public void testAutoConfigure(){
System.out.println(lnwService.sayHello());
}
}
最后输出
再改变一下值
再运行
这样就OK了
SpringBoot扫描spring.factories的原理
关于SpringFactoriesLoader,它在SpringBoot程序启动的时候,在刷新容器中会被执行。
他可以从classpath下的每个jar包中搜寻所有META-INF/spring.factories配置文件,然后解析spring.factories的内容得到Configuration类。
在上的SpringBoot的项目中,扫描出来的@Configuration有100duoge
其实底层是用了ClassLoader类加载器的 getResources(name) 方法,该方法会遍历其负责加载的所有jar包,找到jar包中名称为name的资源文件,这里的资源文件可以是任何文件
所以SpringBoot的SpringFactoriesLoader就是利用ClassLoader这个方法去查找所有jar包下的META-INF/spring.factories文件。
模拟SpringFactoriesLoader
我们也可以自己写个测试来寻找当前所有jar包中的spring.factories文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.lnw;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
public class FindResourcesTest {
public static void main(String[] args) {
String name = "META-INF/spring.factories";
try {
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader()
.getResources(name);
while (urls.hasMoreElements()){
URL url = urls.nextElement();
System.out.println(url.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
最后输出1
2
3
4jar:file:/C:/Users/%e6%9e%97%e6%a5%a0%e7%82%9c/.m2/repository/org/springframework/boot/spring-boot/2.1.0.RELEASE/spring-boot-2.1.0.RELEASE.jar!/META-INF/spring.factories
jar:file:/C:/Users/%e6%9e%97%e6%a5%a0%e7%82%9c/.m2/repository/org/springframework/spring-beans/5.1.2.RELEASE/spring-beans-5.1.2.RELEASE.jar!/META-INF/spring.factories
file:/E:/IDEA/simulation-integration/lnw-spring-boot-auto-config/target/classes/META-INF/spring.factories
jar:file:/C:/Users/%e6%9e%97%e6%a5%a0%e7%82%9c/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.1.0.RELEASE/spring-boot-autoconfigure-2.1.0.RELEASE.jar!/META-INF/spring.factories
总结
最后我来总结一下为什么SpringBoot不用配置文件就能实现配置:
1、每个SpringBoot的主程序类都有一个@SpringBootApplication注解。这个注解里面有一个十分重要的注解@EnableAutoConfiguration
2、@EnableAutoConfiguration注解里面引用了一个类AutoConfigurationImportSelector,这个类非常重要
3、AutoConfigurationImportSelector里面调用了一个方法->SpringFactoriesLoader.loadFactoryNames
4、SpringFactoriesLoader.loadFactoryNames方法,会去扫描当前所有jar包下的METS-INF/spring.factories文件,并把文件的内容扫描放到一个List中
5、生成的List会交给BeanFactory解析