SpringBoot自动配置和注入
- SpringBoot自动配置和注入
- 1. 自动配置原理
- 1.1 依赖管理
- 1.1.1 版本仲裁
- 1.1.2 starter场景启动器
- 1.1.3 变更版本
- 1.2 自动配置
- 2. Bean组件装配
- 2.1 @Configuration配置类
- 2.2 @Bean注解
- 2.3 单例、组建依赖
- 2.4 @Import导入组件
- 2.5 @Conditional条件注入组件
- 2.6 Spring原生配置文件导入
- 3. 自动配置绑定
- 3.1 配置绑定方式
- 3.1.1 原始方式:
- 3.1.2 @ConfigurationProperties导入配置
- 3.1.3 @EnableConfigurationProperties导入配置
- 3.2 自动注入原理
- 3.2.1 @EnableAutoConfiguration自动注入原理
- 3.3 按需配置和注入原理
- 3.4 应用
SpringBoot自动配置和注入
1. 自动配置原理
1.1 依赖管理
1.1.1 版本仲裁
依赖管理通过父项目实现
项目依赖了spring-boot-starter-parent这个父项目,而它还有一个更高级的父项目spring-boot-dependencies,在这个项目的pom文件里,声明了很多依赖的版本号。几乎声明了所有开发中常用的jar包,无需关注版本
1.1.2 starter场景启动器
Starters are a set of convenient dependency descriptors that you can include.
starter是SpringBoot中一种开发环境依赖的集合,导入starter后所有的依赖都自动导入
Spring官方的starter:spring-boot-starter-*(*将用于描述某种开发场景)
第三方starter:*-spring-boot-starter-*
所有场景启动器,最底层的依赖就是spring-boot-starter,这是SpringBoot自动配置的核心依赖
1.1.3 变更版本
如果需要变更自动导入的依赖版本,可以指定同名的properties配置
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
这是基于maven的就近优先原则
1.2 自动配置
会自动配置:
- tomcat
- SpringMVC组件以及Web常见功能
- DispatcherServlet
- characterEncodingFilter
- InternalResourceViewResolver文件上传解析器
- 默认包结构
- 提供了默认的包扫描规则,主程序
MainApplication
所在的包及其下面的所有子包里面的组件都会被扫描进来 - MainApplication不能直接放在java包下面,因为写在java包下的Application类,是不从属于任何一个包的,因而启动类没有包
- 如果你想要修改默认包的扫描位置:
- 可以给MainApplication中的@SpringBootApllication添加scanBasePackages属性
package com.atguigu.boot;
// 扫描com包及其下的所有包,和MainApplication所在的包不同
// 默认是scanBasePackages = "com"
@SpringBootApllication(scanBasePackages = "com.atguigu.boot")
public class MainApplication { ... }
- 或者通过@CompoonentScan指定扫描路径(和SpringMVC差不多)
@SpringBootApplication
// 等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
- 各种配置拥有默认值
- 默认配置最终都是映射到某个类上,如:MultipartProperties
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
- 需要修改默认值:通过application.properties配置文件
- 按需加载所有自动配置项
- 非常多的starter
- 需要先引入了指定场景,这个场景的自动配置才会开启
- SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
2. Bean组件装配
2.1 @Configuration配置类
可以将一个类声明为配置文件类,相当于Spring的配置文件
@Configuration() // 诉SpringBoot这是一个配置类 ==> 配置文件
public class MyConfig {
// 容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。
// 返回的值,就是组件在容器中的实例
@Bean
public User user01(){
User zhangsan = new User("zhangsan", 18);
return zhangsan;
}
@Bean("tom22")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
可以在MainApplication中使用:
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run =
SpringApplication.run(MainApplication.class, args);
//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
// 获取的是单例对象
Pet tom1 = run.getBean("tom22", Pet.class);
}
}
2.2 @Bean注解
添加了@Bean注解的方法,返回值的类型会被视为组件,返回的值就是组件在容器中的实例,这等价于在xml中配置
可以给@Bean添加value属性值,会以这个value作为组件id,如上方的@Bean("tom22")
对类使用@Component、@Controller、@Service、@Repository注解也是可以的配置组件的
被@Bean修饰的方法,即使有方法参数,Spring也会优先从容器中找对应类型的组件并自动传入方法,不一定需要传参
源码示例:SpringBoot默认配置的文件上传解析器MultipartResolver组件的名字就是multipartResolver,但如果一些已有项目的命名不是这个multipartResolver,那么当调用multipartResolver获取组件时,它会执行下方的方法获取组件实例。这个方法需要传入一个对象,但实际上你调用时不用传参,Spring会自动地从容器中寻找对应类型的组件传入
@Bean
// 容器中有这个类型组件
@ConditionalOnBean(MultipartResolver.class)
// 容器中没有这个名字 multipartResolver 的组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// 给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
// SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
2.3 单例、组建依赖
直接是用@Bean
配置的对象默认是单例的
当你是用ConfigurableApplicationContext的getBean()方法获取Bean组件时,获取的对象都是单例的
但是,配置类本身也是一个Bean组件,也就是说你可以获得MyConfig的实例,并调用其创建Bean的方法:
MyConfig bean = run.getBean(MyConfig.class);
// 通过MyConfig获取Bean
User user = bean.user01();
这样子获取到的对象就不是单例的了!因为你没有通过ConfigurableApplicationContext获取
不过,你可以使用修改proxyBeanMethods属性,使用@Configuration(proxyBeanMethods = true)
,使用代理对象调用方法。由于配置类本身也是一个Bean组件,所以你在获取这个配置类时,返回的其实是一个被代理过的对象。而这个代理对象增强的功能其实就是单例。所以,使用这个注解后,即使你使用MyConfig的方法获取Bean,也是单例的对象。
- Full模式(proxyBeanMethods = true)
- 保证每个@Bean方法被调用多少次返回的组件都是单例的
- 可以处理组件依赖关系
- 每一次获取Bean,都需要检查是否已有单例对象,启动加载效率会很低。
- Lite模式(proxyBeanMethods = false)
每个@Bean方法被调用多少次返回的组件都是新创建的
启动加载速度更快
应用场景:直接在MyConfig中调用方法(组件依赖):
public class MyConfig {
@Bean
public User user01(){
User zhangsan = new User("zhangsan", 18);
// user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
2.4 @Import导入组件
使用**@Import**导入组件:
@Import({User.class, DBHelper.class})
@Configuraton(proxyBeanMethods = false)
public class MyConfig {}
Spring会自动根据传入的class参数,通过无参构造创建组件
2.5 @Conditional条件注入组件
org.springframework.boot.autoconfigure.condition
注解 | 创建bean的时机 |
@ConditionalOnBean | 存在某个bean时 |
@ConditionalOnMissingBean | 不存在某个bean时,常见于源码。 当用于配置了某个Bean后,由于这个注解,Spring源码中的某些配置就失效了 |
@ConditionalOnClass | 存在某个类时 |
@ConditionalOnMissingClass | 不存在某个类时 |
@ConditionalOnProperty | 当配置文件配置了某些属性时 |
@ConditionalOnProperty(prefix = “syj”, name = “algorithm”, havingValue = “token”) | 当存在配置文件中以syj为前缀的属性,属性名称为algorithm,然后它的值为token时创建 |
@ConditionalOnProperty(prefix = “syj”, name = “algorithm”, havingValue = “counter”, matchIfMissing = true) | 如果所有的都不满足的话就默认实现,不管这个属性syj.algorithm是不是等于counter |
@ConditionalOnJava | 如果是Java应用 |
@ConditionalOnWebApplication | 如果是Web应用 |
@ConditionalOnNotWebApplication | 如果不是Web应用 |
@ConditionalOnSingleCandidate | 如果只有一个实例,或者仅有一个主实例时 |
2.6 Spring原生配置文件导入
使用@ImportResource(“classpath:spring/bean.xml”)导入Spring原生的xml配置文件
3. 自动配置绑定
SpringBoot可以自动将配置文件中给定的值绑定到类中
3.1 配置绑定方式
3.1.1 原始方式:
使用Java原生代码读取到properties文件中的内容,并且把它封装到JavaBean中:
public class getProperties {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pps = new Properties();
pps.load(new FileInputStream("a.properties"));
Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + "=" + strValue);
//封装到JavaBean。
}
}
}
3.1.2 @ConfigurationProperties导入配置
只有在容器中的组件才能使用SpringBoot提供的注解功能
使用@ConfigurationProperties(prefix = "xxx")
注解可以配置properties配置文件里前缀为xxx.
的参数
3.1.3 @EnableConfigurationProperties导入配置
如果你需要的类不在容器中(比如第三方的类),但是你也想能够自动导入配置,可以在配置类中使用注解:
@Configuration
@EnableConfigurationProperties(Car.class)
public class MyConfig {}
作用:
- 为Car这个类开启配置绑定功能
- 把Car这个类加入到容器中,成为组件。
3.2 自动注入原理
看一看@SpringBootApplication注解的源码:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication{}
其中涉及了三个注解:
- @SpringBootConfiguration:底层是@Configuration,代表当前是一个配置类,而且是朱配置类
- @ComponentScan:指定扫描哪些包
- @EnableAutoConfiguration:自动注入一系列的组件,包括:
- SpringBoot项目需要的组件,按需注入
- 你的项目的MainApplication所在包下的所有组件。
3.2.1 @EnableAutoConfiguration自动注入原理
包含了另外两个注解,该注解源码如下:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
第一个注解@AutoConfigurationPackage:可以自动配置包,同时它指定了默认的包规则。原理如下:
@Import(AutoConfigurationPackages.Registrar.class) //给容器中导入一个组件
public @interface AutoConfigurationPackage {}
利用Registrar给容器中导入一系列组件。将 MainApplication 所在包下的所有组件导入进来。
AutoConfigurationPackages.java源码:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
/*
AnnotationMetadata:提供注解的源信息,被注解的类的信息。
此处我们把注解借助@SpringBootApplication将注解标注在了MainApplication上
所以这里可以获取到MainApplication所在的包的信息
*/
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(
registry,
// 通过.PackageImports(metadata)获取源信息构建对象
// 通过.getPackageNames()获取被注解的类的包名
// 借此,它可以通过包名,将这个包下的所有组件注册进来
(String[])(new AutoConfigurationPackages.PackageImports(metadata)).
getPackageNames().toArray(new String[0])
);
}
}
第二个注解@Import(AutoConfigurationImportSelector.class)
- 在AutoConfigurationImportSelector类中,有一个方法:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 注意这里:
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry
= this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
利用getAutoConfigurationEntry(annotationMetadata);
给容器中批量导入一些组件
- 方法源码如下:
protected AutoConfigurationImportSelector.AutoConfigurationEntry
getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取到所有需要导入到容器中的配置类
List<String> configurations =
this.getCandidateConfigurations(annotationMetadata, attributes);
// 过滤筛选没必要的配置类,然后返回
return new AutoConfigurationImportSelector.AutoConfigurationEntry(
configurations, exclusions
);
}
}
- getCandidateConfigurations方法源码如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 利用工厂加载方法得到所有的组件
List<String> configurations =
SpringFactoriesLoader.loadFactoryNames(
this.getSpringFactoriesLoaderFactoryClass(),
this.getBeanClassLoader()
);
return configurations;
}
- 深入 SpringFactoriesLoader.loadFactoryNames 方法,发现使用了loadSpringFactories方法,这个方法里有一行代码如下:
Enumeration<URL> urls =
classLoader != null ?
classLoader.getResources("META-INF/spring.factories") :
ClassLoader.getSystemResources("META-INF/spring.factories");
- 我们看得出来,SpringBoot默认扫描我们当前系统里面所有
META-INF/spring.factories
位置的文件spring-boot-autoconfigure-2.3.4.RELEASE.jar
包里面也有META-INF/spring.factories
,这里面写死了spring-boot一启动就要给容器中加载的所有的127个配置类
综上,第二个注解@Import(AutoConfigurationImportSelector.class)的作用就是在系统启动时注入需要的Bean,而且这些Bean是默认在配置文件里配置好的。
虽然我们127个场景的所有自动配置启动的时候默认全部加载。但xxxxAutoConfiguration 借助@Conditional注解按照条件装配规则,最终会按需配置。
3.3 按需配置和注入原理
按需注入是通过@Conditional注解实现的,其中包含了@ConditionalOnMissingBean,指的是当用户没有配置这个Bean时,就采用默认配置;当用户已经手动配置时,由于这个注解,默认配置将不生效。
同时,许多Bean的默认注入,其参数都是来自于properties配置文件的,通过@EnableConfigurationProperties(XxxxPropreties.class)获取,因此你自己指定properties配置文件即可修改默认配置的效果。
综上,有两种方法自定义组件配置:
- 使用@Bean指定一个新的组件配置类
- 了解到组件配置对应的配置文件的属性,并在application.properties中修改配置属性的值
3.4 应用
在spring-boot=autoconfigure-2.x.x.RELEASE.jar的org.springframeword.boot.autoconfigure.web.servlet.DispatcherServletAutoConfigutation中,自动配置了SpringMVC的DispatcherServlet组件