1. SpringBoot主要自动配置项
以一个SpringBoot的HelloWorld Web项目为例它主要为我们配好了以下这些东西:
1.1 自动配好Tomcat
配置tomecat分为了两步:
-
引入Tomcat依赖,这一步SpringBoot的依赖管理实现的,引入了spring-boot-starter-web依赖,就自动引入了spring-boot-starter-tomcat依赖。
-
配置Tomcat
1.2 自动配好SpringMVC
- 引入SpringMVC全套组件
- 自动配好SpringMVC常用组件(功能)
1.3 自动配好Web常见功能,如:字符编码问题SpringBoot
帮我们配置好了所有web开发的常见场景, 通过以下程序查看容器里面自动配置的组件。
1.4 配置好了默认的包结构:
以前整合Spring、SpringMVC,需要指定Controller都在哪个包下,包扫描等等各种规则:
但是现在使用SpringBoot我们不需要配置任何的包扫描。
在官方文档中对默认的包扫描规则是做了一个解释:
主程序所在包(myapplication)及其下面的所有子包里面的组件都会被默认扫描进来,无需以前的包扫描配置。
注:如果想将除主程序所在包及其下面的所有子包里面的组件扫描出来就需要自己配置一下,在SpringBootApplication中有一个属性叫做scanBasePackages。指定一下需要扫描的包(com)即可。
或者直接使用使用@ComponentScan 指定扫描路径,ComponentScan 其实就是
SpringBootApplication的一个子注解。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(“com”)
这三个注解共同实现了SpringBootApplication的功能=
1.5各种配置基本都配置好了默认值,无需再次配置,也可以自己修改
以文件上传为例:
默认配置最终都是映射到某个类上,如:MultipartProperties
配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
1.6按需加载所有自动配置项:
有非常多的starter,引入了哪些场景这个场景的自动配置才会开启。
SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面。
2. 关于自动配置的一些注解
2.1 组件添加:
代码中预先准备了两个准备添加的组件:
分别是User与Pet:
在SpringBoot之前使用原生的Spring与SpringBoot做一个对比,首先创建一个Spring的配置文件:beans.xml
之后使用bean标签进行创建对应的属性
SpringBoot中新的创建方式:
首先创建一个类,之后加注@Configuration注释,告诉SpringBoot这是一个配置类,等同于Spring中的配置文件。
在配置文件中使用bean标签给容器中添加组件,在配置类中不能写标签了,就写一个方法构造出来,之后加上@Bean注解。 如果不想将方法名作为组件名称,可以在Bean中标注上。
通过这种方法将容器中所有的组件打印出来:
可以看到组件已经注册成功 。
注: 容器中注册的组件默认是单实例的,此处可以验证一下:
注 :@configuration标注的这个类本身也是一个组件
在SpringBoot2.0以后Configuration注解中多了一个属性,且默认为true
意思为是不是代理Bean的方法。
此处验证了外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象。因为被@configuration注释的这个对象它不是一个普通的对象,而是被SpringCGLIB增强了的代理对象。
如果@Configuration(proxyBeanMethods = true),即Full模式,则代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。如果有就直接调用,没有才会新创,也就是说他会保持组件单实例。
如果调为false,即Lite:
可以看到此时得到的对象就不是代理对象,多次调用个方法得到的也不再相等。
这种设计主要是为了组件依赖场景:
如果现在给User类中加一个属性,Pet
判断用户调用的宠物是否就是容器中的宠物
Full模式下:用户调用的宠物就是容器中的宠物。
Lite模式下,用户调用的宠物不是容器中的宠物。
Lite模式下SpringBoot不会来查检查方法返回的东西在容器中有没有,这样SpringBoot启动速度就快一点 。
总结:
配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
2.2 通过Import组件创建组件
@Import({User.class, DBHelper.class})
给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名。
下面的类中我们加上了@Import注释,同时又自己创建了一个user01这个组件。
以及使用.xml文件方式注册的组件,共有3个User类型的组件
主程序中运行的测试代码:
结果:
共有3个User类型的组件,1个DBHelper类型的组件,其中名称为全类名的组件是Import注解创建的。
2.3 按照条件装配诸如组件
Conditional指定的条件,则进行组件注入
条件装配:当满足Conditional指定的条件,则进行组件注入:
例如:
ConditionalOnBean,当容器中有某个组件的时候,才会做某些行为。
例如:
当容器中有Tom这个组件的时候才会给容器中注册user01这个组件
当写在整个类的上面时:只有类中有一个组件名叫tom,类中的行为才会被实现(注册user01、tom组件),下图这种情况中tom、user01组件都不会被注册。
ConditionalOnMissBean,当容器中没有有某个组件的时候
ConditionalOnClass,当容器中有某个类的时候
ConditionalOnClass,当容器中没有某个类的时候
ConditionalOnResources,当容器中有某个资源的时候
2.4 原生配置文件引入
@ImportResource
很多组件还是以spring的配置文件的方式导入的导入,如果想要挨个迁移成注解的方式会非常麻烦。
以下是一个例子,在beans.xml中注册了两个组件,hehe和haha
输出容器中是否有hehe和haha
可以看到是没有注册成功的。
但是如果此时也不想一点一点钱一成@bean注解的这种写法,就需要给一个配置类上协商@ ImportResource,导入资源:类路径下的beans.xml
此时,再次测试可以看到邮件注册成功:
2.5 配置绑定
2.5.1 背景
开发过程中我们习惯于将一些容易变化的东西(数据库的账号,密码等)写到配置文件中,为了方便之后又将配制文件中的内容一一解析到JavaBean中(数据库连接池)
也就是说需要将配置文件中的数据解析到JavaBean中。
如果使用Java原生代码还是比较麻烦的:
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.......
}
}
}
2.5.2 SpringBoot对配置的优化@Component与@ConfigurationProperties
举例:创建一个Car类
将和汽车有关的属性写入到配置文件中,
之后给Car上面写上注解@ConfigurationProperties(配之文件中的前缀)
此时还未生效,需要加上@Component将组件加入容器中,使得对应的功能生效。
测试:
利用Spring的自动注入,将容器中的Car拿过来,返回这个Car。
2.5.3 SpringBoot对配置的优化@EnableConfigurationProperties @ConfigurationProperties(主要使用这种方式)
@EnableConfigurationProperties (需要开启属性绑定功能的类)有两个功能:
1.开启这个类的属性绑定功能
2.将这个类作为组件自动注册到容器中
使用场景:有可能是引用的第三方包中的类,没法给类中加上@Component注解。
3. SpringBoot自动配置原理入门
@SpringBootApplication相当于下面的3个注解
3.1@SpringBootConfiguration:
进入SpringBootConfiguration源码可以看到:
@SpringBootConfiguration就相当于是@Configuration。代表当前是一个配置类
3.2 @ComponentScan(“com.atguigu.boot”):
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来, 想要改变扫描路径:@SpringBootApplication(scanBasePackages=“com.atguigu”)或者ComponentScan(“com.atguigu.boot”)
3.3 @EnableAutoConfiguration:
主要由以下两个注解实现功能
3.3.1 @AutoConfigurationPackage:
自动配置包,指定了默认的包规则
@Import(AutoConfigurationPackages.Registrar.class) //给容器中导入Registrar
public @interface AutoConfigurationPackage {}
//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来?MainApplication 所在包下。
//这是Registrar的实现:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
//其中new PackageImports(metadata).getPackageNames()就是得到MainApplication所在的包名,将包名封装到数组中,之后注册进去(也就是将主类所在的包中的组件批量注册),这就解释了为什么默认的包路径是MainApplication所在的包
3.3.2 @Import(AutoConfigurationImportSelector.class)
-
@Import注解的源码中的selectImports,确定到底要给容器中导入哪些组件,其返回的String[]是由getAutoConfigurationEntry(annotationMetadata)决定的。
-
调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
List configurations:
利用Spring的工厂加载器 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
-
从META-INF/spring.factories位置来加载一个资源文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
有些位置的META-INF中是没有spring.factories的
主要是扫描spring-boot-autoconfigure-2.3.4.RELEASE.jar中的META-INF/spring.factories,SpringBoot兼容的全场景的自动配置都在此处列举,共127个。
虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
符合条件装配规则(@Conditional)才会导入。
spring-boot-autoconfigure这个包中有我们需要的全场景配置:
以AOP为例:
需要判断:文件中是否存在spring.aop这个配置,且spring.aop.auto的值是true,最后一个matchIFMissiing会认为就算没有将这个值配置为true,也会默认是true。
对于AspectJAutoProxyingConfiguration这个类,可以看到上面的 @ConditionalOnClass(Advice.class)注释 ,说明只有当Advice.class存在时,这个类中的一些配置才会生效。
对于ClassProxyingConfiguration这个类,可以看到
@ConditionalOnMissingClass(“org.aspectj.weaver.Advice”) 意思是如果没有org.aspectj.weaver.Advice这个类,下面的配置才会生效
@ConditionalOnProperty(prefix = “spring.aop”, name = “proxy-target-class”, havingValue = “true”,matchIfMissing = true)意思是必须有spring.aop这个配置项,且spring.aop.name属性必须是proxy-target-class,havingValue 这个配置项必须是true,如果不是true也会默认是true。
上面的条件这个类都是满足的,所以这个类中的配置是生效的。
但是这个类只是简单的AOP功能,也就是说这个AOP必须有接口有实现类才可以创建代理对象。
以cache为例:
@ConditionalOnClass(CacheManager.class)
首先确认容器中有没有CacheManager.class存在,CacheManager.class是在spring-context这个包中的类,此包是整个spring环境中的核心包,所以肯定是存在的
@ConditionalOnBean(CacheAspectSupport.class)
判断容器中有没有CacheAspectSupport.class这种类型的组件(上面的例子中都是以名称判断的,也可以通过其他关键点判断)
通过这行代码可以判断出容器中是没有CacheAspectSupport.class这种类型的组件的存在的
最终输出结果为0,所以不满足@ConditionalOnBean(CacheAspectSupport.class),所以这个类中的所有配置都没有生效,后面的条件已经不需要去看了。
@ConditionalOnMissingBean(value = CacheManager.class, name = “cacheResolver”)
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
以web中的servlet中DispatcherServletAutoConfiguration为例:
@AutoConfigureOrder设置配置生效顺序。介绍
@ConditionalOnWebApplication(type = Type.SERVLET):判断当前是否是一个原生的serrvlet应用,因为springboot2现在支持两种web模式开发,一种是响应式编程,一种是原生的servlet。
@ConditionalOnClass(DispatcherServlet.class):判断是否导入DispatcherServlet.class这个类
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)在ServletWebServerFactoryAutoConfiguration这个类配置完后再配置当前类。
DispatcherServletAutoConfiguration配置完后开始配置DispatcherServletConfiguration
第一个配置设置开启模式Lite,Lite模式下SpringBoot不会来查检查方法返回的东西在容器中有没有,这样SpringBoot启动速度就快一点 。
第二个在默认是生效的可以先不管
第三个确认是否有ServletRegistrartion这个类存在
第四个作用有两个,第一是开启括号中类与配置文件的配置绑定功能,会和配置文件中spring.mvc开始的所有属性一一对应进行绑定
第二个功能是将WebMvcProperties放入容器
可以看到是有一个的
接下来进入这个类:
配置好了DispatcherServlet
给容器中注册组件,在方法中自己new了一个DispatcherServlet,一步步设置好后return。
说明之前在Spring SpringMVC中需要做的那些配置,现在相当于底层已经new好了,帮用户配置好了。
这个类:配置好了文件上传组件
第一个注解为:当容器中有这个类型的组件
第二个为:当容器中没有这个名字的组件。
当用户自己在文件中配置了文件上传解析器,但是没有按照规定起名,此时就可以在文件中找到用户自己配置的解析器,再将解析器返回出去,完成了规范化操作。(因为这个方法的名字叫做multipartResolver,注册的组件会以方法名称作为组件名称)
以HttpEncodingAutoConfiguration(用于防止网页乱码)为例
1.@ConditionalOnWebApplication判断是不是一个原生的Servlet应用
2.@ConditionalOnClass判断是否有某个类
3.判断是否有某个属性
进入类中注册组件@ConditionalOnMissingBean此注解判断用户是否已经配置过了,如果用户配置过则以用户的优先。
也就是说:SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
4. 总结
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。从xxxxProperties里面拿,xxxProperties和配置文件进行了绑定
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了
- 定制化配置
• 用户直接自己@Bean替换底层的组件
• 用户去看这个组件是获取的配置文件什么值就去修改。
例如:
HttpEncodingAutoConfiguration这个例子中的属性就可以直接配置
查询配置项可以通过官方文档
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
也可以通过查询底层代码,例如查找关于cache的配置:
进入cache的自动配置代码。
找到EnableConfigurationProperties中写的类,进入,可以看到他的配置值是以spring.cache开头的各种属性值
导入所有的自动配置类xxxxxAutoConfiguration ----> 按需加载组件 ----> 组件xxxxProperties里面拿值 ----> xxxxProperties绑定的就是application.properties