SpringBoot

发布于:2024-04-27 ⋅ 阅读:(19) ⋅ 点赞:(0)

SpringBoot

第一部分 SpringBoot应用

相关概念

约定优于配置

约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计规范。

本质上是对系统、类库或框架中一些东西假定一个大众化合理的默认值(缺省值)。

例如在模型中存在一个名为User的类,那么对应到数据库会存在一个名为user的表,此时无需做额外的配置,只有在偏离这个约定时才需要做相关的配置(例如你想将表名命名为t_user等非user时才需要写关于这个名字的配置)。

如果所用工具的约定与你的期待相符,便可省去配置;反之,你可以配置来达到你所期待的方式。简单来说就是假如你所期待的配置与约定的配置一致,那么就可以不做任何配置,约定不符合期待时才需要对约定进行替换配置。

好处:大大减少了配置项

SpringBoot主要特性

1.SpringBoot Starter:他将常用的依赖分组进行了整合,将其合并到一个依赖中,这样就可以一次性添加到项目的Maven或Gradle构建中;

2.使编码变得简单:SpringBoot采用 JavaConfig的方式对Spring进行配置,并且提供了大量的注解,极大的提高了工作效率。

3.自动配置:SpringBoot的自动配置特性利用了Spring对条件化配置的支持,合理地推测应用所需的bean并自动化配置他们;

4.使部署变得简单:SpringBoot内置了三种Servlet容器,Tomcat,Jetty,undertow.我们只需要一个Java的运行环境就可以跑SpringBoot的项目了,SpringBoot的项目可以打成一个jar包。

配置文件优先级

  1. 先去项目根目录找config文件夹下找配置文件件

  2. 再去根目录下找配置文件

  3. 去resources下找config文件夹下找配置文件

  4. 去resources下找配置文件

SpringBoot会从这四个位置全部加载主配置文件,如果高优先级中配置文件属性与低优先级配置文件不冲突的属性,则会共同存在— 互补配置 。

备注:

1.如果同一个目录下,有application.yml也有application.properties,默认先读取 application.properties(2.4.0会先读取yml)。

2.如果同一个配置属性,在多个配置文件都配置了,默认使用第1个读取到的,后面读取的不覆盖前面读取到的。

SpringBoot日志框架

日志框架介绍

Spring 框架选择使用了 JCL 作为默认日志输出。而 Spring Boot 默认选择了 SLF4J 结合 LogBack

下图是 SLF4J 结合各种日志框架的官方示例,从图中可以清晰的看出 SLF4J API 永远作为日志的门面,直接应用与应用程序中,具体的日志框架实现SLF4J。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意:由于每一个日志的实现框架都有自己的配置文件,所以在使用 SLF4j 之后,配置文件还是要使用实现日志框架的配置文件。

统一日志框架使用步骤归纳如下

  1. 排除系统中的其他日志框架。

  2. 使用中间包替换要替换的日志框架。

  3. 导入我们选择的 SLF4J 实现。

SpringBoot使用SLF4J分析

1.在SpringBoot的maven依赖中去除了其他的三方日志框架

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.在 spring-boot-starter中引入了 spring-boot-starter-logging

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

spring-boot-starter-logging的依赖如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中logback-classic包含了日志框架 Logback的实现

log4j-to-slf4j 是 log4j 向 slf4j 转换的jar

jul-to-slf4j是Java 自带的日志框架转换为 slf4j

IDEA 中查看 Maven 依赖关系如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由上可知:Spring Boot 可以自动的适配日志框架,而且底层使用 SLF4j + LogBack 记录日志,如果我们自行引入其他框架,需要排除其日志框架。

第二部分 SpringBoot源码分析

构建源码环境

构建要求jdk是1.8+的,Maven3.5+

1.下载源码:我下的是spring-boot-2.2.9.RELEASE

2.编译:进⼊spring-boot源码根⽬录执⾏mvn命令: mvn clean install -DskipTests -Pfast // 跳过测试⽤例,会下载⼤量 jar 包(时间会长一些)

3.打开源码的pom.xml关闭maven代码检查

<properties> 

<revision>2.2.9.RELEASE</revision> 

<main.basedir>${basedir}</main.basedir> 

<disable.checks>true</disable.checks> 

</properties>

4.用工具打开源码,新建一个web工程,我在这里叫spring-boot-mytest,然后在工程中添加一个controller

@RequestMapping("/test")
	public String test() {
		System.out.println("源码导入成功");
		return "源码导入成功";
	}

5.运行spring-boot-mytest,在浏览器输入http://localhost:8080/test 浏览器能打印出"源码导入成功"就代表源码构建成功

分析源码主要是为了解决下面的问题

  1. starter是什么?我们如何去使用这些starter?

  2. 为什么包扫描只会扫描核心启动类所在的包及其子包

  3. 在SpringBoot启动的过程中,是如何完成自动装配的?

  4. 内嵌Tomcat是如何被创建及启动的?

  5. 使用了web场景对应的starter,springmvc是如何自动装配?

依赖管理

在pom文件中SpringBoot工程会继承一个spring-boot-starter-parent

截屏2021-08-31 下午5.56.22

spring-boot-starter-parent会继承一个spring-boot-dependencies

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在spring-boot-dependencies的properties中定义了SpringBoot中相关jar的版本

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在spring-boot-dependencies的dependencyManagement中定义了SpringBoot版本的依赖的组件以及相应版本

截屏2021-08-31 下午6.03.58

spring-boot-starter-parent 通过继承 spring-boot-dependencies 从而实现了SpringBoot的版本依

赖管理,所以我们的SpringBoot工程继承spring-boot-starter-parent后已经具备版本锁定等配置了,这也

就是在 Spring Boot 项目中部分依赖不需要写版本号的原因

在 spring-boot-starter-parent 的 properties 节点中定义了

  • 工程的Java版本为 1.8 。
  • 工程代码的编译源文件编码格式为 UTF-8
  • 工程编译后的文件编码格式为 UTF-8
  • Maven打包编译的版本

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在spring-boot-mytest中还引入了spring-boot-starter-web,其打包了Web开发场景所需的底

层所有依赖(基于依赖传递,当前项目也存在对应的依赖jar包)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正是如此,在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发,而

不需要额外导入Tomcat服务器以及其他Web依赖文件等。当然,这些引入的依赖文件的版本号还是由

spring-boot-starter-parent父依赖进行的统一管理。

Spring Boot除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖,

我们可以打开Spring Boot官方文档,搜索“Starters”关键字可以查询场景依赖启动器,对于官方的启动器都不需要指定版本号

SpringBoot并不是对所有的场景都提供了场景启动器,比如要使用Druid时就没有对应的启动器,但是阿里主动与Spring Boot框架进行了整合,实现了其依赖启动器(druid-spring-boot-starter)。在引入这种第三方的启动器需要指定版本

