三、Springboot解析
3.1 引入方式
3.1.1 继承父项目
在项目中继承 spring-boot-starter-parent
即可。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${springboot.version}</version>
</parent>
在 spring-boot-starter-parent
项目的内部还继承了另外一个父项目 spring-boot-dependencies
。
3.1.2 依赖管理
通过依赖管理的方式,引入Springboot的依赖 spring-boot-dependencies
。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3.2 版本号
3.2.1 默认版本号
在 spring-boot-starter-parent
的父项目 spring-boot-dependencies
中声明几乎所有开发中常用依赖的版本号。所以在Springboot项目中无需关注版本号,自动版本匹配。
<properties>
<activemq.version>5.16.5</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.97</appengine-sdk.version>
<artemis.version>2.19.1</artemis.version>
<aspectj.version>1.9.7</aspectj.version>
<assertj.version>3.21.0</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.1.1</awaitility.version>
<build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.11.22</byte-buddy.version>
<caffeine.version>2.9.3</caffeine.version>
<cassandra-driver.version>4.13.0</cassandra-driver.version>
<classmate.version>1.5.1</classmate.version>
<commons-codec.version>1.15</commons-codec.version>
<commons-dbcp2.version>2.9.0</commons-dbcp2.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<commons-pool.version>1.6</commons-pool.version>
<commons-pool2.version>2.11.1</commons-pool2.version>
<couchbase-client.version>3.2.7</couchbase-client.version>
<db2-jdbc.version>11.5.7.0</db2-jdbc.version>
<dependency-management-plugin.version>1.0.11.RELEASE</dependency-management-plugin.version>
<derby.version>10.14.2.0</derby.version>
<dropwizard-metrics.version>4.2.10</dropwizard-metrics.version>
<ehcache.version>2.10.9.2</ehcache.version>
<ehcache3.version>3.9.9</ehcache3.version>
<elasticsearch.version>7.15.2</elasticsearch.version>
<embedded-mongo.version>3.0.0</embedded-mongo.version>
<flyway.version>8.0.5</flyway.version>
<freemarker.version>2.3.31</freemarker.version>
<git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version>
<glassfish-el.version>3.0.4</glassfish-el.version>
<glassfish-jaxb.version>2.3.6</glassfish-jaxb.version>
<glassfish-jstl.version>1.2.6</glassfish-jstl.version>
<groovy.version>3.0.11</groovy.version>
<gson.version>2.8.9</gson.version>
<h2.version>1.4.200</h2.version>
<hamcrest.version>2.2</hamcrest.version>
<hazelcast.version>4.2.5</hazelcast.version>
<hazelcast-hibernate5.version>2.2.1</hazelcast-hibernate5.version>
<hibernate.version>5.6.9.Final</hibernate.version>
<hibernate-validator.version>6.2.3.Final</hibernate-validator.version>
<hikaricp.version>4.0.3</hikaricp.version>
<hsqldb.version>2.5.2</hsqldb.version>
<htmlunit.version>2.54.0</htmlunit.version>
<httpasyncclient.version>4.1.5</httpasyncclient.version>
<httpclient.version>4.5.13</httpclient.version>
<httpclient5.version>5.1.3</httpclient5.version>
<httpcore.version>4.4.15</httpcore.version>
<httpcore5.version>5.1.3</httpcore5.version>
<infinispan.version>12.1.12.Final</infinispan.version>
<influxdb-java.version>2.22</influxdb-java.version>
<jackson-bom.version>2.13.3</jackson-bom.version>
<jakarta-activation.version>1.2.2</jakarta-activation.version>
<jakarta-annotation.version>1.3.5</jakarta-annotation.version>
<jakarta-jms.version>2.0.3</jakarta-jms.version>
<jakarta-json.version>1.1.6</jakarta-json.version>
<jakarta-json-bind.version>1.0.2</jakarta-json-bind.version>
<jakarta-mail.version>1.6.7</jakarta-mail.version>
<jakarta-management.version>1.1.4</jakarta-management.version>
<jakarta-persistence.version>2.2.3</jakarta-persistence.version>
<jakarta-servlet.version>4.0.4</jakarta-servlet.version>
<jakarta-servlet-jsp-jstl.version>1.2.7</jakarta-servlet-jsp-jstl.version>
<jakarta-transaction.version>1.3.3</jakarta-transaction.version>
<jakarta-validation.version>2.0.2</jakarta-validation.version>
<jakarta-websocket.version>1.1.2</jakarta-websocket.version>
<jakarta-ws-rs.version>2.1.6</jakarta-ws-rs.version>
<jakarta-xml-bind.version>2.3.3</jakarta-xml-bind.version>
<jakarta-xml-soap.version>1.4.2</jakarta-xml-soap.version>
<jakarta-xml-ws.version>2.3.3</jakarta-xml-ws.version>
<janino.version>3.1.7</janino.version>
<javax-activation.version>1.2.0</javax-activation.version>
<javax-annotation.version>1.3.2</javax-annotation.version>
<javax-cache.version>1.1.1</javax-cache.version>
<javax-jaxb.version>2.3.1</javax-jaxb.version>
<javax-jaxws.version>2.3.1</javax-jaxws.version>
<javax-jms.version>2.0.1</javax-jms.version>
<javax-json.version>1.1.4</javax-json.version>
<javax-jsonb.version>1.0</javax-jsonb.version>
<javax-mail.version>1.6.2</javax-mail.version>
<javax-money.version>1.1</javax-money.version>
<javax-persistence.version>2.2</javax-persistence.version>
<javax-transaction.version>1.3</javax-transaction.version>
<javax-validation.version>2.0.1.Final</javax-validation.version>
<javax-websocket.version>1.1</javax-websocket.version>
<jaxen.version>1.2.0</jaxen.version>
<jaybird.version>4.0.6.java8</jaybird.version>
<jboss-logging.version>3.4.3.Final</jboss-logging.version>
<jdom2.version>2.0.6.1</jdom2.version>
<jedis.version>3.7.1</jedis.version>
<jersey.version>2.35</jersey.version>
<jetty-el.version>9.0.52</jetty-el.version>
<jetty-jsp.version>2.2.0.v201112011158</jetty-jsp.version>
<jetty-reactive-httpclient.version>1.1.11</jetty-reactive-httpclient.version>
<jetty.version>9.4.48.v20220622</jetty.version>
<jmustache.version>1.15</jmustache.version>
<johnzon.version>1.2.18</johnzon.version>
<jolokia.version>1.7.1</jolokia.version>
<jooq.version>3.14.16</jooq.version>
<json-path.version>2.6.0</json-path.version>
<json-smart.version>2.4.8</json-smart.version>
<jsonassert.version>1.5.0</jsonassert.version>
<jstl.version>1.2</jstl.version>
<jtds.version>1.3.1</jtds.version>
<junit.version>4.13.2</junit.version>
<junit-jupiter.version>5.8.2</junit-jupiter.version>
<kafka.version>3.0.1</kafka.version>
<kotlin.version>1.6.21</kotlin.version>
<kotlin-coroutines.version>1.5.2</kotlin-coroutines.version>
<lettuce.version>6.1.8.RELEASE</lettuce.version>
<liquibase.version>4.5.0</liquibase.version>
<log4j2.version>2.17.2</log4j2.version>
<logback.version>1.2.11</logback.version>
<lombok.version>1.18.24</lombok.version>
<mariadb.version>2.7.5</mariadb.version>
<maven-antrun-plugin.version>3.0.0</maven-antrun-plugin.version>
<maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
<maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-dependency-plugin.version>3.2.0</maven-dependency-plugin.version>
<maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version>
<maven-enforcer-plugin.version>3.0.0</maven-enforcer-plugin.version>
<maven-failsafe-plugin.version>2.22.2</maven-failsafe-plugin.version>
<maven-help-plugin.version>3.2.0</maven-help-plugin.version>
<maven-install-plugin.version>2.5.2</maven-install-plugin.version>
<maven-invoker-plugin.version>3.2.2</maven-invoker-plugin.version>
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<maven-javadoc-plugin.version>3.3.2</maven-javadoc-plugin.version>
<maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<maven-war-plugin.version>3.3.2</maven-war-plugin.version>
<micrometer.version>1.8.7</micrometer.version>
<mimepull.version>1.9.15</mimepull.version>
<mockito.version>4.0.0</mockito.version>
<mongodb.version>4.4.2</mongodb.version>
<mssql-jdbc.version>9.4.1.jre8</mssql-jdbc.version>
<mysql.version>8.0.29</mysql.version>
<nekohtml.version>1.9.22</nekohtml.version>
<neo4j-java-driver.version>4.4.6</neo4j-java-driver.version>
<netty.version>4.1.78.Final</netty.version>
<netty-tcnative.version>2.0.53.Final</netty-tcnative.version>
<okhttp3.version>3.14.9</okhttp3.version>
<oracle-database.version>21.3.0.0</oracle-database.version>
<pooled-jms.version>1.2.4</pooled-jms.version>
<postgresql.version>42.3.6</postgresql.version>
<prometheus-client.version>0.12.0</prometheus-client.version>
<quartz.version>2.3.2</quartz.version>
<querydsl.version>5.0.0</querydsl.version>
<r2dbc-bom.version>Arabba-SR13</r2dbc-bom.version>
<rabbit-amqp-client.version>5.13.1</rabbit-amqp-client.version>
<rabbit-stream-client.version>0.4.0</rabbit-stream-client.version>
<reactive-streams.version>1.0.4</reactive-streams.version>
<reactor-bom.version>2020.0.20</reactor-bom.version>
<rest-assured.version>4.4.0</rest-assured.version>
<rsocket.version>1.1.2</rsocket.version>
<rxjava.version>1.3.8</rxjava.version>
<rxjava-adapter.version>1.2.1</rxjava-adapter.version>
<rxjava2.version>2.2.21</rxjava2.version>
<saaj-impl.version>1.5.3</saaj-impl.version>
<selenium.version>3.141.59</selenium.version>
<selenium-htmlunit.version>2.54.0</selenium-htmlunit.version>
<sendgrid.version>4.7.6</sendgrid.version>
<servlet-api.version>4.0.1</servlet-api.version>
<slf4j.version>1.7.36</slf4j.version>
<snakeyaml.version>1.29</snakeyaml.version>
<solr.version>8.8.2</solr.version>
<spring-amqp.version>2.4.6</spring-amqp.version>
<spring-batch.version>4.3.6</spring-batch.version>
<spring-data-bom.version>2021.1.5</spring-data-bom.version>
<spring-framework.version>5.3.21</spring-framework.version>
<spring-hateoas.version>1.4.4</spring-hateoas.version>
<spring-integration.version>5.5.13</spring-integration.version>
<spring-kafka.version>2.8.7</spring-kafka.version>
<spring-ldap.version>2.3.8.RELEASE</spring-ldap.version>
<spring-restdocs.version>2.0.6.RELEASE</spring-restdocs.version>
<spring-retry.version>1.3.3</spring-retry.version>
<spring-security.version>5.6.6</spring-security.version>
<spring-session-bom.version>2021.1.3</spring-session-bom.version>
<spring-ws.version>3.1.3</spring-ws.version>
<sqlite-jdbc.version>3.36.0.3</sqlite-jdbc.version>
<sun-mail.version>1.6.7</sun-mail.version>
<thymeleaf.version>3.0.15.RELEASE</thymeleaf.version>
<thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-data-attribute.version>
<thymeleaf-extras-java8time.version>3.0.4.RELEASE</thymeleaf-extras-java8time.version>
<thymeleaf-extras-springsecurity.version>3.0.4.RELEASE</thymeleaf-extras-springsecurity.version>
<thymeleaf-layout-dialect.version>3.0.0</thymeleaf-layout-dialect.version>
<tomcat.version>9.0.64</tomcat.version>
<unboundid-ldapsdk.version>4.0.14</unboundid-ldapsdk.version>
<undertow.version>2.2.18.Final</undertow.version>
<versions-maven-plugin.version>2.8.1</versions-maven-plugin.version>
<webjars-locator-core.version>0.48</webjars-locator-core.version>
<wsdl4j.version>1.6.3</wsdl4j.version>
<xml-maven-plugin.version>1.0.2</xml-maven-plugin.version>
<xmlunit2.version>2.8.4</xmlunit2.version>
</properties>
所以由上面的pom文件的依赖关系可以得出结论:
在
dependencies
中存在的依赖,我们导入项目时,依赖默认是不需要写版本号的;而没有在dependencies
里面管理的依赖自然需要声明版本号。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
3.2.2 修改默认版本号
在当前项目中引入如下配置覆盖父项目中配置,即可修改mysql的版本号。
<properties>
<mysql.version>5.1.49</mysql.version>
</properties>
3.3 启动器
什么是Springboot 项目的Starters?
**Springboot将所有的功能场景都抽取出来,做成一个个的Starters(启动器),只需要在项目里面引入这些Starters相关场景的所有依赖都会导入进来。**要用什么功能就导入什么场景的启动器。
官方的所有 starter 都遵循类似的命名规则:spring-boot-starter-*
,其中 *
是特定类型的应用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件。
第三方的 starter 命名不应该以 spring-boot
开头,因为它是官方 Spring Boot 构件所保留的规则。例如,有一个第三方 starter 项目叫做 thirdpartyproject
,它通常会命名为 thirdpartyproject-spring-boot-starter
。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
所有场景启动器最底层的依赖 spring-boot-starter
。
3.4 自动配置
启动器包含两大核心:
- 导入模块相关依赖;
- 自动化配置;
自动化配置是Spring boot的核心功能之一,正是它消除或者说减少了我们在开发spring应用时所需要的一大堆和业务无关的配置。
而它背后的理念并不新鲜,它使用的是叫做约定优于配置(convention over configuration)的原则,它预先做出一些合理的假设(也就是约定),只要你遵循它的约定,就不需要做出额外的配置,便可以直接使用它提供的功能,从而消除了显式的配置。例如maven会假设源代码目录默认就是项目根目录下的src/main/java目录,如果你遵循这个目录结构,便不需要告诉maven源代码目录是什么。这个原则经常被各种框架使用,因为框架往往根据这些约定做出了相应的配置,如果这些配置符合我们的需求,我们就什么也不用做,只需要欣然的接受并使用就可以了,只有当这些配置不符合我们的需求时,才需要我们做出调整。
接下来以spring-boot-starter-web为例,来看一下都做了哪些自动化配置。
3.4.1 Tomcat容器
- 引入内置Tomcat依赖;
- 配置Tomcat;
3.4.2 SpringMVC
- 引入SpringMVC全套组件;
- 配置SpringMVC常用组件;
- 前端控制器dispatcherServlet
- 处理器映射器requestMappingHandlerMapping
- 处理器适配器requestMappingHandlerAdapter
- 视图解析器viewResolver
- …
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Springboot02BootApplication {
public static void main(String[] args) {
//获取SpringIOC容器,ConfigurableApplicationContext是ApplicationContext接口的子接口
ConfigurableApplicationContext context = SpringApplication.run(Springboot02BootApplication.class, args);
//获取Spring容器中所有Bean的名称
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
}
}
3.4.3 Web功能
- 引入Web常见功能;
- 配置Web功能:
- 上传multipartResolver
- JSON处理mappingJackson2HttpMessageConverter
- 异常处理handlerExceptionResolver
- 后台数据校验mvcValidator
- 字符编码过滤器characterEncodingFilter
- …
3.4.4 组件扫描
无需配置组件扫描,默认的组件扫描包为启动类所在包,表示启动类所在包及其下面的所有子包里面的组件都会被默认扫描进来。
如果需要改变扫描包,在启动类@SpringBootApplication注解中添加scanBasePackages配置项即可,或者通过@ComponentScan注解指定扫描路径。
@SpringBootApplication(scanBasePackages = "com.newcapec")
3.4.5 配置默认值
默认配置最终都是映射到某个类上,如:DataSourceProperties。
3.4.6 按需加载自动配置项
Springboot中有很多的启动器starter,每个启动器中都带有自动配置项,所有的自动配置功能都在 spring-boot-autoconfigure 包里面。
注: Springboot启动时并不是加载所有自动配置项,而是会根据引入的启动器开启相应的自动配置项。
3.5 Springboot容器
3.5.1 @Configuration+@Bean
在Springboot项目中通过@Configuration和@Bean注解进行Bean的配置。
3.5.1.1 Bean类
Customers类:
public class Customers {
private String name;
private Integer age;
public Customers() {
}
public Customers(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Customers{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Orders类:
public class Orders {
private String name;
private Customers customers;
public Orders() {
}
public Orders(String name) {
this.name = name;
}
public Orders(String name, Customers customers) {
this.name = name;
this.customers = customers;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Customers getCustomers() {
return customers;
}
public void setCustomers(Customers customers) {
this.customers = customers;
}
@Override
public String toString() {
return "Orders{" +
"name='" + name + '\'' +
", customers=" + customers +
'}';
}
}
3.5.1.2 配置类
@Configuration
public class MyConfig {
@Bean
public Customers getCustomers(){
return new Customers("Tom", 20);
}
@Bean
public Orders getOrders(){
//在@Configuration注解的配置类中,如果直接调用@Bean标注的方法,相当于从IOC容器中获取该bean并依赖注入
return new Orders("Tom's Order", getCustomers());
}
}
3.5.1.3 测试
public static void main(String[] args) {
//获取SpringIOC容器,ConfigurableApplicationContext是ApplicationContext接口的子接口
ConfigurableApplicationContext context = SpringApplication.run(Springboot02BootApplication.class, args);
//获取Spring中加载的Bean
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
Customers customers = context.getBean(Customers.class);
System.out.println(customers);
Orders orders = context.getBean(Orders.class);
System.out.println(orders);
System.out.println("是否单例:" + (customers == orders.getCustomers()));
}
- @Bean注解的方法名称为默认的Bean名称,@Bean的value配置项可修改Bean的名称。
- 配置类中使用@Bean标注在方法上给容器注册组件,默认也是单实例的。
- @Configuration注解的proxyBeanMethods配置项为false时,Bean不为单例模式,默认值为true。
3.5.2 @Import
- @Import注解通过快速导入的方式实现把实例加入Spring的IOC容器中。
- @Import可以用于导入第三方包,当然@Bean注解也可以,但是@Import注解快速导入的方式更加便捷。
- @Import的三种用法主要包括:
- Class数组方式;
- ImportSelector方式;
- ImportBeanDefinitionRegistrar方式;
3.5.2.1 Class数组
直接填写class数组,Class数组可以有0到多个。
Bean类:
public class Dog {
}
public class Cat {
}
在配置类中导入:
@Configuration
@Import({
Dog.class, Cat.class})
public class MyConfig {
@Bean
public Customers getCustomers(){
return new Customers("Tom", 20);
}
@Bean
public Orders getOrders(){
return new Orders("Tom's Order", getCustomers());
}
}
对应的import的bean都将加入到spring容器中,这些在容器中bean名称是该类的全类名。
3.5.2.2 ImportSelector
通过@Import注解导入的是ImportSelector接口的实现类,在其抽象方法selectImports中定义需要注册的Bean对象。
Bean类:
public class Student {
}
public class Teacher {
}
ImportSelector接口的实现类:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
"com.newcapec.bean.Student", "com.newcapec.bean.Teacher"};
}
}
ImportSelector接口的selectImports方法:
- 返回值String[]:表示注册到容器中的组件全类名的字符串数组。
- 参数AnnotationMetadata:表示当前被@Import注解给标注的所有注解信息。
在配置类中导入:
@Configuration
@Import({
Dog.class, Cat.class, MyImportSelector.class})
public class MyConfig {
@Bean
public Customers getCustomers(){
return new Customers("Tom", 20);
}
@Bean
public Orders getOrders(){
return new Orders("Tom's Order", getCustomers());
}
}
需要注意的是selectImports方法可以返回空数组但是不能返回null,否则会报空指针异常。
3.5.2.3 ImportBeanDefinitionRegistrar
通过@Import注解导入的是ImportBeanDefinitionRegistrar接口的实现类,在其抽象方法registerBeanDefinitions中定义需要注册的Bean对象。与ImportSelector用法类似,只不过这种用法可自定义注册Bean。
Bean类:
public class Users {
}
ImportBeanDefinitionRegistrar接口的实现类:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//指定Bean定义信息
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Users.class);
//注册一个Bean指定bean名称
registry.registerBeanDefinition("users", rootBeanDefinition);
}
}
ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法:
- 参数AnnotationMetadata:表示当前被@Import注解给标注的所有注解信息。
- 参数BeanDefinitionRegistry:表示用于注册定义一个Bean。
在配置类中导入:
@Configuration
@Import({
Dog.class, Cat.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MyConfig {
@Bean
public Customers getCustomers(){
return new Customers("Tom", 20);
}
@Bean
public Orders getOrders(){
return new Orders("Tom's Order", getCustomers());
}
}
3.5.3 @Conditional
@Conditional注解可以用在任何类型或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理。
3.5.3.1 派生注解
3.5.3.2 用法解析
@Conditional派生注解 | 作用(都是判断是否符合指定的条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 有指定的Bean类 |
@ConditionalOnMissingBean | 没有指定的bean类 |
@ConditionalOnExpression | 符合指定的SpEL表达式 |
@ConditionalOnClass | 有指定的类 |
@ConditionalOnMissingClass | 没有指定的类 |
@ConditionalOnSingleCandidate | 容器只有一个指定的bean,或者这个bean是首选bean |
@ConditionalOnProperty | 指定的property属性有指定的值 |
@ConditionalOnResource | 路径下存在指定的资源 |
@ConditionalOnWebApplication | 系统环境是web环境 |
@ConditionalOnNotWebApplication | 系统环境不是web环境 |
@ConditionalOnjndi | JNDI存在指定的项 |
3.5.3.3 案例
Bean类:
public class Color {
}
public class Red {
}
public class Green {
}
配置类:
@Configuration
@Import({
Dog.class, Cat.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MyConfig {
@Bean
public Customers getCustomers(){
return new Customers("Tom", 20);
}
@Bean
public Orders getOrders(){
return new Orders("Tom's Order", getCustomers());
}
@Bean("color")
public Color getColor(){
return new Color();
}
@ConditionalOnBean(name = "color")
@Bean("red")
public Red getRed(){
return new Red();
}
@ConditionalOnMissingBean(name = "color")
@Bean("green")
public Green getGreen(){
return new Green();
}
}
测试:
public static void main(String[] args) {
//获取SpringIOC容器,ConfigurableApplicationContext是ApplicationContext接口的子接口
ConfigurableApplicationContext context = SpringApplication.run(Springboot02BootApplication.class, args);
//获取Spring中加载的Bean
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
Customers customers = context.getBean(Customers.class);
System.out.println(customers);
Orders orders = context.getBean(Orders.class);
System.out.println(orders);
System.out.println("是否单例:" + (customers == orders.getCustomers()));
boolean color = context.containsBean("color");
System.out.println("容器中是否包含color组件:" + color);
boolean red = context.containsBean("red");
System.out.println("容器中是否包含red组件:" + red);
boolean green = context.containsBean("green");
System.out.println("容器中是否包含green组件:" + green);
}
color Bean存在时:
容器中是否包含color组件:true
容器中是否包含red组件:true
容器中是否包含green组件:false
color Bean不存在时:
容器中是否包含color组件:false
容器中是否包含red组件:false
容器中是否包含green组件:true
3.6 Springboot启动解析
3.6.1 主程序
在一个普通的Java类上面添加一个注解@SpringBootApplication,将自动识别为一个Springboot的应用程序,该类中的main方法也是Springboot程序的入口方法。
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
3.6.2 @SpringBootApplication
SpringBoot程序的主程序注解,当前类也是Springboot的主配置类。
@SpringBootApplication注解的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
这里又涉及到三个注解@SpringBootConfiguration、@ComponentScan和@EnableAutoConfiguration。
3.6.3 @SpringBootConfiguration
@SpringBootConfiguration是Springboot程序的配置类注解。
@SpringBootConfiguration注解的源码:
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
}
@Configuration注解的源码:
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
}
分析:
- @Configuration注解是Spring框架提供的注解,用来标识一个类为配置类。
- @SpringBootConfiguration是Springboot提供的注解,表示这是一个Springboot的配置类。
- 配置类也是容器中的一个组件,就是@Component注解的作用。
3.6.4 @ComponentScan
@ComponentScan注解的源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
}
分析:
- 指定扫描包,并扫描包下的类以及其子包。
- @Repeatable(ComponentScans.class):表示在没有@Repeatable标注的注解中,在同一个地方使用相同的注解会报错,有了此元注解标注的注解,就可以在同一个地方使用相同的注解。@ComponentScan注解可以在@ComponentScans注解中使用多次。
3.6.5 @EnableAutoConfiguration
开启自动配置功能。
之前Spring的时代,需要配置的xml内容;现在Springboot帮我们自动配置,@EnableAutoConfiguration告诉SpringBoot开启自动配置功能。
@EnableAutoConfiguration注解的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
这里又涉及到两个注解@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)。
3.6.6 @AutoConfigurationPackage
自动配置包,指定了默认的包规则。
@AutoConfigurationPackage注解的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
这里又涉及到@Import(AutoConfigurationPackages.Registrar.class)注解。
3.6.7 @Import(AutoConfigurationPackages.Registrar.class)
使用@Import注解导入一个AutoConfigurationPackages.Registrar的类,给容器中注册一系列组件。
AutoConfigurationPackages.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));
}
}
利用断点调试功能,可得知使用@SpringBootApplication注解标识的类所在的包以及所在子包下的所有类,自动扫描注册到Spring容器中。跟之前学习Spring时,使用的xml文件中,组件扫描基础包的功能一样。
3.6.8 @Import(AutoConfigurationImportSelector.class)
使用@Import注解导入一个AutoConfigurationImportSelector的类,给容器中注册一系列组件。
AutoConfigurationImportSelector的部分源码:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
}
3.6.8.1 selectImports方法
在源码99行,利用getAutoConfigurationEntry(annotationMetadata);
给容器中批量导入一些组件。
3.6.8.2 getAutoConfigurationEntry方法
在源码123行,调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
获取到所有准备导入到容器中的组件。这些组件就是自动配置类,就是给容器中导入某个场景(Starter)需要的所有组件,并配置好这些组件。有了自动配置类,免去了我们手动编写配置注入功能组件等的工作。
3.6.8.3 工厂加载器
在源码178行,利用工厂加载SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
获取到所有组件。
3.6.8.4 spring.factories文件
SpringFactoriesLoader类中的loadFactoryNames()方法源码:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
查看源码得知Springboot默认加载了类路径下的META-INF/spring.factories文件。
在spring-boot-autoconfigure-2.6.9.jar包中找到META-INF/spring.factories文件。
从25行到158行总共133个准备导入的组件,与调试中展示的个数相符。
3.6.8.5 按需开启自动配置
虽然默认加载130个自动配置,但Springboot仍会按照条件装配规则@Conditional,最终按需配置。当代码执行到129行时,我们可以看到需要导入的自动配置类仅剩24个。
3.6.8.6 修改默认配置
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
- Springboot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先。
- @Bean标注方法的形参可在容器中查找组件。
3.6.8.5 示例1
随便找一个看看,这里看的是RedisAutoConfiguration,看看redis是如何配置的
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({
LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
分析:
- @Configuration:表示这个类是一个配置类。
- @ConditionalOnClass(RedisOperations.class):表示RedisOperations类存在则启用这个配置。
- @EnableConfigurationProperties(RedisProperties.class) :RedisProperties这个类中初始化了redis的一些配置参数。在yml或者properties中写的配置就是这个类的对应的逻辑。
- @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) :表示导入redis连接的配置。
3.6.8.6 示例2
这里看的是DispatcherServletAutoConfiguration,看看DispatcherServlet是如何配置的
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
/**
* The bean name for a DispatcherServlet that will be mapped to the root URL "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/**
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
}
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
}
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DefaultDispatcherServletCondition extends SpringBootCondition {
}
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DispatcherServletRegistrationCondition extends SpringBootCondition {
}
}
分析:
@Configuration
配置类它也是一个Bean,但对于配置类来说,某些场景下的执行顺序是必须的,是需要得到保证的。Spring Boot下对自动配置的管理对比于Spring它就是黑盒,它会根据当前容器内的情况来动态的判断自动配置类的载入与否、以及载入的顺序,所以可以说:Spring Boot的自动配置它对顺序是有强要求的。需求驱使,Spring Boot给我们提供了@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder(下面统称这三个注解为“三大注解”)这三个注解来帮我们解决这种诉求。@AutoConfigureOrder
属于1.3.0版本新增,表示绝对顺序(数字越小,优先顺序越高)。@AutoConfigureBefore
、@AutoConfigureAfter
来控制配置的顺序,表示在某种配置之后或之前载入;DispatcherServletAutoConfiguration
被载入的前提是:ServletWebServerFactoryAutoConfiguration
已经完成初始化。- 在DispatcherServletAutoConfiguration定义了四个内部类,只有DispatcherServletAutoConfiguration配置生效,才会判断内部类配置条件。
总结:
- Springboot先加载所有的自动配置类xxxxxAutoConfiguration。
- 每个自动配置类按照条件进行生效。默认都会绑定配置文件指定的值,从xxxxProperties类中获取,xxxProperties和配置文件进行了绑定。
- 生效的配置类就会给容器中装配很多组件,只要容器中有这些组件,相当于这些功能就有了。
- 定制化配置:
- 用户直接自己@Bean替换底层的组件。
- 用户去看这个组件是获取的配置文件什么值就去修改。