springboot自动配置的原理
SpringBoot的核心就是自动配置,自动配置又是基于条件判断来配置Bean。关于自动配置的源码在spring-boot-autoconfigure-2.0.0.RELEASE.jar
在讲springBoot自动配置之前我们应该思考,为什么要有自动配置,有什么好处呢?
为什么要有springboot自动配置?
直接文字说明可能不直观,理解起来也吃力,我们用例子来证明体现。
Spring搭建一个项目
如果我们用传统的spring来搭建一个SSM框架:
核心配置类:
spring-web.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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<!-- 配置SpringMVC -->
<!-- 1.开启SpringMVC注解模式 -->
<!-- 简化配置: (1)自动注册DefaultAnootationHandlerMapping,AnotationMethodHandlerAdapter
(2)提供一些列:数据绑定,数字和日期的format @NumberFormat, @DateTimeFormat, xml,json默认读写支持 -->
<mvc:annotation-driven />
<!-- 2.静态资源默认servlet配置 (1)加入对静态资源的处理:js,gif,png (2)允许使用"/"做整体映射 -->
<mvc:resources mapping="/resources/**" location="/resources/" />
<mvc:default-servlet-handler />
<!-- 3.定义视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/html/"></property>
<property name="suffix" value=".html"></property>
</bean>
<!-- 在spring-mvc.xml文件中加入这段配置后,spring返回给页面的都是utf-8编码了 -->
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<!-- 4.扫描web相关的bean -->
<context:component-scan base-package="com.xiateng.web" />
</beans>
web.xml
<servlet>
<servlet-name>spring-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>spring-dispatcher</servlet-name>
<!-- 默认匹配所有请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
可以看到,这里用到两个配置文件,如果要集成更多第三方技术配置会更复杂、繁重。
SpringBoot搭建一个项目pring
相对于Spring,新建一个spingBoot项目无需任何配置就可启动一个项目。
具体新建流程就不说了,有兴趣可以看看这篇:https://blog.csdn.net/qq_43037478/article/details/90031380
我们会看到,创建好的项目的pom文件里自动引入了几个jar包,我们先看下面这个父依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
点进去会发现里面会有个dependencies,里面其实就是很多jar包的版本号(由于文件比较长,这里只截图部分。)
这里我们可以得到一个结论,就是我们引入依赖的时候不需要知道版本,因为SpringBoot的父依赖里已经帮我们维护了一套版本
父依赖里继续往下翻,会看到里面帮我们写好了资源库,不用我们自己去配置
<resources>
<resource>
<filtering>true</filtering>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<excludes>
<exclude>**/application*.yml</exclude>
<exclude>**/application*.yaml</exclude>
<exclude>**/application*.properties</exclude>
</excludes>
</resource>
</resources>
启动器,我这里用的是下面这个跟mybatis整合的
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
引入这个依赖的同时会自动引入jdbc,autoconfigure,starter等相关依赖,这里就不细说了
不难看出SpringBoot相比于Spring创建项目的优势:
- 遵循”约定优于配置”的原则,使用springboot的时候只需要少量配置即可,大部分时候我们可以使用默认配置。
- 项目搭建速度非常快,没有繁重的配置,而且不容易出错。
- 无需配置就可整合第三方框架
- 内嵌Servlet如:Tomcat日期,应用可以直接jar包运行
下面我们着重讲下SpringBoot的自动配置
首先我们找到入口:启动类(该启动类的核心就是@SpringBootApplication注解)
// exclude = {DataSourceAutoConfiguration.class} 排除主动注入数据源
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application extends WebMvcConfigurerAdapter{
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
@SpringBootApplication其实是个组合注解,我们只需要关心其中的@SpringBootConfiguration注解和@EnableAutoConfiguration注解
@SpringBootConfiguration // 核心
@EnableAutoConfiguration // 核心
@ComponentScan(excludeFilters =
{@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)})
public @interface SpringBootApplication {
}
@SpringBootConfiguration注解内容
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 核心
public @interface SpringBootConfiguration {
}
可以看到,SpringBootConfiguration其实就携带了一个@Configuration注解,这个注解详细大家都很熟悉了,他代表自己是一个spring的配置类。
@EnableAutoConfiguration注解内容
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class}) // 核心
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以看到,在@EnableAutoConfiguration注解内使用到了@import注解来完成导入配置的功能,而EnableAutoConfigurationImportSelector内部则是使用了SpringFactoriesLoader.loadFactoryNames方法进行扫描具有META-INF/spring.factories文件的jar包。下面是2.0.0.RELEASE实现源码:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//扫描具有META-INF/spring.factories文件的jar包
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//去重
configurations = this.removeDuplicates(configurations);
// 排序
configurations = this.sort(configurations, autoConfigurationMetadata);
// 删除需要排除的类,如:启动类上的@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
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);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
加载spring.factories文件的类
所有的配置都存放在configurations中, 而这些配置都从getCandidateConfiguration中获取, 这个方法是用来获取候选的配置。
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;
}
实际上它返回了一个List,这个List是由loadFactoryNames()方法返回的,其中传入了一个getSpringFactoriesLoaderFactoryClass(),我们可以看看这个方法的内容。
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
发现没有?它实际上就是返回所有标注了这个类的所有包,而标注了这个类的包不就是@SpringBootApplication吗
从而我们可以得出结论:饶了一圈其实就是为了将启动类所需的所有资源导入。
spring.factories内容:一个key对应一个集合类(注:自动配置只会读取key为EnableAutoConfiguration下的集合类)
总结
SpringBoot所有自动配置类都是在启动的时候进行扫描并加载,通过spring.factories可以找到自动配置类的路径,但是不是所有存在于spring,factories中的配置都进行加载,而是通过@ConditionalOnClass注解进行判断条件是否成立(只要导入相应的stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。
还没有评论,来说两句吧...