截屏2021-08-31 下午6.23.45

自动配置

在启动类里面会用到**@SpringBootApplication**点进注解会发现其是一个复合注解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中和自动配置相关的注解是**@EnableAutoConfiguration**,进入该注解:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的Bean ,并加载到 IOC 容器。

@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到

IoC容器。

@EnableAutoConfiguration

@EnableAutoConfiguration有两个比较重要的注解:@AutoConfigurationPackage、@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage

截屏2021-09-01 上午11.19.21

@AutoConfigurationPackage注解通过@Import注册了一个AutoConfigurationPackages.Registrar.class

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

register注册了一个bean,该bean的name是AutoConfigurationPackages.class.getName(),class是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有一个参数,这个参数是使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解 @Entity 定义的 entity类。

@Import(AutoConfigurationImportSelector.class)

@Import(AutoConfigurationImportSelector.class)导入了AutoConfigurationImportSelector

AutoConfigurationImportSelector可以帮助 SpringBoot 应用将所有符合条件的自动配置类都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。AutoConfigurationImportSelector的继承关系如下:

截屏2021-09-01 上午11.45.40

在启动类的run方法中在自动配置时会使用到AutoConfigurationImportSelector,这里先不从run方法进入,直接在AutoConfigurationImportSelector中分析相关代码

在AutoConfigurationImportSelector中有一个内部类AutoConfigurationGroup,其process方法就是run中在实现自动配置时会调用的方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关键方法是getAutoConfigurationEntry

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截屏2021-09-01 下午2.36.14

关键方法是getCandidateConfigurations,getCandidateConfigurations最后会调用loadSpringFactories

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

loadSpringFactories这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。

spring.factories里面保存着SpringBoot的默认提供的自动配置类。SpringBoot的spring.factories中和自动配置相关的代码如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

filter方法如下:

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
   long startTime = System.nanoTime();
   // 将从spring.factories中获取的自动配置类转成字符串数组
   String[] candidates = StringUtils.toStringArray(configurations);
   // 定义skip数组,是否需要跳过。注意skip数组与candidates数组顺序一一对应
   boolean[] skip = new boolean[candidates.length];
   boolean skipped = false;
   // getAutoConfigurationImportFilters方法:拿到OnBeanCondition, OnClassCondition和OnWebApplicationCondition
   // 然后遍历这三个条件类去过滤从spring.factories加载的大量配置类
   for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
      // 调用各种aware方法,将beanClassLoader,beanFactory等注入到filter对象中,
      // 这里的filter对象即OnBeanCondition,OnClassCondition或 OnWebApplicationCondition
      invokeAwareMethods(filter);
      // 判断各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的
      // @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里 面的注解值)是否匹配,
      // 注意candidates数组与match数组一一对应
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
         // 若有不匹配的话
         if (!match[i]) {
            // 不匹配的将记录在skip数组,标志skip[i]为true,也与candidates数组一一 对应
            skip[i] = true;
            // 因为不匹配,将相应的自动配置类置空
            candidates[i] = null;
            // 标注skipped为true
            skipped = true;
         }
      }
   }
   // 这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和 OnWebApplicationCondition过滤后,全部都匹配的话,则全部原样返回
   if (!skipped) {
      return configurations;
   }
   // 建立result集合来装匹配的自动配置类
   List<String> result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) {
      // 若skip[i]为false,则说明是符合条件的自动配置类,此时添加到result集合中
      if (!skip[i]) {
         result.add(candidates[i]);
      }
   }
   if (logger.isTraceEnabled()) {
      int numberFiltered = configurations.size() - result.size();
      logger.trace("Filtered " + numberFiltered + " auto configuration class in "
            + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
   }
   // 最后返回符合条件的自动配置类
   return new ArrayList<>(result);
}

filter 方法主要做的事情就是调用AutoConfigurationImportFilter 接口的 match 方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnClass , @ConditionalOnBean 或 @ConditionalOnWebApplication 是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。

AutoConfigurationEntry 方法主要做的事情如下:

  • 从 spring.factories 配置文件中加载 EnableAutoConfiguration 自动配置类),获取的自动配置类如图所示。
  • 若 @EnableAutoConfiguration 等注解标有要 exclude 的自动配置类,那么再将这个自动配置类排除掉;
  • 排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些不符合条件的自动配置类;
  • 经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类;
  • 最后再将符合条件的自动配置类返回。
@Conditional

@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。

  • @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean
  • @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean
  • @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式的条件判断。
  • @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean
  • @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean
  • @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean
  • @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化
  • @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
  • @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
  • @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
  • @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
  • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。

SpringBoot在自动配置时主要做了以下事情:

  • 加载:从spring.factories配置文件中加载自动配置类;
  • 排除:加载的自动配置类中排除掉 @EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类;
  • 过滤:然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和@ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;
  • 通知:然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和 exclude 的自动配置类。
  • 注册:最后spring再将最后筛选后的自动配置类导入IOC容器中
@ComponentScan

从定义的扫描路径中,找出标识了需要装配的类自动装配到spring 的bean容器中。

常用属性如下:

  • basePackages、value:指定扫描路径,如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径
  • basePackageClasses:指定具体扫描的类
  • includeFilters:指定满足Filter条件的类
  • excludeFilters:指定排除Filter条件的类

includeFilters和excludeFilters 的FilterType可选:

ANNOTATION(注解类型 默认)、ASSIGNABLE_TYPE(指定固定类)、ASPECTJ(ASPECTJ类型)、REGEX(正则表达式)、CUSTOM(自定义类型),自定义的Filter需要实现TypeFilter接口

在SpringBoot中没有指定扫描路径所以默认是启动类所在的路径

run方法

run方法会调用重载的run方法,重载的run方法会创建一个SpringApplication对象,然后调用对象的run方法

截屏2021-09-02 下午5.48.00

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建SpringApplication对象

在创建SpringApplication对象的时候会四件比较重要的事:

  • 推断应用类型
  • 初始化ApplicationContextInitializer(spring.factories文件中ApplicationContextInitializer的值)
  • 初始化ApplicationListener(spring.factories文件中ApplicationListener的值)
  • 推断main方法的类名

截屏2021-09-02 下午5.49.16

ApplicationContextInitializer的类如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ApplicationListener监听器如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

getSpringFactoriesInstances方法通过SpringFactoriesLoader.loadFactoryNames可以获取spring.factories文件中对应的类名,从而生成对应的对象

截屏2021-09-02 下午6.41.14

第一步:获取并启动监听器

事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。Spring的事件为Bean和Bean之间的消息传递提供支持。当一个对象处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知,消息、邮件等情况。

