Spring Boot的自动配置原理解析
一、Spring Boot概述
SpringBoot是整合Spring技术栈的一站式框架,是简化Spring技术栈的快速开发脚手架。
Spring Boot官网:https://spring.io/
Spring的生态:https://spring.io/projects/spring-boot
查看版本新特性:https://github.com/spring-projects/spring-boot/wiki#release-notes
Spring Boot文档:https://docs.spring.io/spring-boot/docs/current/reference/html/
二、注解说明
介绍Spring Boot自动配置过程中可能使用到的注解,对其有个大概的了解。
@SpringBootApplication(核心启动)
@SpringBootApplication注解是Spring Boot的启动注解,是三个注解的总和
@Configuration: 用于定义一个配置类
@EnableAutoConfiguration :Spring Boot会自动根据jar包的依赖来自动配置项目。
@ComponentScan: 告诉Spring哪个packages用注解标识的类会被spring自动扫描并且装入bean容器。
@ComponentScan(包扫描)
扫描被@Component 、@Controller、@Service、@Repository等注解的类,注解默认会扫描该@ComponentScan注解类所在的包下所有的类
@Import(导入组件)
给容器中自动创建出导入类型的组件、默认组件的名字就是全类名(com.example.demo.model.User)
@Import({User.class, XXX.class})
@Configuration
public class MyConfig {
}
@Conditional(条件装配)
条件装配注解:满足Conditional指定的条件,则进行组件注入。
条件装配注解既可以使用在类上也可以使用在方法上。
有如下注解:
当@ConditionalOnBean(name = "myUser")
时,myCar方法执行,向容器添加car组件
当@ConditionalOnBean(name = "myUser22")
时,myCar方法不执行
@Configuration
public class MyConfig {
/**
* 向容器添组件,方法名作为组件id
* 返回类型就是组件类型
* 返回值:组件在容器中的实例
*/
@Bean
public User myUser() {
User user = new User();
return user;
}
/**
* 在@Bean注解指定值:向容器添组件, id为`car`
*/
@Bean("car")
@ConditionalOnBean(name = "myUser")
public Car myCar() {
return new Car();
}
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
System.out.println("容器中是否存在 myUser: " + run.containsBean("myUser"));
System.out.println("容器中是否存在 car: " + run.containsBean("car"));
}
}
@Configuration(申明配置文件)
@Configuration申明一个类是配置类,代替传统的配置文件
@Configuration具体两种模式:Full模式(proxyBeanMethods = true)与Lite模式(proxyBeanMethods = false)
Full模式:
保证每个@Bean方法被调用多少次返回的组件都是单实例的
配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
Lite模式:
每个@Bean方法被调用多少次返回的组件都是新创建的
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
/**
* 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 配置类本身也是组件
* proxyBeanMethods:代理bean的方法
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
*/
@Configuration(proxyBeanMethods = true)
public class MyConfig {
/**
* 向容器添组件,方法名作为组件id
* 返回类型就是组件类型
* 返回值:组件在容器中的实例
*/
@Bean
public User myUser() {
User user= new User("zhangsan", 18);
//user组件依赖了Car组件,当proxyBeanMethods = false时,多次调用myUser方法产生的依赖组件Car与调用myCar方法产生的Car不是同一个,反之是同一个。
user.setCar(myCar());
return user;
}
/**
* 在@Bean注解指定值:向容器添组件, id为`car`
*/
@Bean("car")
public Car myCar() {
return new Car("car");
}
}
测试
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
// 得到IOC容器
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
// 所有容器里的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
// 从容器中获取组件
User user1 = run.getBean("myUser", User.class);
User user2 = run.getBean("myUser", User.class);
System.out.println("user1 == user2 :" + (user1 == user2));
MyConfig bean = run.getBean(MyConfig.class);
// com.example.demo.MyConfig$$EnhancerBySpringCGLIB$$c0fab96c@62102051
System.out.println(bean);
//如果@Configuration(proxyBeanMethods = true)代理对象调用方法:SpringBoot总会检查这个组件是否在容器中存在
User user3 = bean.myUser();
User user4 = bean.myUser();
System.out.println("user3 == user4 :" + (user3 == user4));
// proxyBeanMethods = true :保证方法被调用返回的组件都是单实例,包括依赖组件。
// proxyBeanMethods = false: 保证方法被调用返回的组件都是新创建实例
User user5 = run.getBean("myUser", User.class);
Car car = run.getBean("car", Car.class);
System.out.println("user5 == car :" + (user5.getCar() == car));
}
}
@ImportResource(原生配置文件引入)
@ImportResource支持将原生配置文件引入
创建spring原生配置文件beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="xml_user" class="com.example.demo.model.User"/>
<bean id="xml_car" class="com.example.demo.model.Car"/>
</beans>
使用@ImportResource注解引入
@Configuration
@ImportResource("classpath:/beans.xml")
public class MyConfig {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
System.out.println("容器中是否存在 xml_user: " + run.containsBean("xml_user"));
System.out.println("容器中是否存在 xml_car: " + run.containsBean("xml_car"));
}
}
启动main方法
容器中是否存在 xml_user: true
容器中是否存在 xml_car: true
@ConfigurationProperties(配置绑定)
1.使用Java读取到properties文件中的内容,并且把它封装到JavaBean中
test.properties
name:"userName"
age:20
public class getProperties {
public static void main(String[] args) throws IOException {
Properties pps = new Properties();
Class<getProperties> getPropertiesClass = getProperties.class;
InputStream inputStream = getPropertiesClass.getClassLoader().getResource("test.properties").openStream();
pps.load(inputStream);
//得到配置文件的名字
Enumeration<?> enums = pps.propertyNames();
while (enums.hasMoreElements()) {
String name = pps.getProperty("name");
String age = pps.getProperty("age");
//封装到JavaBean
User user = new User();
user.setName(name);
user.setAge(Integer.valueOf(age));
}
}
}
2.使用@Component+@ConfigurationProperties
application.properties
user.name="userName"
user.age=20
@Data
@Component
@ConfigurationProperties(prefix = "user")
public class User {
private String name;
private Integer age;
}
3.使用@EnableConfigurationProperties+@ConfigurationProperties
@Data
@ConfigurationProperties(prefix = "user")
public class User {
private String name;
private Integer age;
}
// 开启User配置绑定功能,把这个User组件自动注册到容器中
@EnableConfigurationProperties(User.class)
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
执行测试
@RestController
public class TestController {
@Qualifier("user")
@Autowired
User user;
@RequestMapping("/test")
public User test() {
return user;
}
}
三、自动配置过程解析
@SpringBootApplication
注解包含如下3个注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}
1.@SpringBootConfiguration
@SpringBootConfiguration
注解包含@Configuration
注解(代表当前是一个配置类
)
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
2.@EnableAutoConfiguration
@EnableAutoConfiguration
注解包含@AutoConfigurationPackage
、@Import
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
@AutoConfigurationPackage
@AutoConfigurationPackage
注解直译:自动导入包,其指定了默认的包规则
// 向容器添加Registrar组件
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
使用Registrar类向容器添加一系列组件
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 将指定的一个包(DemoApplication所在包)下的所有组件导入容器中
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
@Import(AutoConfigurationImportSelector.class)
@Import注解向容器添加AutoConfigurationImportSelector组件,同时导入相关依赖组件。
1.使用getAutoConfigurationEntry(annotationMetadata)
向容器批量导入组件
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
2.调用getCandidateConfigurations(annotationMetadata, attributes)
获取到所有需要导入到容器中的配置类
protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
3.使用工厂最终加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)
得到的所有组件
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;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
4.从META-INF/spring.factories
位置来加载一个文件
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 {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
MultiValueMap<String, String> result = new LinkedMultiValueMap();
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()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
默认扫描当前系统里面所有META-INF/spring.factories
位置的文件
这些文件里写死了spring-boot启动就要向容器中加载的所有配置类
3.@ComponentScan
@ComponentScan
注解指定扫描哪些Spring注解
四、按需开启自动配置
自动配置类
各种配置都拥有默认值,默认配置最终都是映射到某个类上,即:配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
自动配置项是按需加载:当引入了某个starter启动器后,自动配置才会开启
虽然文件中写死了所有自动配置启动的时候默认全部加载,但是Spring Boot会按照条件装配(@Conditional)规则,最终实现按需配置。
org.springframework.boot.autoconfigure
包下面全是自动配置类
additional-spring-configuration-metadata.json
配置文件存在大量的默认配置,如项目端口默认8080。
spring.factories
文件存在大量以Configuration为结尾的类名称,这些类就是存有自动配置信息的类,而SpringApplication在获取这些类名后再加载。
自动配置类解析
例如AOP自动配置类:如果不引入Aop相关依赖,则AOP自动配置并不会生效。
条件装配满足,执行类
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
由于Aop存在默认配置,即使不在application.properties
中配置,只要引入Aop相关依赖,AOP功能默认开启。
五、修改默认配置
修改配置文件默认值
参考官网:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties
在src/main/resources下创建application.properties,添加配置以此覆盖默认配置
查看组件获取的配置文件是什么值,然后修改即可
过程:*AutoConfiguration ---> 组件 ---> *Properties里面拿值 ----> application.properties
SpringBoot加载所有的自动配置类 ,每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。当配置类生效,就会向容器中装配很多组件,容器中有这些组件,就相当于有了这些功能
如RabbitAutoConfiguration
自动配置类,从RabbitProperties
里面获取相应配置,RabbitProperties又和application.properties
配置文件进行了绑定
@Bean替换底层组件
直接自己@Bean
替换底层的组件
SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先。
如给容器中加入自定义文件上传解析器
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
/**
* 显示声明自定义文件解析器为mutipartResolver
*/
@Bean(name = "multipartResolver")
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("UTF-8");
resolver.setResolveLazily(true);
resolver.setMaxInMemorySize(40960);
resolver.setMaxUploadSize(10 * 1024 * 1024);
return resolver;
}
}
再看DispatcherServletAutoConfiguration
类
@Bean
// 容器中有这个类型组件
@ConditionalOnBean(MultipartResolver.class)
// 容器中没有名叫 multipartResolver 的组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
// 给该方法传入对象参数,参数的值就从容器中找,防止用户配置的文件上传解析器不符合规范
return resolver;
}
六、开启自动配置报告
在配置文件中开启自动配置报告
debug=true
控制台打印如下信息:
Positive matches: 配置生效
Negative matches: 配置未生效
七、Spring Boot应用启动器
查询Spring Boot官方提供的Starter:https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
开发使用启动器
1)spring-boot-starter :这是Spring Boot的核心启动器,包含了自动配置、日志和YAML
2)spring-boot-starter-actuator :帮助监控和管理应用
3)spring-boot-starter-amqp :通过spring-rabbit来支持AMQP协议(Advanced Message Queuing Protocol)
4)spring-boot-starter-aop :支持面向方面的编程即AOP,包括spring-aop和AspectJ
5)spring-boot-starter-artemis :通过Apache Artemis支持JMS的API(Java Message Service API)
6)spring-boot-starter-batch :支持Spring Batch,包括HSQLDB数据库
7)spring-boot-starter-cache :支持Spring的Cache抽象
8)spring-boot-starter-cloud-connectors :支持Spring Cloud Connectors,简化了在像Cloud Foundry或Heroku这样的云平台上连接服务
9)spring-boot-starter-data-elasticsearch :支持ElasticSearch搜索和分析引擎,包括spring-data-elasticsearch
10)spring-boot-starter-data-gemfire :支持GemFire分布式数据存储,包括spring-data-gemfire
11)spring-boot-starter-data-jpa :支持JPA(Java Persistence API),包括spring-data-jpa、spring-orm、hibernate
12)spring-boot-starter-data-MongoDB :支持MongoDB数据,包括spring-data-mongodb
13)spring-boot-starter-data-rest :通过spring-data-rest-webmvc,支持通过REST暴露Spring Data数据仓库
14)spring-boot-starter-data-solr :支持Apache Solr搜索平台,包括spring-data-solr
15)spring-boot-starter-freemarker :支持FreeMarker模板引擎
16)spring-boot-starter-groovy-templates :支持Groovy模板引擎
17)spring-boot-starter-hateoas :通过spring-hateoas支持基于HATEOAS的RESTful Web服务
18)spring-boot-starter-hornetq :通过HornetQ支持JMS
19)spring-boot-starter-integration :支持通用的spring-integration模块
20)spring-boot-starter-jdbc :支持JDBC数据库
21)spring-boot-starter-jersey :支持Jersey RESTful Web服务框架
22)spring-boot-starter-jta-atomikos :通过Atomikos支持JTA分布式事务处理
23)spring-boot-starter-jta-bitronix :通过Bitronix支持JTA分布式事务处理
24)spring-boot-starter-mail :支持javax.mail模块
25)spring-boot-starter-mobile :支持spring-mobile
26)spring-boot-starter-mustache :支持Mustache模板引擎
27)spring-boot-starter-Redis :支持Redis键值存储数据库,包括spring-redis
28)spring-boot-starter-security :支持spring-security
29)spring-boot-starter-social-facebook :支持spring-social-facebook
30)spring-boot-starter-social-linkedin :支持pring-social-linkedin
31)spring-boot-starter-social-twitter :支持pring-social-twitter
32)spring-boot-starter-test :支持常规的测试依赖,包括JUnit、Hamcrest、Mockito以及spring-test模块
33)spring-boot-starter-thymeleaf :支持Thymeleaf模板引擎,包括与Spring的集成
34)spring-boot-starter-velocity :支持Velocity模板引擎
35)spring-boot-starter-web :支持全栈式Web开发,包括Tomcat和spring-webmvc
36)spring-boot-starter-websocket :支持WebSocket开发
37)spring-boot-starter-ws :支持Spring Web Services
面向生产环境启动器
1)spring-boot-starter-actuator :增加了面向产品上线相关的功能,比如测量和监控
2)spring-boot-starter-remote-shell :增加了远程ssh shell的支持
替换技术启动器
1)spring-boot-starter-jetty :引入了Jetty HTTP引擎(用于替换Tomcat)
2)spring-boot-starter-log4j :支持Log4J日志框架
3)spring-boot-starter-logging :引入了Spring Boot默认的日志框架Logback
4)spring-boot-starter-tomcat :引入了Spring Boot默认的HTTP引擎Tomcat
5)spring-boot-starter-undertow :引入了Undertow HTTP引擎(用于替换Tomcat)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136941.html