关注「Java视界」公众号,获取更多技术干货

Spring Boot 自动配置原理及自定义Starter

一、Spring Boot 是怎么完成自动配置的?

初识Spring Boot时我们就知道,Spring Boot有一个全局配置文件:application.properties或application.yml。这些配置是如何在Spring Boot项目中生效的呢?

Spring Boot关于自动配置的源码在spring-boot-autoconfigure-x.x.x.x.jar中:

自动配置始于Spring Boot的核心注解:@SpringBootApplication  ,它在主函数所在的类中,是个复合注解:

这里面   @SpringBootConfiguration   @EnableAutoConfiguration   @ComponentScan 是比较重要的注解,但 @EnableAutoConfiguration  是和自动配置关系最紧密的注解。

@EnableAutoConfiguration 也是一个复合注解:

@EnableAutoConfiguration 的关键功能由@Import提供,原因是它导入了一个很重要的类:AutoConfigurationImportSelector,这个类提供了一个重要的方法 selectImports():

selectImports()中 getCandidateConfigurations()是个重要的方法,它通过SpringFactoriesLoader.loadFactoryNames()扫描所有META-INF/spring.factories具有的jar包:

spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件:

这个spring.factories文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些xxxxAutoConfiguration就是自动配置类,比如这个文件中可以找到RedisAutoConfiguration、RabbitAutoConfiguration等自动配置类。

Spring Boot启动类(main方法所在类)在启动的时候,就会执行run(...)方法,run()方法的内部就会执行selectImports()方法把所有的自动配置类加载到Spring容器。

每一个XxxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的条件注解有如下几项(在autoconfigure.condition包下还有很多,下面简单列举几个):

  • @ConditionalOnBean:当容器里有指定的bean的条件下。
  • @ConditionalOnMissingBean:当容器里不存在指定bean的条件下。
  • @ConditionalOnClass:当类路径下有指定类的条件下。
  • @ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
  • @ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。

application.yml文件中的配置是怎么生效的?比如 spring.rabbitmq.port=5672 这个配置项是怎么生效的,我们可以看下RabbitAutoConfiguration类:

在RabbitAutoConfiguration上面有个 @EnableConfigurationProperties 注解,它的作用是开启配置属性,让 @ConfigurationProperties 注解的properties类生效并使用。如让RabbitProperties生效并注册到Spring容器中,并且使用!

上面 @EnableConfigurationProperties 注解后面有一个 RabbitProperties 类:

RabbitProperties类上面就是由 @ConfigurationProperties 注解加持的,@ConfigurationProperties 和 @EnableConfigurationProperties 结合使用,@ConfigurationProperties的作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties负责导入这个已经绑定了属性的bean到spring容器中。那么所有其他的和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些XxxxProperties类,它与配置文件中定义的prefix关键字开头的一组属性是唯一对应的。在全局配置的属性如:spring.rabbitmq.port=5672 等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。

而诸多的XxxxAutoConfiguration自动配置类,就是Spring容器的JavaConfig形式,作用就是为Spring 容器导入bean,而所有导入的bean所需要的属性都通过xxxxProperties的bean来获得。

回头再看RabbitAutoConfiguration类就能明白上面的这句话的含义,如下RabbitAutoConfiguration会在Spring容器中返回template这个Bean,这个Bean的属性已经从RabbitProperties的bean中获得(RabbitProperties的bean的属性通过@ConfigurationProperties 注解由application.yml文件中取得),但是仔细看template这个bean的上面还有一个@ConditionalOnMissingBean 注解,即当容器里不存在指定bean的条件下这个自动配置才会生效,注册该bean到spring容器,@ConditionalXxx 和 全局配置文件application.yml的结合可以实现灵活的配置。

总结:

  1. XxxxProperties类的含义是:封装配置文件中相关属性;XxxxAutoConfiguration类的含义是:自动配置类,目的是给容器中添加组件。而其他的主方法启动,则是为了加载这些五花八门的XxxxAutoConfiguration类。
  2. 如何用好自动配置:1)SpringBoot启动会加载大量的自动配置类2)我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;3)我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)4)给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值。

 二、Spring Boot 自动配置原理给我们的启示

上面的自动配置过程是搞明白了,但是这个能够给我们带来什么呢?

那就是我们也可以仿照这个自动配置过程完成自己的starter并实现自动配置。

2.1 Spring Boot中的 Starter

Spring Boot 的便利性体现在,它简化了很多烦琐的配置,这对于开发人员来说是一个福音,通过引入各种 Spring Boot Starter 包可以快速搭建出一个项目的脚手架。目前提供的 Spring Boot Starter 包有:

  • spring-boot-starter-web:快速构建基于 Spring MVC 的 Web 项目,使用 Tomcat 做默认嵌入式容器。
  • spring-boot-starter-data-redis:操作 Redis
  • spring-boot-starter-data-mongodb:操作 Mongodb。
  • spring-boot-starter-data-jpa:操作 Mysql。
  • spring-boot-starter-activemq:操作 Activemq。
  • ……

自动配置非常方便,当我们要操作 Mongodb 的时候,只需要引入 spring-boot-starter-data-mongodb 的依赖,然后配置 Mongodb 的链接信息 spring.data.mongodb.uri=mongodb://localhost/test 就可以使用 MongoTemplate 来操作数据,MongoTemplate 的初始化工作全部交给 Starter 来完成。

结合上面的自动配置过程我们也可以实现自己的 Starter:

(1)创建一个项目 spring-boot-starter-demo,pom.xml 配置代码如下

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