getRunListeners方法通过调用getSpringFactoriesInstances方法获取spring.factories文件中SpringApplicationRunListener对应的值的对象

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SpringApplicationRunListener对应的类如下:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

需要注意的是这里的监听器EventPublishingRunListener和在构建SpringApplication时获取的ApplicationListener监听器是不一样的,EventPublishingRunListener是负责在SpringBoot启动的各个阶段广播出不同的消息给ApplicationListener

第二步:构建应用上下文环境

应用上下文环境包括计算机的环境、Java环境、Spring的运行环境、Spring项目的配置(application.properties/yml)等等。

构造应用上下文环境主要功能如下:

  • 创建并配置相应的环境
  • 根据用户配置environment系统环境
  • 启动相应的监听器
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // Create and configure the environment
   //创建并配置相应的环境
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   //根据用户配置environment系统环境
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   ConfigurationPropertySources.attach(environment);
   //启动相应的监听器 其中configFileApplicationListener 是加载项目配置文件监听器
   listeners.environmentPrepared(environment);
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   ConfigurationPropertySources.attach(environment);
   return environment;
}

getOrCreateEnvironment方法主要是根据之前设置的web应用类型创建对应的对象,在web中创建的是StandardServletEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {
   if (this.environment != null) {
      return this.environment;
   }
   switch (this.webApplicationType) {
   case SERVLET:
      return new StandardServletEnvironment();
   case REACTIVE:
      return new StandardReactiveWebEnvironment();
   default:
      return new StandardEnvironment();
   }
}

StandardServletEnvironment的继承关系如下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

configureEnvironment方法主要是封装main方法的args和激活相应的配置文件

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
   if (this.addConversionService) {
      ConversionService conversionService = ApplicationConversionService.getSharedInstance();
      environment.setConversionService((ConfigurableConversionService) conversionService);
   }
   //封装main 的args
   configurePropertySources(environment, args);
   //激活相应的配置文件
   configureProfiles(environment, args);
}

environmentPrepared方法主要是来加载配置文件的,改方法最后会调用SimpleApplicationEventMulticaster的multicastEvent方法,如下:

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   Executor executor = getTaskExecutor();
   for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      if (executor != null) {
         executor.execute(() -> invokeListener(listener, event));
      }
      else {
         invokeListener(listener, event);
      }
   }
}

在执行getApplicationListeners方法的时候会返回一个configFileApplicationListener监听器,是加载项目配置文件监听器,之前说的配置文件的地址及优先级就是在这个监听器里面:

截屏2021-09-03 上午11.15.13

第三步:初始化应用上下文

createApplicationContext方法创建了应用上下文和进行了IOC的初始化

方法如下:

protected ConfigurableApplicationContext createApplicationContext() {
   Class<?> contextClass = this.applicationContextClass;
   if (contextClass == null) {
      try {
         switch (this.webApplicationType) {
         case SERVLET:
            contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
            break;
         case REACTIVE:
            contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
            break;
         default:
            contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
         }
      }
      catch (ClassNotFoundException ex) {
         throw new IllegalStateException(
               "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
      }
   }
   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

根据webApplicationType的不同会创建不同的上下文对象,一共有三个类型:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在创建SpringApplication对象时指定了webApplicationType=SERVLET,所以在web中创建的对象是AnnotationConfigServletWebServerApplicationContext

而AnnotationConfigServletWebServerApplicationContext继承了ServletWebServerApplicationContext,ServletWebServerApplicationContext继承了GenericWebApplicationContext,GenericWebApplicationContext继承了GenericApplicationContext;

所以在创建AnnotationConfigServletWebServerApplicationContext时候也触发了GenericApplicationContext的构造方法,而GenericApplicationContext构造方法中创建了一个DefaultListableBeanFactory保存到beanFactory中,DefaultListableBeanFactory就是IOC容器

应用上下文可以理解成IOC容器的高级表现形式,应用上下文确实是在IOC容器的基础上丰富了一些高级功能。应用上下文对IOC容器是持有的关系。他的一个属性beanFactory就是IoC容器(DefaultListableBeanFactory)。所以他们之间是持有,和扩展的关系。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第四步:刷新上下文前置处理

prepareContext方法如下:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   //设置容器环境
   context.setEnvironment(environment);
   //执行容器后置处理
   postProcessApplicationContext(context);
   //启动初始化器
   applyInitializers(context);
   //向各个监听器发送容器已经准备好的事件
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   //将main函数中的args参数封装成单例Bean,注册进容器
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      //将 printedBanner 也封装成单例,注册进容器
      beanFactory.registerSingleton("SpringBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   //加载我们的启动类,将启动类注册进IOC容器
   load(context, sources.toArray(new Object[0]));
   //发布容器已加载事件
   listeners.contextLoaded(context);
}

其中主要的方法有两个:getAllSources()、load(context, sources.toArray(new Object[0]))

getAllSources方法返回了在创建SpringApplication对象时被放入primarySources的启动类

public Set<Object> getAllSources() {
   Set<Object> allSources = new LinkedHashSet<>();
   if (!CollectionUtils.isEmpty(this.primarySources)) {
      allSources.addAll(this.primarySources);
   }
   if (!CollectionUtils.isEmpty(this.sources)) {
      allSources.addAll(this.sources);
   }
   return Collections.unmodifiableSet(allSources);
}

load方法主要是为了把启动类加入IOC容器中

protected void load(ApplicationContext context, Object[] sources) {
   if (logger.isDebugEnabled()) {
      logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
   }
   BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
   if (this.beanNameGenerator != null) {
      loader.setBeanNameGenerator(this.beanNameGenerator);
   }
   if (this.resourceLoader != null) {
      loader.setResourceLoader(this.resourceLoader);
   }
   if (this.environment != null) {
      loader.setEnvironment(this.environment);
   }
   loader.load();
}

loader方法中createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources)、loader.load()是重点

createBeanDefinitionLoader

createBeanDefinitionLoader方法在调用前,执行了getBeanDefinitionRegistry(context),主要是将应用上下文强转为BeanDefinitionRegistry

private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
   if (context instanceof BeanDefinitionRegistry) {
     //将应用上下文强转为BeanDefinitionRegistry
      return (BeanDefinitionRegistry) context;
   }
   if (context instanceof AbstractApplicationContext) {
      return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
   }
   throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}

createBeanDefinitionLoader方法中直接创建了一个BeanDefinitionLoader对象:

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
   return new BeanDefinitionLoader(registry, sources);
}

其中BeanDefinitionLoader的构造函数如下:

截屏2021-09-06 下午2.34.48

其中sources存的是启动类

load

load方法经过反复调用重载方法后执行以下代码:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中register最后会调用doRegisterBean:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

