文章目录
SpringBoot自动配置原理
大家好,我是被白菜拱的猪。
一个热爱学习废寝忘食头悬梁锥刺股,痴迷于girl的潇洒从容淡然coding handsome boy。
经过两个月的春招,大厂offer虽一无所获,但在面试过程中积累了很多宝贵经验,也意识到自己在动手能力、项目上有很大的不足,从今天起基础先让让队,动手敲一敲代码,学学新技术,纸上得来终觉浅,绝知此事要躬行啊,最近在重新学一遍 SpringBoot,下面就尝试着用自己的语言把 SpringBoot 的自动配置原理讲清楚。
SpringBoot 介绍
什么是 Spring?Spring 我一直认为是春天的意思,也就是说程序员的春天到来了,它能够帮助我们做很多东西,Spring 有强大的生态,可以搞 Web 应用、分布式、微服务、数据访问、批处理…但是这些东西是怎么整合的呢?还是像以前一样写大量的配置文件吗?我记得去年做一个小设计的时候,在正式敲代码之前要配置各种 xml 文件,配置 DispatchServlet、视图解析器、拦截器,运行的时候还要下载 tomcat,实在头疼至极。
而如今,这些统统不需要,SpringBoot 应运而生,只需动动手指头,就可以跑起一个简单的 web 应用。SpringBoot 是啥,官网上有这么一句话:
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
注意这个 “just run”,是的,你只需要运行就可以,饭都给你端到跟前了,你只需要动动筷子动动嘴,饭都不用你做,岂不是美滋滋。SpringBoot 就是帮你把饭都给你做好,你要开发 web 应用,它把开发中需要的 tomcat、dispatchServlet、视图解析器、springMVC 等封装成一个 spring-boot-starter-web,你只需要引入这个 starter就好,所以简化了构建配置,不像之前一样还要想想开发 web 应用需要什么,一套打包全给给你准备好。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
注解介绍
而之前的 xml 也可以掰掰了,那么 xml 文件不用写,那里面配置的东西我们改怎么实现呢?别担心,我们可以创建一个配置类可以等效的替代 xml,只需要在这个类上写 @Configuration,它的意思就是表明这是一个配置类,@Configuration 的里面其实就是一个 @Component,它组件加入容器中,像我们熟悉的还有 @Bean、@Controller、@Service。我们可以在配置类中使用 @Bean 往容器中添加 Bean。
@Configuration
另外需要注意的是在 SpringBoot2 中,相较于SpringBoot1 @Configuration 多了一个 proxyBeanMethods 属性,它是什么意思呢?他的默认值为true。
@Configuration(proxyBeanMethods = true)
public class HelloConfig {
@Bean
public User user01() {
return new User();
}
}
我们从容器中获得 HelloConfig 对象时并打印,发现他的结果是不一样的,结果如下所示
com.happycode.boot.config.HelloConfig$$EnhancerBySpringCGLIB$$272c916d@433e536f//true
com.happycode.boot.config.HelloConfig@4c060c8f//false
当为true的时候我们获得是代理对象,然后为false 的时候就是普通对象,对应的是 Full 模式和 Lite 模式,那么他对类中添加的组件所造成的影响是什么?
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
HelloConfig bean = run.getBean(HelloConfig.class);
System.out.println(bean);
User user = bean.user01();
User user1 = bean.user01();
//通过方法调用,我们发现当proxyBeanMethods为false时获得的对象不相等,true的时候相等
System.out.println(user == user1);
User user2 = run.getBean("user", User.class);
User user3 = run.getBean("user", User.class);
//proxyBeanMethods不管为false,还是true,每次从容器中获得bean都是同一个对象
System.out.println(user2==user3);
}
}
通过比较对象是否相等,我们发现不管是 false 还是 true,从容器中获得的对象都是单例的,也就是说 proxyBeanMethods 这个属性不是控制单例还是多例。(假如我们想让容器中的对象为多例,仍然是通过@Scope(“prototype”)来实现),那么不同的是什么呢?是我们通过方法调用来获得对象时,当为 true 的时候,也就是通过代理对象调用 user01() 返回的对象都是从容器中获得的,也就是说得到的都是同一个对象,而为 false 的时候,我们每次都是重新创建一个对象。
在 SpringBoot2 之后我们发现大量的底层源码都是将 ProxyBeanMethods = false,这是因为当为 true 时在每次获得对象时每次都要从容器中检查是否含有该对象,这个检测的过程是耗时的,所以关掉是为了提高性能。那为什么有的地方不关掉呢,这是为了适应循环依赖的现象,比如我有个宠物,每次召唤我的宠物肯定是同一个,不可能每次召唤都给我创建一个宠物吧。
为什么花一小篇幅说了 proxyBeanMethods,不是将自动配置原理的吗?这是因为在看关于自动配置原理的源码时,你会发现 proxyBeanMethods 会一直出现,所以打破砂锅问到底的精神让我必须在此解释清楚 proxyBeanMethods。
@ImportResource
另外在开发中还会出现一种情况,就是之前的人仍在使用 xml 文件,那我们如何使用获取 xml 文件中配置的信息的,我们就可以使用 @ImportResource 将 xml 配置的组件加载到容器中。
@ConfigurationProperties
@ConfigurationProperties 是进行配置绑定的,将 application.properties 与我们创建的bean中的属性绑定在一起,比如 codingboy.name = ljl
@Component
@ConfigurationProperties(prefix = "codingboy")
public class User {
光有 @ConfigurationProperties 这个注解还不行,你还得把 bean 放入容器中,所以得使用 @Component,或者在启动类上面使用 @EnableConfigurationProperties 生效。
@ComponentScan
这个是一般是写在启动类上面的,目的是扫描包,一般 SpringBoot 默认扫描的包是 @SpringBootApplication 这个注解的类(也就是启动类)所在的包及其子包,所以在这些地方我们所使用的注解是有效的,那么对于其他地方,我们比如显示的指定,用 @ComponentScan这个注解进行扫描。为什么默认的是启动类所在的包及其子包,且听我娓娓道来。
自动配置原理入门
下面就跟着我的步伐,来看看 SpringBoot 程序是如何启动的,本次 SpringBoot 所使用的版本是 2.4.5。
@SpringBootApplication
我们先看看 @SpringBootApplication 是啥,点开一看,好家伙,里面还有好多注解。
重要是下面三个注解,@ComponentScan介绍过了,指定扫描哪些,不扫描哪些。@SpringBootConfiguration 点进去一看其实就是个 @Configuration,也就是说这是一个配置类。所以重任都交给了 @EnableAutoConfiguration,看名字也知道,这小子不简单啊不简单,我们今天讲的是自动配置,你这家伙就直接是使自动配置生效,辣我们点开看一看,发现又有俩注解,@AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class)。
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
1、@AutoConfigurationPackage
自动配置包,我们点开一看
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
它使用 @Import注解将Registrar类注册到容器中,那么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));
}
}
里面有两个方法,主要的是第一个方法,注册bean定义信息,它的作用是获得注解的元数据信息,通过注解的元数据信息获得包名将其转化为数组,然后将包中的组件全部注册到容器中,那么这个注解的元数据信息是哪一个注解呢,因为是合成注解,所以其实就是最外面的@SpringBootApplication,这也就是为什么前面所说的,在核心配置类所在包及其子包我们不用使用 @ComponentScan扫描,因为在最开始的时候默认就已经指定该位置下的组件全部注册到容器中。
2、@Import(AutoConfigurationImportSelector.class)
点进AutoConfigurationImportSelector,加入断点,我们发现在
记住这个数字,130。接着点啊点。点点点
就是一个字点,总结一下:
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
所以我们发现最后都是加载 META-INF/spring.factories位置的文件。
小总结
SpringBoot 在启动的时候实际上已经在文件中把要自动配置类写死了,但是加载后并不是所有的都会注册到容器中,而是按照需求,根据 @Conditional 注解来判断是否要注册,比如我们随便点开一个
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
@ConditionalOnWebApplication是指他只在 web 应用才会注册,那么注册所需的信息则跟ServerProperties进行绑定。所以我们以后敲代码的时候 application.properties 不会写啥的时候要么去 SpringBoot 官方文档查看,要么自己点开这些 xxxProperties 查看。另外假如我们修改了其中某些配置, SpringBoot 在自动配置的时候优先使用我们自定义的。
总结:
-
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
-
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
-
生效的配置类就会给容器中装配很多组件
-
只要容器中有这些组件,相当于这些功能就有了
-
定制化配置
-
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
总结
本文先是简短的介绍了 SpringBoot 的作用,然后介绍几个简单的注解,通过解释三个注解对 SpringBoot 的自动配置进行叙述,没想到写这篇文章竟然写了一下午,看和写完全两码事,有些地方因为实力有限,没有解释清楚,还请多多包涵。看视频以为是会了,但实际上还差了好远,还是那句话,纸上得来终觉浅,绝知此事要躬行。