1.提炼三句话
整体来讲Spring Boot是通过条件注解、条件评估器和自动配置导入器等机制来实现自动配置的。
条件评估器来判断是否需要加载某个自动配置类。条件评估器通常被定义在“org.springframework.boot.autoconfigure.condition”包中,例如,ClassCondition、BeanCondition、MissingBeanCondition、WebApplicationCondition等
条件注解来判断是否需要加载某个自动配置类。条件注解通常被定义在“org.springframework.boot.autoconfigure.condition”包中,例如,@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnWebApplication等。
自动配置导入器是用来来自动导入相关的自动配置类。自动配置导入器通常被定义在“org.springframework.boot.autoconfigure”包中,例如,JpaBaseConfiguration、DataSourcePoolMetadataProvidersConfiguration、HibernateJpaAutoConfiguration等
2. 详解
Spring Boot自动配置是一项强大的功能,它可以根据classpath下的类自动配置应用程序上下文。Spring Boot自动配置实际上是一组自定义的Spring Bean定义,它们可以通过自动扫描被发现并加载到容器中。
Spring Boot自动配置类通常被定义在“org.springframework.boot.autoconfigure”包中,并以“XxxAutoConfiguration”命名。Spring Boot提供了很多默认的自动配置类,例如,Spring MVC自动配置类(WebMvcAutoConfiguration)、JPA自动配置类(JpaBaseConfiguration)、数据源自动配置类(DataSourceAutoConfiguration)等。
Spring Boot通过一系列机制来实现自动配置,包括条件注解、条件评估器和自动配置导入器。下面我们来详细了解一下这些机制的源码实现。
2.1 条件注解
Spring Boot通过条件注解来判断是否需要加载某个自动配置类。条件注解通常被定义在“org.springframework.boot.autoconfigure.condition”包中,例如,@ConditionalOnClass、@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnWebApplication等。
例如,下面是一个基于@ConditionalOnClass注解的自动配置类:
@Configuration @ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}) @EnableConfigurationProperties(DataSourceProperties.class) @AutoConfigureAfter({DataSourcePoolMetadataProvidersConfiguration.class, HibernateJpaAutoConfiguration.class}) public class DataSourceAutoConfiguration { // 自动配置代码... }
自动配置类会在classpath中存在DataSource和EmbeddedDatabaseType类时被加载。
2.2 条件评估器
Spring Boot通过条件评估器来判断是否需要加载某个自动配置类。条件评估器通常被定义在“org.springframework.boot.autoconfigure.condition”包中,例如,ClassCondition、BeanCondition、MissingBeanCondition、WebApplicationCondition等。
例如基于ClassCondition的条件评估器:
public class OnClassCondition extends FilteringSpringBootCondition { // ... protected boolean matches(Class<?> type) { return this.beanFactory.getBeanNamesForType(type, true, false).length > 0; } // ... }
条件评估器会判断当前应用程序上下文中是否存在指定类型的Bean。
2.3 自动配置导入器
通过自动配置导入器来自动导入相关的自动配置类。自动配置导入器通常被定义在“org.springframework.boot.autoconfigure”包中,例如,JpaBaseConfiguration、DataSourcePoolMetadataProvidersConfiguration、HibernateJpaAutoConfiguration等。
例如基于@Import注解的自动配置导入器:
@Configuration @ConditionalOnClass({LocalContainerEntityManagerFactoryBean.class, EnableJpaRepositories.class}) @EnableConfigurationProperties(JpaProperties.class) @Import({JpaBaseConfiguration.class, HibernateJpaAutoConfiguration.class}) public class HibernateJpaConfiguration { // 自动配置代码... }
自动配置导入器会导入JpaBaseConfiguration和HibernateJpaAutoConfiguration两个自动配置类。
Spring Boot通过条件注解、条件评估器和自动配置导入器等机制来实现自动配置。这些机制可以帮助开发人员快速构建应用程序,减少重复的配置工作。
3.学以致用
Spring Boot 的自动配置类是一个非常强大的特性,它可以根据项目中的依赖和配置自动配置应用程序的环境,从而让开发者更加专注于业务逻辑的实现。
在实际项目中,我们可以通过自定义自动配置类来实现一些特定的需求,或者通过过滤机制来排除一些不需要的自动配置类。
使用场景和示例代码
1. 自定义自动配置类
假设我们的项目需要使用一个名为 MyService 的服务,我们可以定义一个自动配置类 MyServiceAutoConfiguration,它可以根据项目的配置来初始化 MyService 实例。具体代码如下:
@Configuration @EnableConfigurationProperties(MyServiceProperties.class) @ConditionalOnClass(MyService.class) public class MyServiceAutoConfiguration { @Autowired private MyServiceProperties properties; @Bean @ConditionalOnMissingBean(MyService.class) public MyService myService() { return new MyService(properties.getHost(), properties.getPort()); } }
在上面的代码中,我们使用了@EnableConfigurationProperties 注解来启用 MyServiceProperties 配置类,并且使用@ConditionalOnClass 注解来判断 MyService 是否在类路径上可用。在@Bean 方法中,我们使用了@ConditionalOnMissingBean 注解来判断当前应用程序上下文中是否已经存在 MyService 实例,如果不存在,则创建一个新的实例。
2. 排除不需要的自动配置类
有时候我们可能不需要某些自动配置类,比如我们不需要使用 Spring Boot 的数据库自动配置,我们可以通过在 application.properties 文件中设置 exclude 属性来排除它。具体代码如下:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
在上面的配置中,通过设置 spring.autoconfigure.exclude 属性来排除 DataSourceAutoConfiguration 类,从而禁用 Spring Boot 的数据库自动配置。
3. 使用条件注解控制自动配置
有时候我们可能需要根据特定的条件来控制自动配置是否生效,比如根据某个属性的值来判断是否需要配置某个组件。在这种情况下,我们可以使用@ConditionalXXX 注解来控制自动配置,这些注解都是继承自 Spring 提供的 Condition 接口的。
例如,可以根据 application.properties 中的一个属性来控制是否需要配置 MyService 实例,具体代码如下:
@Configuration @EnableConfigurationProperties(MyServiceProperties.class) @ConditionalOnClass(MyService.class) @ConditionalOnProperty(prefix = "myservice", name = "enabled", havingValue = "true", matchIfMissing = true) public class MyServiceAutoConfiguration { @Autowired private MyServiceProperties properties; @Bean @ConditionalOnMissingBean(MyService.class) public MyService myService() { return new MyService(properties.getHost(), properties.getPort()); } }
使用@ConditionalOnProperty 注解来判断是否需要配置 MyService 实例,prefix 参数指定属性的前缀,name 参数指定属性的名称,havingValue 参数指定属性的值,matchIfMissing 参数指定当属性不存在时的默认值。
4. 自定义属性配置类
这个在我们的项目中是很常见的,我就不赘述了
使用 MyServiceProperties 配置类来获取 application.properties 中的配置,这个配置类需要使用@EnableConfigurationProperties 注解来启用。如果想要更加灵活地控制该配置类的加载,可以使用@ConfigurationProperties 注解来标记这个配置类,并且使用@ImportResource 注解来加载配置文件。
例如可将 MyServiceProperties 配置类的配置文件单独放在一个 myservice.properties 文件中,并且使用@ConfigurationProperties 注解来加载该文件,代码如下:
@Configuration @ImportResource("classpath:/myservice.properties") @ConfigurationProperties(prefix = "myservice") public class MyServiceProperties { private String host; private int port; // getters and setters }
5. 自定义自动配置类的优先级
每个自动配置类都有一个优先级,优先级越高的自动配置类会优先生效。想要自定义自动配置类的优先级,可以使用@AutoConfigureOrder 注解来指定。
例如可以将 MyServiceAutoConfiguration 的优先级设置为 1,使它比其他自动配置类优先生效,具体代码如下:
@Configuration @EnableConfigurationProperties(MyServiceProperties.class) @ConditionalOnClass(MyService.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) public class MyServiceAutoConfiguration { @Autowired private MyServiceProperties properties; @Bean @ConditionalOnMissingBean(MyService.class) public MyService myService() { return new MyService(properties.getHost(), properties.getPort()); } }
6. 自定义自动配置类的加载顺序
自动配置类的加载顺序默认是按照字母顺序来决定的(可以看源码剖析部分)。如果想要自定义自动配置类的加载顺序,可以使用@AutoConfigureAfter 和@AutoConfigureBefore 注解来指定。
例如,可以将 MyServiceAutoConfiguration 设置为在 DataSourceAutoConfiguration 之后加载,代码如下:
使用@AutoConfigureAfter 注解将 MyServiceAutoConfiguration 设置为在 DataSourceAutoConfiguration 之后加载。
@Configuration @EnableConfigurationProperties(MyServiceProperties.class) @ConditionalOnClass(MyService.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class MyServiceAutoConfiguration { @Autowired private MyServiceProperties properties; @Bean @ConditionalOnMissingBean(MyService.class) public MyService myService() { return new MyService(properties.getHost(), properties.getPort()); } }
自定义优先级和加载顺序可以帮助我们更加灵活地控制自动配置。在实际项目中大家可以自由发挥。
7. 自定义自动配置类的条件
在 Spring Boot 中可以使用条件注解来控制自动配置类的加载条件。除了 Spring Boot 提供的条件注解之外,还可以自定义条件注解来控制自动配置类的加载条件。
自定义一个@ConditionalOnMyCondition 注解来控制某个自动配置类的加载条件,代码如下:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Conditional(MyCondition.class) public @interface ConditionalOnMyCondition { String value(); }
然后,在自动配置类中使用@ConditionalOnMyCondition 注解来控制加载条件,具体代码如下:
@Configuration @EnableConfigurationProperties(MyServiceProperties.class) @ConditionalOnClass(MyService.class) @ConditionalOnMyCondition("my.condition.enabled") public class MyServiceAutoConfiguration { @Autowired private MyServiceProperties properties; @Bean @ConditionalOnMissingBean(MyService.class) public MyService myService() { return new MyService(properties.getHost(), properties.getPort()); } }
8. 自定义自动配置类的条件类
除了自定义条件注解之外还可以自定义条件类来控制自动配置类的加载条件。条件类需要实现 Condition 接口,并且实现 matches 方法。
例如可以定义一个 MyCondition 条件类来控制某个自动配置类的加载条件,具体代码如下:
定义一个 MyCondition 条件类,并且实现了 matches 方法来判断是否满足加载条件。当 application.properties 中的 my.condition.enabled 属性为 true 时,才会加载这个自动配置类。
public class MyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String enabled = context.getEnvironment().getProperty("my.condition.enabled"); return Boolean.parseBoolean(enabled); } }
然后,在自动配置类中使用@Conditional 注解来指定 MyCondition 条件类,具体代码如下:
@Configuration @EnableConfigurationProperties(MyServiceProperties.class) @ConditionalOnClass(MyService.class) @Conditional(MyCondition.class) public class MyServiceAutoConfiguration { @Autowired private MyServiceProperties properties; @Bean @ConditionalOnMissingBean(MyService.class) public MyService myService() { return new MyService(properties.getHost(), properties.getPort()); } }