该方法会把对应的类封装为AnnotatedGenericBeanDefinition ,然后封装成definitionHolder,其中最后一行registerBeanDefinition方法内部会调用registry.registerBeanDefinition来注册bean

截屏2021-09-06 下午3.10.07

org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition如下,就是把beanDefinition放到IOC容器的beanDefinitionMap中

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
       throws BeanDefinitionStoreException {

    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    if (beanDefinition instanceof AbstractBeanDefinition) {
       try {
          ((AbstractBeanDefinition) beanDefinition).validate();
       }
       catch (BeanDefinitionValidationException ex) {
          throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                "Validation of bean definition failed", ex);
       }
    }

    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {
       if (!isAllowBeanDefinitionOverriding()) {
          throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
       }
       else if (existingDefinition.getRole() < beanDefinition.getRole()) {
          // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
          if (logger.isInfoEnabled()) {
             logger.info("Overriding user-defined bean definition for bean '" + beanName +
                   "' with a framework-generated bean definition: replacing [" +
                   existingDefinition + "] with [" + beanDefinition + "]");
          }
       }
       else if (!beanDefinition.equals(existingDefinition)) {
          if (logger.isDebugEnabled()) {
             logger.debug("Overriding bean definition for bean '" + beanName +
                   "' with a different definition: replacing [" + existingDefinition +
                   "] with [" + beanDefinition + "]");
          }
       }
       else {
          if (logger.isTraceEnabled()) {
             logger.trace("Overriding bean definition for bean '" + beanName +
                   "' with an equivalent definition: replacing [" + existingDefinition +
                   "] with [" + beanDefinition + "]");
          }
       }
       this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    else {
       if (hasBeanCreationStarted()) {
          // Cannot modify startup-time collection elements anymore (for stable iteration)
          synchronized (this.beanDefinitionMap) {
             this.beanDefinitionMap.put(beanName, beanDefinition);
             List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
             updatedDefinitions.addAll(this.beanDefinitionNames);
             updatedDefinitions.add(beanName);
             this.beanDefinitionNames = updatedDefinitions;
             removeManualSingletonName(beanName);
          }
       }
       else {
          // Still in startup registration phase
          this.beanDefinitionMap.put(beanName, beanDefinition);
          this.beanDefinitionNames.add(beanName);
          removeManualSingletonName(beanName);
       }
       this.frozenBeanDefinitionNames = null;
    }

    if (existingDefinition != null || containsSingleton(beanName)) {
       resetBeanDefinition(beanName);
    }
    else if (isConfigurationFrozen()) {
       clearByTypeCache();
    }
}
第五步:刷新上下文

refreshContext(context);方法最后会调用spring的refresh();

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();//刷新上下文环境

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//这里是在子类中启动refreshBeanFactory() 的地方

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);//准备bean工厂,以便在此上下文中使用

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);//设置 beanFactory 的后置处理

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);//调用 BeanFactory 的后处理器,这些处理器是在Bean 定义中向容器注册的

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);//注册Bean的后处理器,在Bean创建过程中调用

         // Initialize message source for this context.
         initMessageSource();//对上下文中的消息源进行初始化

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();//初始化上下文中的事件机制

         // Initialize other special beans in specific context subclasses.
         onRefresh();//初始化其他特殊的Bean

         // Check for listener beans and register them.
         registerListeners();//检查监听Bean 并且将这些监听Bean向容器注册

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);///实例化所有的(non-lazy-init)单件

         // Last step: publish corresponding event.
         finishRefresh();//发布容器事件,结束Refresh过程
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

上述refresh()方法是spring中容器初始化方法,这里和SpringBoot涉及的方法主要有:obtainFreshBeanFactorypostProcessBeanFactoryinvokeBeanFactoryPostProcessors

obtainFreshBeanFactory

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在refreshBeanFactory中SpringBoot什么也没干:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在getBeanFactory中SpringBoot直接返回了在创建应用上下文时(AnnotationConfigServletWebServerApplicationContext)创建的DefaultListableBeanFactory

截屏2021-09-06 下午5.27.35

obtainFreshBeanFactory方法中主要做了三个工作,刷新beanFactory,获取beanFactory,返回beanFactory。

postProcessBeanFactory

postProcessBeanFactory()方法向上下文中添加了Bean工厂的后置处理器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

invokeBeanFactoryPostProcessors
流程梳理

在IOC容器的初始化过程包括三个步骤,分别是:Resource定位、BeanDefinition的载入、BeanDefinition的注册

在invokeBeanFactoryPostProcessors()方法中完成了IOC容器初始化过程的三个步骤。

  • Resource定位:在SpringBoot中,包扫描是从主类所在的包开始扫描的,在刷新应用上下文前置处理(prepareContext()方法)中,会先将主类解析成BeanDefinition,然后在refresh()方法的invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition获取basePackage的路径。这样就完成了定位的过程。

    其次SpringBoot的各种starter是通过SPI扩展机制实现的自动装配,SpringBoot的自动装配同样也是在invokeBeanFactoryPostProcessors()方法中实现的。

    还有一种情况,在SpringBoot中有很多的@EnableXXX注解,其底层是@Import注解,在invokeBeanFactoryPostProcessors()方法中也实现了对该注解指定的配置类的定位加载。

  • BeanDefinition的载入:所谓的载入就是通过定位得到的basePackage,SpringBoot会将该路径拼接成:classpath:xxx/xxx/**/.class的形式,然后xPathMatchingResourcePatternResolver类会将该路径下所有的.class文件都加载进来,然后遍历判断是不是有**@Component**注解(@Configuration,@Controller,@Service等注解底层都是@Component注解),如果有的话,就是我们要装载的BeanDefinition。

  • BeanDefinition的注册:通过调用BeanDefinitionRegister接口的实现来完成。把载入过程中解析得到的BeanDefinition向IOC容器进行注册。注册就是在IOC容器中将BeanDefinition对象put到一个ConcurrentHashMap中,IOC容器就是通过这个HashMap来持有这些BeanDefinition数据的。比如DefaultListableBeanFactory 中的beanDefinitionMap属性。

invokeBeanFactoryPostProcessors的调用链:

refresh中的invokeBeanFactoryPostProcessors方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.调用AbstractApplicationContext中invokeBeanFactoryPostProcessors:

截屏2021-09-07 下午2.58.24

2.调用PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors方法:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.调用PostProcessorRegistrationDelegate的invokeBeanDefinitionRegistryPostProcessors方法:

截屏2021-09-07 下午3.03.20

4.调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.调用ConfigurationClassPostProcessor的processConfigBeanDefinitions方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截屏2021-09-07 下午3.06.42

6.调用ConfigurationClassParser的parse方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7.调用ConfigurationClassParser的重载parse方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

