Spring Boot的自动配置,到底是怎么做到?
作者:祖大帅
链接:juejin.im/post/5b679fbc5188251aad213110
来源:掘金
1. Spring Boot、Spring MVC 和 Spring 有什么区别?
分开描述各自的特征:
Spring 框架就像一个家族,有众多衍生产品例如 boot、security、jpa等等。但他们的基础都是Spring 的ioc和 aop,ioc 提供了依赖注入的容器, aop解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能。
Spring MVC提供了一种轻度耦合的方式来开发web应用。它是Spring的一个模块,是一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。
Spring Boot实现了自动配置,降低了项目搭建的复杂度。它主要是为了解决使用Spring框架需要进行大量的配置太麻烦的问题,所以它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box)。
所以,用最简练的语言概括就是:Spring 是一个“引擎”;Spring MVC 是基于Spring的一个 MVC 框架;Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。
2. SpringBoot到底是怎么做到自动配置的?
从代码里看项目SpringBoot的项目启动类只有一个注解@SpringBootApplication和一个run方法。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
直接看@SpringBootApplication的代码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor; @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 {};
}
@SpringBootApplication:包含了@SpringBootConfiguration(打开是@Configuration),@EnableAutoConfiguration,@ComponentScan注解。
2.1 @Configuration
JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。
对比一下传统XML方式和config配置方式的区别:
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" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
">
<bean id="app" class="com..." />
用一个过滤器举例,JavaConfig的配置方式是这样:
@Configuration
public class DruidConfiguration {
@Bean
public FilterRegistrationBean statFilter(){
//创建过滤器
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//设置过滤器过滤路径
filterRegistrationBean.addUrlPatterns("/*");
//忽略过滤的形式
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。
任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。
2.2 @ComponentScan
@ComponentScan对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。
注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
2.3 @EnableAutoConfiguration
(核心内容)看英文意思就是自动配置,概括一下就是,借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器。
@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(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。该配置模块的主要使用到了SpringFactoriesLoader。
2.4 SpringFactoriesLoader详解
SpringFactoriesLoader为Spring工厂加载器,该对象提供了loadFactoryNames方法,入参为factoryClass和classLoader即需要传入工厂类名称和对应的类加载器,方法会根据指定的classLoader,加载该类加器搜索路径下的指定文件,即spring.factories文件,传入的工厂类为接口,而文件中对应的类则是接口的实现类,或最终作为实现类。
public abstract class SpringFactoriesLoader {
//...
public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
...
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
....
}
}
所以文件中一般为如下图这种一对多的类名集合,获取到这些实现类的类名后,loadFactoryNames方法返回类名集合,方法调用方得到这些集合后,再通过反射获取这些类的类对象、构造方法,最终生成实例。
下图有助于我们形象理解自动配置流程(盗个图)
2.5 AutoConfigurationImportSelector
继续上面讲的AutoConfigurationImportSelector.class。该类主要关注selectImports方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}
该方法在springboot启动流程——bean实例化前被执行,返回要实例化的类信息列表。如果获取到类信息,spring可以通过类加载器将类加载到jvm中,现在我们已经通过spring-boot的starter依赖方式依赖了我们需要的组件,那么这些组件的类信息在select方法中就可以被获取到。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
方法中的getCandidateConfigurations方法,其返回一个自动配置类的类名列表,方法调用了loadFactoryNames方法,查看该方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
自动配置器会跟根据传入的factoryClass.getName()到项目系统路径下所有的spring.factories文件中找到相应的key,从而加载里面的类。我们就选取这个mybatis-spring-boot-autoconfigure下的spring.factories文件
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
进入org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration中,又是一堆注解
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration{
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
}
@Spring的Configuration是一个通过注解标注的springBean,
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class})这个注解的意思是:当存在SqlSessionFactory.class, SqlSessionFactoryBean.class这两个类时才解析MybatisAutoConfiguration配置类,否则不解析这一个配置类。我们需要mybatis为我们返回会话对象,就必须有会话工厂相关类。
@CondtionalOnBean(DataSource.class):只有处理已经被声明为bean的dataSource
@ConditionalOnMissingBean(MapperFactoryBean.class)这个注解的意思是如果容器中不存在name指定的bean则创建bean注入,否则不执行
只要一个基于SpringBoot项目的类路径下存在SqlSessionFactory.class, SqlSessionFactoryBean.class,并且容器中已经注册了dataSourceBean,就可以触发自动化配置,意思说我们只要在maven的项目中加入了mybatis所需要的若干依赖,就可以触发自动配置,但引入mybatis原生依赖的话,每集成一个功能都要去修改其自动化配置类,那就得不到开箱即用的效果了。所以Spring-boot为我们提供了统一的starter可以直接配置好相关的类,触发自动配置所需的依赖(mybatis)如下:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
因为maven依赖的传递性,我们只要依赖starter就可以依赖到所有需要自动配置的类,实现开箱即用的功能。也体现出Springboot简化了Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发。
Spring Boot的自动配置,到底是怎么做到?的更多相关文章
- 初识Spring Boot框架(二)之DIY一个Spring Boot的自动配置
在上篇博客初识Spring Boot框架中我们初步见识了SpringBoot的方便之处,很多小伙伴可能也会好奇这个Spring Boot是怎么实现自动配置的,那么今天我就带小伙伴我们自己来实现一个简单 ...
- Spring boot 的自动配置
Xml 配置文件 日志 Spring Boot对各种日志框架都做了支持,我们可以通过配置来修改默认的日志的配置: #设置日志级别 logging.level.org.springframework=D ...
- spring boot 系列之六:深入理解spring boot的自动配置
我们知道,spring boot自动配置功能可以根据不同情况来决定spring配置应该用哪个,不应该用哪个,举个例子: Spring的JdbcTemplate是不是在Classpath里面?如果是,并 ...
- Spring Boot 排除自动配置的 4 种方法,关键时刻很有用!
Spring Boot 提供的自动配置非常强大,某些情况下,自动配置的功能可能不符合我们的需求,需要我们自定义配置,这个时候就需要排除/禁用 Spring Boot 某些类的自动化配置了. 比如:数据 ...
- 了解Spring Boot的自动配置
摘自:https://www.jianshu.com/p/ddb6e32e3faf Spring Boot的自动配置给开发者带来了很大的便利,当开发人员在pom文件中添加starter依赖后,mave ...
- 自定义spring boot的自动配置
文章目录 添加Maven依赖 创建自定义 Auto-Configuration 添加Class Conditions 添加 bean Conditions Property Conditions Re ...
- Spring Boot的自动配置
Spring Boot的自动配置 --摘自https://www.hollischuang.com/archives/1791 随着Ruby.Groovy等动态语言的流行,相比较之下Java的开发显得 ...
- Spring Boot的自动配置原理及启动流程源码分析
概述 Spring Boot 应用目前应该是 Java 中用得最多的框架了吧.其中 Spring Boot 最具特点之一就是自动配置,基于Spring Boot 的自动配置,我们可以很快集成某个模块, ...
- Spring Boot的自动配置的原理
Spring Boot在进行SpringApplication对象实例化时会加载META-INF/spring.factories文件,将该配置文件中的配置载入到Spring容器. 1.1.1. ...
随机推荐
- django模板中变更数据库信息后,如何把变更后的信息同步更新到数据库
我们在基于django开发项目的过程中,经常会遇到数据库表字段增加,删除,或者修改的情况,以及字段属性更改的情况,因为django基于ORM模式来操作数据库的, 传统上如果django项目中的数据库m ...
- POJO类中布尔类型为啥不让用isXxx命名
源码面前,了无秘密 <阿里开发规范泰山版>(2020.04.22)-->编程规约-->(一) 命名风格-->第8条规定: [强制]POJO 类中的任何布尔类型的变量,都不 ...
- 双向绑定和 vuex 是否冲突
在严格模式下确实有问题,解决方案: https://vuex.vuejs.org/zh/guide/forms.html
- AutoJS 实现QQ小游戏胡莱三国爬塔
AutoJS 开发文档参考 环境 安卓QQ 胡莱三国小游戏 AutoJS APP 使用方法 安装AutoJs,打开无障碍模式,进入到胡莱三国小游戏,在Autojs中执行脚本 代码 "auto ...
- celery 基础教程(一):工作流程,架构以及概念
1.工作流程 celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者)和worker(任务的处理者). clients发出消息到队列中,broker将队列中的 ...
- 数据可视化之DAX篇(十六)如何快速理解一个复杂的DAX?这个方法告诉你
https://zhuanlan.zhihu.com/p/64422393 经常有朋友提出一个问题,然后我给出一个DAX之后,TA又不是很理解,反复多次沟通才能把一个表达式讲清楚.或者TA自己写了一个 ...
- cnn卷积理解
首先输入图像是28*28处理好的图. 第一层卷积:用5*5的卷积核进行卷积,输入为1通道,输出为32通道.即第一层的输入为:28*28图,第一层有32个不同的滤波器,对同一张图进行卷积,然后输出为32 ...
- 对掌机游戏Pokemon的一部分系统的拆解流程图
整体系统拆解 POKEMON系统拆解 属性.技能.进化形态 属性提升系统 种族值说明: 所有Pokemon都拥有自己的种族的种族值,且固定(例如:小火龙:309, 皮卡丘: 320) 种族值是各项属性 ...
- 在运行vue项目时,执行npm install报错小记
在运行vue项目时,执行npm install 报错,导致后续的执行报各种错误,根据报错,尝试了网上的各种办法,最后发现时网络问题下载失败导致,解决办法: 安装cnpm==>npm instal ...
- Python 简明教程 --- 23,Python 异常处理
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 要么做第一个,要么做最好的一个. 目录 我们在编写程序时,总会不自觉的出现一些错误,比如逻辑错误,语 ...