在向应用程序加入Spring Boot时,有个名为spring-boot-autoconfigure的JAR文件,其中包含了很多配置类。
它们利用了Spring 的条件化配置,这是Spring 4.0引入的新特征。条件化配置允许配置存在应用程序中,但在满足某些条件之前都忽略这个配置。
在Spring里可以很方便地编写你自己的条件,所要做的就是实现Condition接口,覆盖它的matches()方法。
举例来说,下面这个简单的条件类只有在Classpath里存在JdbcTemplate时才会生效:
package readinglist;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class JdbcTemplateCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
context.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
return true;
} catch (Exception e) {
return false;
}
}
}
当你用Java来声明Bean的时候,可以使用这个自定义条件类:
@Bean
@Conditional(JdbcTemplateCondition.class)
public MyService myService() {
return new MyService();
}
只有当JdbcTemplateCondition 类的条件成立时才会创建MyService这个Bean。也就是说MyService Bean创建的条件是Classpath里有JdbcTemplate。否则这个Bean的声明就会被忽略掉。
Spring Boot定义了很多更有趣的条件,并把它们运用到了配置类上,这些配置类构成了Spring Boot的自动配置。Spring Boot运用条件化配置的方法是,定义多个特殊的条件化注解,并将它们用到配置类上。
表 2-1 自动配置中使用的条件化注解
条件化注解 | 配置生效条件 |
---|---|
@ConditionalOnBean | 配置了某个特定的Bean |
@ConditionalOnMissingBean | 没有配置某个特定的Bean |
@ConditionalOnClass | Classpath 里有指定的类 |
@ConditionalOnMissingClass | Classpathh 里缺少指定的类 |
@ConditionalOnExpression | 给定的Spring Expression Language(SpEL)表达式结果为true |
@ConditionalOnJava | Java的版本匹配特定值或者一个范围值 |
@ConditionalOnJndi | 参数中给定的JNDI位置必须存在一个,如果没有参数,则要有JNDIInitialContext |
@ConditionalOnProperty | 指定的配置属性要有一个明确的值 |
@ConditionalOnResource | Classpath 里有指定的资源 |
@ConditionalOnWebApplication | 这是一个Web应用程序 |
@ConditionalOnNotWebApplication | 这不是一个Web应用程序 |
DataSourceAutoConfiguration的片段(Spring Boot自动配置库的一部分):
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
...
}
DataSourceAutoConfiguration 添加了@Configuration 注解。
最重要的是,DataSourceAutoConfiguration 添加了@ConditionalOnClass 注解,要求Classpath里必须要有DataSource和EmbeddedDataBaseType。如果它们不存在,条件就不成立,DataSourceAutoConfiguration提供的配置都会被忽略掉。
DataSourceAutoConfiguration 里嵌入了一个JdbcTemplateConfiguration 类,自动配置了一个JdbcTemplate Bean :
@Configuration
@Conditional(DataSourceAutoConfiguration.DataSourceAvailableCondition.class)
protected static class JdbcTemplateConfiguration {
@Autowired(required = false)
private DataSource dataSource;
@Bean
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(this.dataSource);
}
...
}
自动配置会做出一下配置决策
- 因为Classpath里有H2,所以会创建一个嵌入式的H2数据库Bean,它的类型是javax.sql.DataSource,JPA实现(Hibernate)需要它来访问数据库。
- 因为Classpath里有Hibernate(Spring Data Jpa 传递引入的)的实体管理器,所以会自动配置与hibernate相关的Bean,包括Spring的LocalContainerEntityManagerFactoryBean 和 JpaVendorAdapter。
- 因为Classpath里有Spring Data JPA,所以它会自动配置为根据仓库的接口创建仓库实现。
- 因为Classpath里有Thymeleaf,所以Thymeleaf会配置为Spring MVC的视图,包括一个Thymeleaf的模板解析器、模板引擎及视图解析器。视图解析器会解析相对于Classpath根目录的/templates目录里的模板。
- 因为这是一个Spring MVC Web应用程序,所以会注册一个资源管理器,把相对于Classpath根目录的/static 目录里的静态内容提供出来(这个资源处理器还能处理/public、/resources和/META-INF/resources的静态内容。)
- 因为Classpath 里有Tomcat(通过Web起步依赖传递引用),所以会启动一个嵌入式的Tomcat容器,监听8080端口。
由此可见,Spring Boot自动配置承担起了配置Spring 的重任,因此你能专注于编写自己的应用程序。
覆盖自动配置很简单,就当自动配置不存在,直接显式地写一段配置
想要覆盖Spring Boot的自动配置,所要做的仅仅是编写一个显式的配置,Spring会自动发现你的配置,随后降低自动配置的优先级,以你的配置为准。
大部分情况下 @ConditionalOnMissingBean 注解是覆盖自动配置的关键。
@Bean
@ConditionalOnMissingBean(JdbcOperation.class)
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(this.dataSource);
}
在需要时可以配置出一个JdbcTemplate Bean,但是上面还有@ConditionalOnMissingBean 注解,要求当前不存在JdbcOperations 类型(JdbcTemplate实现了该接口)的Bean才生效。如果当前已经有了一个JdbcOperations Bean了,条件即不满足,不会执行jdbcTemplate()方法。
Spring Boot的设计是加载应用级配置,随后再考虑自动配置类。因此,如果你已经配置了一个JdbcTemplate Bean,那么在执行自动配置时就已经存在一个JdbcTemplate类型的Bean了,于是忽略自动配置的JdbcTemplate Bean。
Spring Security,自动配置会考虑几个配置类。
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass({EnableWebSecurity.class})
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
@ConditionalOnWebApplication
public class SpringBootWebSecurityConfiguration {
...
}
SpringBootWebSecurityConfiguration 上有好几个注解。
@ConditionalOnClass 说明Classpath里必须要有@EnableWebSecurity注解。
@ConditionalOnWebApplication 说明这必须是个Web应用程序
@ConditionalOnMissingBean 才是我们的安全配置类代替SpringBootSecurityConfiguration的关键所在。
Spring Boot 的自动配置和 @ConditionalOnMissingBean 能让你显式地覆盖自动配置的Bean。