8.调用ConfigurationClassParser的processConfigurationClass方法:

截屏2021-09-07 下午3.12.27

9.调用ConfigurationClassParser的doProcessConfigurationClass方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截屏2021-09-07 下午3.14.01

doProcessConfigurationClass是重点分析的类

在其方法中有一个的parse(bdCand.getBeanClassName(), holder.getBeanName())的调用,该方法会递归调用doProcessConfigurationClass。因为当Spring扫描到需要加载的类会进一步判断每一个类是否满足是@Component/@Configuration 注解的类, 如果满足会递归调用parse()方法,查找其相关的类。

还有一个processImports(configClass, sourceClass, getImports(sourceClass), true); 该方法是递归处理 通过@Import 注解查找到的类。

因为存在递归,所以在debug的时候会很乱。

@PropertySource

下述代码是获取主类上的@PropertySource注解,解析该注解并将该注解指定的properties配置文件中的值存储到Spring的 Environment中,Environment接口提供方法去读取配置文件中的值,参数是properties文件中定义的key值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

@ComponentScan解析

下述代码是为了解析主类上的@ComponentScan注解,后面的代码将会解析该注解并进行包扫描。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Set<BeanDefinitionHolder> scannedBeanDefinitions =
      this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

上述代码会调用this.componentScanParser.parse解析componentScan,parse方法中有两行代码比较重要:

截屏2021-09-07 下午5.11.09

当basePackages是空的是时候会调用ClassUtils.getPackageName(declaringClass),并把结果添加到basePackages中,而ClassUtils.getPackageName(declaringClass)返回的结果就是主类所在的包。这就是为什么SpringBoot的默认包扫描路径时主类所在的包,因为@ComponentScan注解默认的扫描路径是被注解的类所在包

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

到这里就完成了IOC容器初始化中的Resource定位

doScanfindCandidateComponents方法会把basePackages(在这里是主类所在的包)路径下所有符合条件的类解析成BeanDefinition,在registerBeanDefinition方法中进行注册,所以doScan完成了IOC容器初始化中的BeanDefinition载入BeanDefinition注册

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中findCandidateComponents的调用栈如下:

调用ClassPathScanningCandidateComponentProvider的findCandidateComponents:

截屏2021-09-07 下午6.02.28

调用ClassPathScanningCandidateComponentProvider的scanCandidateComponents:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

scanCandidateComponents会将basePackage拼接成classpath*:xxx/xxx/**/*.class,然后调用getResources(packageSearchPath)方法,扫描该路径下的所有的类。然后遍历这些Resources,判断Resources对应的类是不是@Component 注解标注的类,并且是不需要排除掉的类。然后封装成ScannedGenericBeanDefinition,添加到candidates中返回。

registerBeanDefinition方法如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry)刷新上下文前置处理步骤中注册启动类是一样的

到这里针对SpringBoot中包扫描的定位方式对应的BeanDefinition的定位、加载和注册过程就结束了

@Import解析

下述代码是为了解析主类上的@Import注解,并获取该注解指定的配置类。

截屏2021-09-07 下午4.01.54

其中**getImports(sourceClass)**方法会返回imports,在SpringBoot中会返回两个组件类:AutoConfigurationPackages.Registrar、AutoConfigurationImportSelector,方法如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中collectImports(sourceClass, imports, visited)方法是一个递归方法,他的内部会判断sourceClass类上的注解是否是@Import,如果是就获取value值加入imports中,如果不是就对类上的注解一层层解析,直到找到**@Import**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

processImports方法中会获取到SpringBoot中的AutoConfigurationPackages.Registrar、AutoConfigurationImportSelector类,但是解析这两个类并不是在processImports方法中,是在上游的ConfigurationClassParserd的parse方法中的**this.deferredImportSelectorHandler.process()**中:

截屏2021-09-08 下午3.18.15

对SpringBoot项目来说process方法就是自动装配的入口

process方法调用链如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调用DeferredImportSelectorGroupingHandler的processGroupImports方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调用DeferredImportSelectorGrouping的grouping.getImports()方法:

截屏2021-09-08 下午3.38.12

调用AutoConfigurationImportSelector.AutoConfigurationGroup的process方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里的process方法就是上面@EnableAutoConfiguration中解析@Import(AutoConfigurationImportSelector.class)时的代码

parse方法中解析了SpringBoot使用@Import导入的类,解析出来的是spring.factories中EnableAutoConfiguration对应的类,但是对于这些类的注册是在parse方法的上游ConfigurationClassPostProcessor的processConfigBeanDefinitions方法(org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions内部调用了org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)和this.reader.loadBeanDefinitions(configClasses);)中this.reader.loadBeanDefinitions(configClasses);代码就是为了注册解析出来的类

loadBeanDefinitions方法调用如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调用ConfigurationClassBeanDefinitionReader的loadBeanDefinitionsForConfigurationClass方法:

截屏2021-09-08 下午4.01.21

调用ConfigurationClassBeanDefinitionReader的registerBeanDefinitionForImportedConfigurationClass方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());就是去注册bean

第六步:刷新上下文后置处理

是扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。

自动配置Tomcat

关键代码分析

首先在spring-boot-starter-web中导入了相关的jar包

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后spring.factories中EnableAutoConfiguration有一个配置类:ServletWebServerFactoryAutoConfiguration,该类导入了EmbeddedTomcat

截屏2021-09-08 下午5.40.07

EmbeddedTomcat会向容器中注册一个TomcatServletWebServerFactory工厂对象,其中通过ConditionalOnClass可以看到只有存在Tomcat.class才会进行注册,而spring-boot-starter-web中导入tomcat相关jar包后Tomcat.class是存在的,EmbeddedTomcat代码如下:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中TomcatServletWebServerFactory中有一个getWebServer方法会实例化一个tomcat并在getTomcatWebServer(tomcat)中启动tomcat:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中getTomcatWebServer(tomcat)会创建一个TomcatWebServer,在创建的过程中会启动tomcat:

截屏2021-09-08 下午5.52.04

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

getWebServer调用分析

在SpringBoot中执行refreshContext(context);刷新应用上下文时会调用spring的refresh方法,其中有一个onRefresh方法,onRefresh()会调用到ServletWebServerApplicationContext中的createWebServer()方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

createWebServer中有两行关键代码:

ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());

获取到factory就是TomcatServletWebServerFactory,factory.getWebServer调用的就是上面的TomcatServletWebServerFactory中的getWebServer方法截屏2021-09-08 下午6.17.36

偷一张网上的调用流程图如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自动配置SpringMVC

关键代码分析

首先在spring-boot-starter-web中导入了springMVC相关的jar包

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后spring.factories中EnableAutoConfiguration有一个配置类:DispatcherServletAutoConfiguration

