SouthLight's Blog


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

为什么SpringBoot可以不用配置文件

发表于 2019-03-03 | 分类于 Spring

前言

我们使用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
13
package 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}

我们看到注解里面还有注解,分别是@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。其中@SpringBootCofuration这个注解是没什么作用的,@ComponentScan注解主要是做扫描当前包下的加了注解的类, @EnableAutoConfiguration 才是关键,也就是说只要加了这个@EnableAutoConfiguration注解就可以打开SpringBoot的自动配置的功能。我们看它的源码

@EnableAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.springframework.boot.autoconfigure;

@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 {};

}

这里引用了一个@Import注解,里面注入了一个AutoConfigurationImportSelector类。

AutoConfigurationImportSelector

SpringBoot运行后,就会创建Spring的容器,容器的创建需要一些配置类做初始化,比如@Configuration或者@Import注解的类,所以Spring容器创建之初会执行AutoConfigurationImportSelector里面的代码,最重要的方法就是org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations

1
2
3
4
5
6
7
8
9
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就是扫描当前项目以及项目依赖的所有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
18
package 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.lnw.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
// 读取前缀为lnw的属性
@ConfigurationProperties(prefix = "lnw")
public class LNWProperties {

private final static String MESSAGE = "LNW-SPRING-BOOT";

/**
* 默认值为MESSAGE
*/
private String message = MESSAGE;

public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.lnw.service;
public class LNWService {
private String message;

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public String sayHello(){
return "Hello " + message;
}
}

最后是spring.factories,我们要把LNWAutoConfiguration这个配置文件交给springboot

1
2
org.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
27
package 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
19
package 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
4
jar: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解析

1…192021…38

SouthLight Lin

38 日志
16 分类
19 标签
© 2020 SouthLight Lin
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4