Spring Boot自动配置原理与实践(一)
前言
Spring Boot众所周知是为了简化Spring的配置,省去XML的复杂化配置(虽然Spring官方推荐也使用Java配置)采用Java+Annotation方式配置。如下几个问题是我刚开始接触Spring Boot的时候经常遇到的一些疑问,现在总结出来希望能帮助到更多的人理解Spring Boot,当然这只是个人的理解,稍微显得肤浅但易懂!当时我明白了以下几个问题后,觉得Spring Boot也不过如此,没有啥花里胡哨的,希望能帮到大家!
本博文主要由两个部分组成:第一篇通过源码等形式介绍自动配置的原理与组成部分,第二篇通过实现自定义的starter实现自动配置加深对自动配置的理解。
注:创作不易啊,虽然写只写了5个小时左右,但是整个准备过程很早就开始了,当然这样的记录方式主要是督促自己对Spring Boot要深入理解,顺带希望能帮到更多人
问题一:那么Spring Boot是靠什么样的手段简化配置呢?
从广义上讲,从软件设计范式来说。Spring Boot提供一种“约定优于配置”来简化配置;减少开发人员去开发没必要的配置模块时间,采用约定,并且对不符合约定的部分采用灵活配置满足即可!
问题二:那么什么叫约定优于配置呢?
比如:我们知道Spring Boot都是内嵌Tomcat、Jetty等服务器的,其中默认的Tomcat服务器,相应的监听端口也默认绑定,这些默认即为约定。大部分情况下我们都会使用默认默认的Spring Boot自带的内嵌的默认自动配置约定,不会大量去自定义配置,即这就是我理解的约定优于配置。那么问题三来了。当我们觉得不符合我们实际生产环境的时候才会去改变默认配置,那么需要很麻烦的手段吗?
问题三:更改默认的端口或者服务器(即上述提到的约定)需要很麻烦的代码编写吗?甚至需要更改源码吗?
答案肯定是否定的,只需要更改application.yml/properties(application-*.yml/properties)配置文件即可。当然也可以从源头上开始重新创建自己需要的模块(准确来说可以是starter),需要的注册的配置类Bean。从而达到灵活的自动配置目的,这就是本篇要介绍的Spring Boot的自动配置。
明白以上三个问题后,其实就能理解为什么Spring Boot相对于Spring更加灵活,方便,简单的原因了========>无非就是大量使用默认自动配置+灵活的自动配置
一、Spring Boot自动配置的原理介绍
主要从spring boot是如何启动后自动检测自动配置与spring boot如何灵活处理自动配置两个方面讲解,前者主要分析源码,从简单的注解入手到最后的读取spring.facoties文件。后者主要举例RedisAutoConfiguration自动配置来说明spring boot对于相关的配置类bean的通过注解的灵活处理。
1、Spring Boot是如何发现自动配置的?
都知道Spring Boot程序入口处(xxxApplication.java)都会有 @SpringBootApplication 的注解。这个注解表明该程序是主程序入口,也是SpringBoot应用
@SpringBootApplication
public class DemoApplication { public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
} }
查看 @SpringBootApplication 注解,不难发现是一个组合注解,包括了 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 。换句话说这三个注解可以替代 @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}
)}
)
其中注解 @EnableAutoConfiguration 就是实现SpringBoot自动配置的关键所在,值得注意的是@EnableXXXX注解并不是SpringBoot特有,在Spring 3.x中就已经引出,比如:@EnableWebMvc用来启用Spring MVC而不需要XML配置,@EnableTransactionManagement注释用来声明事务管理而不需要XML配置。如此可见,@EnableXXXX是用来开启某一个特定的功能,从而使用Java来配置,省去XML配置。在SpringBoot中自动配置的开启就是依靠注解 @EnableAutoConfiguration ,继续查看注解接口 @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 {};
}
其中关键是 @Import({AutoConfigurationImportSelector.class}) ,表示选择所有SpringBoot的自动配置。废话不多说,还得继续看该选择器源码,其中最主要的方法是 getAutoConfiguationEntry 方法,该方法的主要功能是:
获得自动配置---->删除重复自动配置----->删除排除掉的自动配置---->删除过滤掉的自动配置------>最终的自动配置
这里我们主要关注如何获取自动配置,即图中红圈部分,即 getCandidateConfigurations 方法,该方法通过SpringFactoresLoader.loadFactoryNames方法读取某个classpath下的文件获取所有的configurations
注意到: No auto configuration classes found in META-INF/spring.factories..... ,它应该是从META-INF/spring.factories文件中下读取configurations,我们再往下查看方法 SpringFactoresLoader.loadFactoryNames ,该方法果然是读取META-INF/spring.factories文件的,从中获取自动配置键值对。
最后,我们找到META-INF/spring.factories文件(位于spring-boot-autoconfigure下)
查看其内容:
...
# 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,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
...
这时候才恍然大悟,原来如此。简单总结起来就是,当SpringBoot应用启动读取自动配置之后,从上到下的相关调用栈(文字简单描述)
- @EnableAutoConfiguration开启启动配置功能
- 通过@Import导入AutoConfigurationImportSelector选择器发现(注册)自动配置
- getAutoConfigurationEntry方法获取并处理所有的自动配置
- SpringFactoriesLoader.loadFactoryNames读取spring.factories文件并加载自动配置键值对。
2、Spring Boot是如何灵活处理自动配置的?
从spring.factories文件中我们选取RedisAutoConfiguration举例说明,至于为什么选这个呢?这是因为之前我有写过关于Spring Boot整合Redis的相关博文。所以好入手!
首先,还是一样进入RedisAutoConfiguration(Idea大法好,点击进入即可查看代码)
源码如下,主要关注以下几个注解:
1)@ConditionalOnClass:在当前classpath下是否存在指定类,若是则将当前的配置注册到spring容器。可见其灵活性。相关的@ConditionalXXX注解是spring 4.x推出的灵活注册bean的注解,称之为“条件注解”。有如下一系列条件注解:
- @ConditionalOnMissingBean: 当spring容器中不存在指定的bean时,才会注册该Bean。
- @ConditionalOnMissingClass:当不存在指定的类时,才会注册该bean。
- @ConditionalOnProperty:根据配置文件中某个属性来决定是否注册该Bean,相对于其它条件注解较为复杂。主要有以下两种情况应用:
一种是@ConditionalOnProperty(prefix = “mysql”, name = “enable”, havingValue = “true”)表示以为mysql为前缀的mysql.enable属性如果为空或者不是true时不会注册指定Bean,只有mysql.enable属性为true时候才会注册该指定Bean
一种是@ConditionalOnProperty(prefix = “mysql”, name = “enable”, havingValue = “true”, matchIfMissing = true)matchIfMissing表示如果没有以mysql为前缀的enable属性,则为true表示符合条件,可以注册该指定Bean,默认为false。
- @ConditionalOnJava:是否是Java应用
- @ConditionalOnWebApplication:是否是Web应用
- @ConditionalOnExpression:根据表达式判断是否注册Bean
...........剩下的还有很多,在autoconfigure.condition包下...........
2)@EnableConfigurationProperties:让 @ConfigurationProperties 注解的properties类生效并使用。如让RedisProperties生效并注册到Spring容器中,并且使用!
3)@ConditionalOnMissingBean:当前Spring容器中没有该bean(name或者class指定),则注册该bean到spring容器。
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
} @Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
} @Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
由此可见Spring Boot对于自动配置的灵活性,可以传递给我们一种信息,Spring Boot是如何灵活处理自动配置的?
往大了说,就是你可以选择约定使用大量默认配置简化开发过程,你也可以自定义开发过程中需要的自动配置。往细了说,就是通过各式各样的条件注解注册需要的Bean来实现灵活配置。
那么实现一个自动配置有哪些重要的环节呢?
继续往下看第二部分!
二、Spring Boot自动配置的重要组成部分
上面只是对自动配置中几个重要的注解做了简单介绍,但是并没有介绍要开发一个简单的自动配置需要哪些环节(部分),同样地我们还是以RedisAutoConfiguration与RabbitAutoConfiguration为例,主要查看注解头:
RedisAutoConfiguration:
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
RabbitAutoConfiguration:
@Configuration
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@EnableConfigurationProperties({RabbitProperties.class})
@Import({RabbitAnnotationDrivenConfiguration.class})
你会发现通常的一般的AutoConfiguration都有@Configuration,@ConditionalOnClass,@EnableConfigurationProperties,@Import 注解:
- @Configuration:自然不必多说,表示是配置类,是必须存在的!
- @ConditionalOnClass:表示该配置依赖于某些Class是否在ClassPath中,如果不存在那么这个配置类是毫无意义的。也可以说是该class在配置类中充当重要的角色,是必须存在的!。在后面的自定义spring boot starter中我们可以简单统称为服务类
- @EnableConfigurationProperties:读取配置文件,需要让相关的Properties生效,每个自动配置都离不开properties属性,是必须存在的!
- @Import:导入配置类中需要用到的bean,通常是一些配置类。表示此时的自动配置类需要基于一些配置类而实现自动配置功能。当然不是必须的,有些是没有这个注解的,是不需要基于某些配置类的!
那么,可以简单总结得到一个自动配置类主要有以下几部分组成(单纯是根据个人理解总结出来的,没有官方说明)
- properties bean配置属性:用来读取spring配置文件中的属性,@EnableConfigurationProperties与@ConfigurationProperties结合使用,具体请看下一篇实践stater例子。
- 该配置类依赖的一些Class,使用@ConditionalOnClass判断;
- 该配置类依赖的一些配置Bean,使用@Import导入。
- 可能还有加载顺序的控制,如@AutoConfigureAfter,@AutoConfigureOrder等
- 一些Bean的加载,往往通过方法返回Object,加@Bean以及一些条件注解来实现
========================================未完待续(下一篇补充自定义starter会涉及自动配置组成部分)==========================================
Spring Boot自动配置原理与实践(一)的更多相关文章
- Spring Boot自动配置原理与实践(二)
前言 在之前的博文(Spring Boot自动配置原理与实践(一))中,已经介绍了Spring boot的自动配置的相关原理与概念,本篇主要是对自动配置的实践,即自定义Starter,对原理与概念加深 ...
- Spring Boot自动配置原理(转)
第3章 Spring Boot自动配置原理 3.1 SpringBoot的核心组件模块 首先,我们来简单统计一下SpringBoot核心工程的源码java文件数量: 我们cd到spring-boot- ...
- Spring Boot自动配置原理、实战
Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...
- Springboot 系列(三)Spring Boot 自动配置原理
注意:本 Spring Boot 系列文章基于 Spring Boot 版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别. 前言 关于配置文件可以配置的内容,在 Spring ...
- spring boot 自动配置原理
1).spring boot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration,先看一下启动类的main方法 public ConfigurableApplic ...
- Spring Boot 自动配置原理(精髓)
一.自动配置原理(掌握) SpringBoot启动项目会加载主配置类@SpringBootApplication,开启@EnableAutoConfiguration自动配置功能 @EnableAut ...
- Spring Boot自动配置原理
使用Spring Boot之后,一个整合了SpringMVC的WEB工程开发,变的无比简单,那些繁杂的配置都消失不见了,这 是如何做到的? 一切魔力的开始,都是从我们的main函数来的,所以我们再次来 ...
- 【串线篇】spring boot自动配置原理
配置文件到底能写什么?怎么写?自动配置原理: 配置文件能配置的属性参照 一.自动配置原理: 1.1.SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfigur ...
- Spring Boot 自动配置原理是什么?
注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否有这个 ...
随机推荐
- poj3709 K-Anonymous Sequence[贪心+斜率优化dp]
地址 n个数,可进行把一个数减小的操作,代价为减小的值.现求使数列任意一个数都存在至少k-1个数和他相同,问操作的最小代价. 可以先考虑最小的数,由于只能减,所以必须得至少k-1个数减为最小数,贪心策 ...
- nginx 轮询模式 nginx_upstream_jvm_route 插件安装
使用nginx_upstream_jvm_route插件的目的是为了保证在轮询机制下的session的共享 前提:源码方式安装nginx.patch命令 1.下载nginx_upstream_jvm_ ...
- 使用unlist将日期型数据的列表转换为向量时,出现的异常
在使用unlist函数,将日期型的列表,转换为向量时,不会得到期望的结果,如下: > dateLst <- list(Sys.Date()) > dateLst [[1]] [1] ...
- nodejs的mysql模块学习(二)连接数据库
nodejs连接mysql的方式有两种 官方建议的第一种是 let mysql = require('mysql'); let connection = mysql.createConnection( ...
- java 发邮件 代码
/** * java发送带附件的邮件 * 周枫 * 2013.8.10 */ package com.dsideal.Util; import javax.mail.*; import javax.m ...
- hduoj题目分类
基础题:1000.1001.1004.1005.1008.1012.1013.1014.1017.1019.1021.1028.1029.1032.1037.1040.1048.1056.1058.1 ...
- 泛型(Generic)
当集合中存储的对象类型不同时,那么会导致程序在运行的时候的转型异常 import java.util.ArrayList; import java.util.Iterator; public clas ...
- Web 字体的选择和运用
首先来看一则设计师和前端小白的日常,这是使用 Mac OS 的设计师给的效果图 这是使用 Windows 开发的实际产品页面 我跟你们说,设定字体要按照基本法! (PS: 以上截图不代表网站真实面貌, ...
- Java线程安全与多线程开发
互联网上充斥着对Java多线程编程的介绍,每篇文章都从不同的角度介绍并总结了该领域的内容.但大部分文章都没有说明多线程的实现本质,没能让开发者真正“过瘾”. 从Java的线程安全鼻祖内置锁介绍开始,让 ...
- 3dmax导出缩放
参考 http://blog.dou.li/unity3d-import-3dmax-model-scale.html http://blog.csdn.net/pdw_jsp/article/det ...