截屏2021-09-08 下午6.42.19

该类能被解析有一些前置条件:

  • @ConditionalOnWebApplication指明该项目必须是一个web项目,且是Servlet项目的时候才会被解析。
  • @ConditionalOnClass指明必须存在DispatcherServlet这个类才会解析该类。
  • @AutoConfigureAfter指明该类要在ServletWebServerFactoryAutoConfiguration类之解析之后再解析。

DispatcherServletAutoConfiguration中有两个比较重要的类:

  • DispatcherServletConfiguration:配置DispatcherServle
  • DispatcherServletRegistrationConfiguration:生成一个Bean,负责将DispatcherServlet给注册到如tomcat这样的ServletContext中,这样才能够提供请求服务

DispatcherServletConfiguration配置类:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • @Conditional指明了一个前置条件判断,由DefaultDispatcherServletCondition实现。主要是判断了是否已经存在DispatcherServlet,如果没有才会触发解析。

  • @ConditionalOnClass指明了当ServletRegistration这个类存在的时候才会触发解析,生成的DispatcherServlet才能注册到ServletContext中。

  • @EnableConfigrationProperties将会从application.properties这样的配置文件中读取spring.http和spring.mvc前缀的属性生成配置对象HttpProperties和WebMvcProperties。

  • 类里面有两个方法,都是注册一个Bean。dispatcherServlet方法将生成一个DispatcherServlet的Bean对象。multipartResolver方法是把自定义的MultipartResolver的Bean给重命名一下,防止自定义时不是用multipartResolver这个名字作为Bean的名字。

DispatcherServletRegistrationConfiguration注册类:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • @Conditional有一个前置判断,DispatcherServletRegistrationCondition判断了该注册类的Bean是否存在。
  • @ConditionOnClass也判断了ServletRegistration是否存在
  • @EnableConfigurationProperties生成了WebMvcProperties的属性对象
  • @Import导入了DispatcherServletConfiguration

DispatcherServletRegistrationConfiguration只有一个方法,生成了DispatcherServletRegistrationBean。

DispatcherServletRegistrationBean负责注册DispatcherServlet,DispatcherServletRegistrationBean的类图如下:

截屏2021-09-09 上午11.50.41

可以看到DispatcherServletRegistrationBean实现了ServletContextInitializer接口,实现该接口是可以初始化ServletContext。ServletContextInitializer接口就一个方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中RegistrationBean类实现了onStartup方法,如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中register是一个抽象方法,在其子类DynamicRegistrationBean中有其实现:

截屏2021-09-09 下午2.20.45

其中addRegistration也是抽象方法,在其子类ServletRegistrationBean也有实现发方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里this.servlet就是在创建DispatcherServletRegistrationBean的时候传入的DispatcherServlet,servletContext.addServlet(name, this.servlet)这行代码会将DispatcherServlet添加到了servletContext当中。

所以只要调用了DispatcherServletRegistrationBean的onStartup就会将DispatcherServlet放入servletContext。

onStartup调用分析

和tomcat一样,进入createWebServer方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

该方法在执行factory.getWebServer之前会进入一个**getSelfInitializer()**方法

截屏2021-09-09 下午3.03.47

其中selfInitialize方法如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到getServletContextInitializerBeans()方法返回的集合中有在前面生成的DispatcherServletRegistrationBean,会调用该类的onStartup方法,这样就向ServletContext中加入了一个 Dispatcherservlet

Servlet3.0规范中这个说明,除了可以动态加Servlet,还可以动态加Listener,Filter,方法名为:

  • addServlet
  • addListener
  • addFilter

第三部分 SpringBoot数据访问简单解析

数据源自动配置

在SpringBoot中默认的连接池是HikariCP

在spring-boot-starter-jdbc中引入了HikariCP:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在spring.factories中有一个org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration的自动配置类

截屏2021-09-09 下午4.41.45

其中和连接池相关的是PooledDataSourceConfiguration类,这个类生效有一个条件就是要设置spring.datasource.type的值,不过SpringBoot会给一个默认值,所以不设置也可以通过这个校验

PooledDataSourceConfiguration类会导入一些连接池相关的类,都是DataSourceConfiguration的内部类,Hikari类如下:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

会通过createDataSource(properties, HikariDataSource.class)创建一个HikariDataSource 的Bean(其实在2.0 之后默认默认使用的就是 hikari 连接池 )

createDataSource方法如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

type方法:

截屏2021-09-09 下午5.20.00

build方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

build方法会调用一个getType方法来确认类型,getType如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到当没有设置type值时会通过调用**findType(this.classLoader)**来返回type,findType如下:

截屏2021-09-09 下午5.21.16

可以看到会取出DATA_SOURCE_TYPE_NAMES中第一个类型返回

而DATA_SOURCE_TYPE_NAMES的值如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所以在type不存在时默认类型为com.zaxxer.hikari.HikariDataSource

在DataSourceConfiguration中还有一个Generic类是用来自定义连接池的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自定义连接池必须设置spring.datasource.type

自动配置Mybatis

Spring Boot官方没有对MyBatis进行整合,但是MyBatis团队自行适配了对应的启动器,所以实现Spring Boot与MyBatis的整合非常简单,主要是引入对应的依赖启动器,并进行数据库相关参数设置即可

<!-- 配置数据库驱动和mybatis dependency --> 

<dependency> 

<groupId>org.mybatis.spring.boot</groupId> 

<artifactId>mybatis-spring-boot-starter</artifactId> 

<version>1.3.2</version> 

</dependency> 
spring:
  datasource:
    username: root
    password: xxx
    url: jdbc:mysql://xxx/xxx?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 使用druid数据源
    type: com.alibaba.druid.pool.DruidDataSource

在SpringBoot启动的时候会加载所有的spring.factories,而Mybatis就在自己的spring.factories中配置了一个自动配置类:截屏2021-09-10 上午10.33.38

在MybatisAutoConfiguration中有几个关键的类和方法:

  • MybatisProperties方法主要是对应mybatis的配置文件
  • sqlSessionFactory方法,作用是创建SqlSessionFactory类、Configuration类 (mybatis最主要的类,保存着与mybatis相关的东西)
  • SqlSessionTemplate,作用是与mapperProoxy代理类有关

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截屏2021-09-10 上午10.46.04

创建sqlSessionFactory

sqlSessionFactory方法创建了SqlSessionFactoryBean 和configuration,然后调用SqlSessionFactoryBean.getObject()返回的对象,将其注册IOC容器中。

getObject方法如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

getObject中有个afterPropertiesSet方法实际上就是调用了MyBatis的初始化流程,所以在执行sqlSessionFactory方法的getObject方法的时候MyBatis就开始了初始化

afterPropertiesSet如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