(2)创建一个配置类,用于在属性文件中配置值,相当于 RabbitProperties 类,代码如下所示:

@Data
@ConfigurationProperties("spring.user")
public class UserPorperties {
    private String name;
}

通过 @ConfigurationProperties("spring.user") 注解可以绑定application.yml配置文件中 spring.rabbitmq.port=5672 这种形式的配置,这里"spring.user"就是会识别spring.user.XXX这样的配置。

 (3)再定义一个 Client,相当于 RabbitTemplate,里面定一个方法,用于获取配置中的值,代码如下所示:

public class UserClient {
    private UserPorperties userPorperties;
    public UserClient() {
    }
    public UserClient(UserPorperties p) {
        this.userPorperties = p;
    }
    public String getName() {
        return userPorperties.getName();
    }
}

以上,一个基本的 Starter 就已经创建好了。

(4)一个最基本的 Starter 包定义好了,但目前肯定是不能使用 UserClient,因为我们没有自动构建 UserClient 的实例。接下来开始构建 UserClient,代码如下所示:

@Configuration
@EnableConfigurationProperties(UserPorperties.class)
public class UserAutoConfiguration {
    @Bean
    @ConditionalOnProperty(prefix = "spring.user", value = "enabled", havingValue = "true")
    public UserClient userClient(UserPorperties userPorperties) {
        return new UserClient(userPorperties);
    }
}

@ConfigurationProperties的作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties负责导入这个已经绑定了属性的bean到spring容器中。

(5)在 resources /META-INF/spring.factories 文件中添加自动配置类UserAutoConfiguration:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cxytiandi.demo.UserAutoConfigure

Spring Boot 会默认扫描跟启动类平级的包,假如我们的 Starter 跟启动类不在同一个主包下,通过在spring.factories 文件中添加自动配置类的方式,Spring Boot 启动时会去读取 spring.factories 文件,然后根据配置激活对应的配置类,至此一个简单的 Starter 包就实现了。

(6)使用 Starter

现在可以在其他的项目中引入这个 Starter 包,如下:

<dependency>
    <groupId>com.cxytiandi</groupId>
    <artifactId>spring-boot-starter-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

引入之后就直接可以使用 UserClient,UserClient 在项目启动的时候已经自动初始化好:

@RestController
public class UserController {
    @Autowired
    private UserClient userClient;
    @GetMapping("/user/name")
    public String getUserName() {
        return userClient.getName();
    }
}

在属性文件中配置 name 的值和开启 UserClient:

spring.user.name=zhangsan
spring.user.enabled=true

访问 /user/name 就可以返回我们配置的 zhangsan。

三、使用注解开启 Starter 自动构建

很多时候我们不想在引入 Starter 包时就执行初始化的逻辑,而是想要由用户来指定是否要开启 Starter 包的自动配置功能,比如常用的 @EnableAsync 这个注解就是用于开启调用方法异步执行的功能。
同样地,我们也可以通过注解的方式来开启是否自动配置,如果用注解的方式,那么 spring.factories 就不需要编写了,下面就来看一下怎么定义启用自动配置的注解,代码如下所示:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({UserAutoConfigure.class})
public @interface EnableUserClient {

}

这段代码的核心是 @Import({UserAutoConfigure.class}),通过导入的方式实现把 UserAutoConfigure 实例加入 SpringIOC 容器中,这样就能开启自动配置了。使用方式就是在启动类上加上该注解,代码如下所示:

@SpringBootApplication
@EnableUserClient
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}

在某些场景下,UserAutoConfigure 中会配置多个对象,对于这些对象,如果不想全部配置,或是想让用户指定需要开启配置的时候再去构建对象,这个时候我们可以通过 @ConditionalOnProperty 来指定是否开启配置的功能,代码如下所示:

@Bean
@ConditionalOnProperty(prefix = "spring.user",value = "enabled",havingValue = "true")
public UserClient userClient(UserPorperties userPorperties) {
    return new UserClient(userPorperties);
}

通过上面的配置,只有当启动类加了 @EnableUserClient 并且配置文件中 spring.user.enabled=true 的时候才会自动配置 UserClient。

四、JavaConfig

Spring JavaConfifig  Spring 社区的产品,Spring 3.0引入了他,它提供了配置 Spring IOC 容器的纯Java 方法。因此它有助于避免使用 XML 配置。

使用 JavaConfifig 的优点在于:

面向对象的配置。由于配置被定义为 JavaConfig 中的类,因此用户可以充分利用 Java 中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean 方法等。 减少或消除 XML 配置。基于依赖注入原则的外化配置的好处已被证明。

但是,许多开发人员不希望在 XML  Java 之间来回切换。JavaConfifig 为开发人员提供了一种纯 Java 方法来配置与 XML 配置概念相似的 Spring 容器。从技术角度来讲,只使用 JavaConfifig 配置类来配置容器是可行的,但实际上很多人认为将JavaConfifig  XML 混合匹配是理想的。类型安全和重构友好。JavaConfifig 提供了一种类型安全的方法来配置 Spring容器。由于 Java 5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不需要任何强制转换或基于字符串的查找。

常用的Java config

  • @Confifiguration:在类上打上写下此注解,表示这个类是配置类
  • @ComponentScan:在配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 <context:component-scan >
  • @Beanbean的注入:相当于以前的< bean id="objectMapper" class="org.codehaus.jackson.map.ObjectMapper" />
  • @EnableWebMvc:相当于xml<mvc:annotation-driven >
  • @ImportResource: 相当于xml < import resource="applicationContext cache.xml">
posted @ 2022-06-25 14:02  沙滩de流沙  阅读(402)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货