会调用buildSqlSessionFactory方法:

截屏2021-09-10 下午2.50.10

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截屏2021-09-10 下午3.01.59

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在执行buildSqlSessionFactory方法时会判断this.configLocation != null是否成立,如果成立就代表存在mybatis的核心配置文件,就会解析配置文件:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截屏2021-09-10 下午3.07.43

其中parse方法就是来解析的,parse调用了parseConfiguration方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建SqlSessionFactory对象是在buildSqlSessionFactory方法的最后一行this.sqlSessionFactoryBuilder.build(configuration),该方法会生成一个DefaultSqlSessionFactory对象,如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建sqlSessionTemplate

https://cdn.jsdelivr.net/gh/dingkaige/typora-images/img/20210910104607.png

sqlSessionTemplate是一个实现了SqlSession的类,和DefaultSqlSession不同的是sqlSessionTemplate是一个线程安全的

@MapperScan

只完成初始化还是不够的,还有重要的一步就是生成Mapper接口的代理对象,代理的对象的生成主要是靠**@MapperScan**注解,该注解如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到通过@Import导入了MapperScannerRegistrar,MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,那么在执行spring的invokeBeanFactoryPostProcessors方法时就会调用到registerBeanDefinitions方法

/**
 *    Copyright 2010-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.mybatis.spring.annotation;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of
 * MyBatis mapper scanning. Using an @Enable annotation allows beans to be
 * registered via @Component configuration, whereas implementing
 * {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
 *
 * @author Michael Lanyon
 * @author Eduardo Macarron
 * 
 * @see MapperFactoryBean
 * @see ClassPathMapperScanner
 * @since 1.2.0
 */
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		//拿到MapperScan注解,并解析注解中定义的属性封装成AnnotationAttributes对象
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}

可以看到会读取MapperScan设置的属性并且把value、basePackages、basePackageClasses的值加到basePackages中然后去执行了doScan方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

doScan调用了super.doScan(basePackages);processBeanDefinitions(beanDefinitions);

  • doScan主要是为了把mapper接口封装成BeanDefinitionHolder

  • processBeanDefinitions方法主要会把原BeanDefinition的beanClass类型,修改为MapperFactoryBean,而MapperFactoryBean会在bean生成的时候生成代理对象

doScan

doScan方法在刷新上下文-@ComponentScan解析中解析过。在doScanfindCandidateComponents方法会把basePackages(在这里是mapper)路径下所有符合条件的类解析成BeanDefinition,在registerBeanDefinition方法中进行注册,所以doScan完成了IOC容器初始化中的BeanDefinition载入BeanDefinition注册

但是要注意ClassPathMapperScanner重写了ClassPathScanningCandidateComponentProvider的isCandidateComponent方法,所以mapper接口没有使用@Component注解也能通过isCandidateComponent的校验

截屏2021-09-10 下午4.40.09

processBeanDefinitions

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在processBeanDefinitions中有一行代码:definition.setBeanClass(this.mapperFactoryBean.getClass());会把原BeanDefinition的beanClass类型,修改为MapperFactoryBean,MapperFactoryBean实现了FactoryBean接口,所以在spring进行bean实例化的时候会执行其getObject方法,getObject返回的对象才是被真正的bean对象

getObject如下:

截屏2021-09-10 下午5.11.57

其中**getSqlSession()**返回的对象就是SqlSessionTemplate的实例,然后调用SqlSessionTemplate的getMapper方法,this.mapperInterface就是mapper中的一个个接口

getMapper如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

继续进入getMapper最终的getMapper如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中创建了一个MapperProxyFactory对象,然后调用**mapperProxyFactory.newInstance(sqlSession)**生成了一个代理对象。MapperProxyFactory类如下:

截屏2021-09-10 下午5.18.16

其中MapperProxy类实现了InvocationHandler

mapper接口现在也通过动态代理生成了实现类,并且注入到spring的bean容器中了,之后就可以通过@Autowired或者getBean等方式,从spring容器中获取到对应的对象了

第四部分 SpringBoot缓存简单解析

JSR107介绍

JSR的意思是Java Specification Requests 的缩写 ,Java规范请求,故名思议提交Java规范, JSR-107就是关于如何使用缓存的规范是java提供了一个接口规范,类似于JDBC规范,没有具体的实现,具体的实现就是reids等这些缓存。

Java Caching(JSR-107)定义了5个核心接口,分别是CachingProvider、CacheManager、Cache、Entry和Expiry。

  • CachingProvider(缓存提供者):创建、配置、获取、管理和控制多个CacheManager

  • CacheManager(缓存管理器):创建、配置、获取、管理和控制多个唯一命名的Cache,Cache存在于CacheManager的上下文中。一个CacheManager仅对应一个CachingProvider

  • Cache(缓存):是由CacheManager管理的,CacheManager管理Cache的生命周期,Cache存在于CacheManager的上下文中,是一个类似map的数据结构,并临时存储以key为索引的值。一个Cache仅被一个CacheManager所拥有

  • Entry(缓存键值对):是一个存储在Cache中的key-value对

  • Expiry(缓存时效):每一个存储在Cache中的条目都有一个定义的有效期。一旦超过这个时间,条目就自动过期,过期后,条目将不可以访问、更新和删除操作。缓存有效期可以通过ExpiryPolicy设置

JSR107图示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个应用里面可以有多个缓存提供者(CachingProvider),一个缓存提供者可以获取到多个缓存管理器(CacheManager),一个缓存管理器管理着不同的缓存(Cache),缓存中是一个个的缓存键值对(Entry),每个entry都有一个有效期(Expiry)。缓存管理器和缓存之间的关系有点类似于数据库中连接池和连接的关系。

使用JSR-107需导入的依赖:

<dependency> 
<groupId>javax.cache</groupId> 
<artifactId>cache-api</artifactId> 
</dependency>

Spring的缓存介绍

概述

Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用Java Caching(JSR-107)注解简化我们进行缓存开发。

Spring Cache 只负责维护抽象层,具体的实现由自己的技术选型来决定。将缓存处理和缓存技术解除耦合。

每次调用需要缓存功能的方法时,Spring会检查指定参数的指定的目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

SpringBoot默认开启的缓存管理器是ConcurrentMapCacheManager,创建的缓存组件是ConcurrentMapCache,将缓存数据保存在一个个的ConcurrentHashMap<Object, Object>中。开发时我们可以使用缓存中间件:redis、memcache、ehcache等,这些缓存中间件的启用很简单,只要向容器中加入相关的bean就会启用,可以启用多个缓存中间件

使用Spring缓存抽象时我们需要关注以下两点

  • 确定哪些方法需要被缓存
  • 缓存策略

重要接口

  • Cache:缓存抽象的规范接口,缓存实现有:RedisCache、EhCache、ConcurrentMapCache等

  • CacheManager:缓存管理器,管理Cache的生命周期

概念
概念/注解 作用
Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存。
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略
@Cacheable

@Cacheable注解的属性:

属性 说明
cacheNames/value 指定缓存的名字,缓存使用CacheManager管理多个缓存组件Cache,这些Cache组件就是根据这个名字进行区分的。对缓存的真正CRUD操作在Cache中定义,每个缓存组件Cache都有自己唯一的名字,通过cacheNames或者value属性指定,相当于是将缓存的键值对进行分组,缓存的名字是一个数组,也就是说可以将一个缓存键值对分到多个组里面
key 缓存数据时的key的值,默认是使用方法参数的值,可以使用SpEL表达式计算key的值
keyGenerator 缓存的生成策略,和key二选一,都是生成键的,keyGenerator可自定义
cacheManager 指定缓存管理器(如ConcurrentHashMap、Redis等)
cacheResolver 和cacheManager功能一样,和cacheManager二选一
condition 指定缓存的条件(满足什么条件时才缓存),可用SpEL表达式(如#id>0,表示当入参id大于0时才缓存)
unless 否定缓存,即满足unless指定的条件时,方法的结果不进行缓存,使用unless时可以在调用的方法获取到结果之后再进行判断(如#result==null,表示如果结果为null时不缓存)
sync 是否使用异步模式进行缓存

注:

既满足condition又满足unless条件的也不进行缓存

使用异步模式进行缓存时(sync=true):unless条件将不被支持

可用的SpEL表达式见下表:

名字 位置 描述 示例
methodName root object 当前被调用的方法名 #root.methodName
method root object 当前被调用的方法 #root.method.name
target root object 当前被调用的目标对象 #root.target
targetClass root object 当前被调用的目标对象类 #root.targetClass
args root object 当前被调用的方法的参数列表 #root.args[0]
caches root object 当前方法调用使用的缓存列表(如@Cacheable(value= {“cache1”, “cache2”})),则有两个cache #root.caches[0].name
argument name evaluation context 方法参数的名字,可以直接 #参数名,也可以使用#p0或#a0 #iban、#a0、#p0
result evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效, 如"unless","cache put"的表 达式,"cache evict"的表达式 beforeInvocation=false) #result
@CachePut

既调用方法,又更新缓存数据,一般用于更新操作,在更新缓存时一定要和想更新的缓存有相同的缓存名称和相同的key(可类比同一张表的同一条数据)

运行时机:

  • 先调用目标方法
  • 将目标方法的结果缓存起来

@CachePut标注的方法总会被调用,且调用之后才将结果放入缓存,因此可以使用#result

获取到方法的返回值。

@CacheEvict

缓存清除,清除缓存时要指明缓存的名字和key,相当于告诉数据库要删除哪个表中的哪条数据,key默认为参数的值

allEntries:是否清除指定缓存中的所有键值对,默认为false,设置为true时会清除缓存中的所有键值对,与key属性二选一使用

beforeInvocation:在@CacheEvict注解的方法调用之前清除指定缓存,默认为false,即在方法调用之后清除缓存,设置为true时则会在方法调用之前清除缓存(在方法调用之前还是之后清除缓存的区别在于方法调用时是否会出现异常,若不出现异常,这两种设置没有区别,若出现异常,设置为在方法调用之后清除缓存将不起作用,因为方法调用失败了)

@CacheConfig

作用:标注在类上,抽取缓存相关注解的公共配置,可抽取的公共配置有缓存名字、主键生成器等(如注解中的属性所示)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {

   String[] cacheNames() default {};

   
   String keyGenerator() default "";

   
   String cacheManager() default "";


   String cacheResolver() default "";

}

自动配置缓存源码分析

在spring.factories中有一个自动配置类CacheAutoConfiguration

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CacheAutoConfiguration类导入了一个名为CacheConfigurationImportSelector的内部类:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中返回了十个缓存组件,在默认情况下使用的是 SimpleCacheConfiguration:

截屏2021-09-13 下午4.55.17

在SimpleCacheConfiguration给SpringBoot的IOC容器添加了一个bean,是一个 ConcurrentMapCacheManager对象,ConcurrentMapCacheManager实现了CacheManager接口,其getCache会在执行方法的时候被调用,如下:

@Override
@Nullable
public Cache getCache(String name) {
   Cache cache = this.cacheMap.get(name);
   if (cache == null && this.dynamic) {
      synchronized (this.cacheMap) {
         cache = this.cacheMap.get(name);
         if (cache == null) {
            cache = createConcurrentMapCache(name);
            this.cacheMap.put(name, cache);
         }
      }
   }
   return cache;
}

getCache 方法使用了双重锁校验,如果没有 Cache 会调用cache = this.createConcurrentMapCache(name);生成一个Cache,this.createConcurrentMapCache如下:

protected Cache createConcurrentMapCache(String name) {
   SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
   return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256), isAllowNullValues(), actualSerialization);
}

ConcurrentMapCache有以下三个属性:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中store就对应Entry ,用来存放键值对,在其方法中可以看见操作 Cache 的方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截屏2021-09-13 下午5.07.12

其中lookup就是用来获取value的,put方法是用来保存value的

基于Redis的缓存

当我们引入的starter后,根据自动配置,SpringBoot会在容器中加入redis相关的一些bean,其中有两个跟操作redis相关的:RedisTemplate和StringRedisTemplate(用来操作字符串:key和value都是字符串),template中封装了操作各种数据类型的操作(stringRredisTemplate.opsForValue()、stringRredisTemplate.opsForList()等)

截屏2021-09-13 下午5.19.48

在使用redis存储对象时,会使用RedisCacheConfiguration组件,但是因为SpringBoot默认采用的是JDK的对象序列化方式,所以需要自定义序列化规则,可以使用JSON格式进行对象的序列化操作

其中采用的是JDK的对象序列化方式在源码中如下,标蓝的地方就是证明:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以自定义一个RedisCacheManager,修改序列化规则:

@Configuration
public class RedisConfig {
   @Bean
   public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
      // 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
      RedisSerializer<String> strSerializer = new StringRedisSerializer();
      Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
      // 解决查询缓存转换异常的问题
      ObjectMapper om = new ObjectMapper();
      om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
      om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
      jacksonSeial.setObjectMapper(om);
      // 定制缓存数据序列化方式及时效
      RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(1))
            .serializeKeysWith(RedisSerializationContext
                  .SerializationPair.fromSerializer(strSerializer))
            .serializeValuesWith(RedisSerializationContext
                  .SerializationPair.fromSerializer(jacksonSeial))
            .disableCachingNullValues();
      RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
      return cacheManager;
   }
}