探索@Conditional的力量
在开发基于Spring的应用程序时,我们可能会遇到有条件地注册bean的需求。
例如,您可能想在本地运行应用程序时注册一个指向dev数据库的DataSource bean,而在生产环境中运行时则指向一个不同的生产数据库。
您可以将数据库连接参数外部化到属性文件中,并使用适合该环境的文件,但是每当需要指向其他环境并构建应用程序时,都需要更改配置。
为了解决这个问题,Spring 3.1引入了Profiles的概念。您可以注册多个相同类型的bean,并将它们与一个或多个概要文件关联。当您运行该应用程序时,您可以激活所需的配置文件以及与已激活的配置文件相关联的Bean,并且只会注册那些配置文件。
@Configuration public class AppConfig { @Bean @Profile("DEV") public DataSource devDataSource() { ... } @Bean @Profile("PROD") public DataSource prodDataSource() { ... } }
然后,您可以使用系统属性-Dspring.profiles.active = DEV指定活动配置文件。
这种方法适用于简单情况,例如根据已激活的配置文件启用或禁用bean注册。但是,如果您要基于某些条件逻辑来注册bean,那么概要文件方法本身是不够的。
为了为有条件地注册Spring Bean提供更大的灵活性,Spring 4引入了@Conditional的概念。通过使用 @Conditional方法,您可以根据任意条件有条件地注册bean。
例如,在以下情况下,您可能要注册一个bean:
这些仅是几个示例,您可以具有所需的任何条件。
让我们看看Spring的@Conditional是如何工作的。
假设我们有一个UserDAO 接口,其中包含从数据存储中获取数据的方法。我们有两个工具UserDAO的 接口,即JdbcUserDAO 其谈判的MySQL 数据库和MongoUserDAO 进行对话的MongoDB的。
JdbcUserDAO
MongoUserDAO
MongoDB
我们可能只想基于系统属性(即dbType)启用JdbcUserDAO和MongoUserDAO 的一个接口。
如果使用java -jar myapp.jar -DdbType = MySQL启动应用程序,那么我们要启用JdbcUserDAO。 否则,如果使用java -jar myapp.jar -DdbType = MONGO启动该应用程序,我们要启用MongoUserDAO。
java -jar myapp.jar -DdbType = MySQL
java -jar myapp.jar -DdbType = MONGO
假设我们有一个 UserDAO bean和一个 JdbcUserDAO bean。该 MongoUserDAO 实现如下:
UserDAO bean
JdbcUserDAO bean
ongoUserDAO
public interface UserDAO { List<String> getAllUserNames(); } public class JdbcUserDAO implements UserDAO { @Override public List<String> getAllUserNames() { System.out.println("**** Getting usernames from RDBMS *****"); return Arrays.asList("Siva","Prasad","Reddy"); } } public class MongoUserDAO implements UserDAO { @Override public List<String> getAllUserNames() { System.out.println("**** Getting usernames from MongoDB *****"); return Arrays.asList("Bond","James","Bond"); } }
我们可以实现条件MySQLDatabaseTypeCondition 来检查系统属性dbType 是否为“ MYSQL”,如下所示:
public class MySQLDatabaseTypeCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { String enabledDBType = System.getProperty("dbType"); return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MYSQL")); } }
我们可以实现条件MongoDBDatabaseTypeCondition 来检查系统属性dbType 是否为“ MONGODB ”,如下所示:
public class MongoDBDatabaseTypeCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { String enabledDBType = System.getProperty("dbType"); return (enabledDBType != null && enabledDBType.equalsIgnoreCase("MONGODB")); } }
现在,我们可以使用@Conditional有条件地配置JdbcUserDAO 和MongoUserDAO Bean ,如下所示:
@Conditional
MongoUserDAO Bean
@Configuration public class AppConfig { @Bean @Conditional(MySQLDatabaseTypeCondition.class) public UserDAO jdbcUserDAO(){ return new JdbcUserDAO(); } @Bean @Conditional(MongoDBDatabaseTypeCondition.class) public UserDAO mongoUserDAO(){ return new MongoUserDAO(); } }
如果运行类似 java -jar myapp.jar -DdbType = MYSQL的应用程序,则仅 会注册JdbcUserDAO Bean。但是,如果将系统属性设置为-DdbType = MONGODB,则只会 注册MongoUserDAO Bean。
现在,我们已经看到了如何基于系统属性有条件地注册bean。
假设我们想注册MongoUserDAO bean只有当MongoDB的 Java驱动程序类“com.mongodb.Server”,可在类路径中,如果不是我们想注册JdbcUserDAO bean。
为此,我们可以创建条件来检查MongoDB驱动程序类“ com.mongodb.Server”的存在与否,如下所示:
public class MongoDriverPresentsCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext,AnnotatedTypeMetadata metadata) { try { Class.forName("com.mongodb.Server"); return true; } catch (ClassNotFoundException e) { return false; } } } public class MongoDriverNotPresentsCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { try { Class.forName("com.mongodb.Server"); return false; } catch (ClassNotFoundException e) { return true; } } }
我们刚刚看到了如何根据类路径中是否存在类来有条件地注册bean。
如果仅在尚未注册其他类型为UserDAO的 Spring Bean的情况下才想注册MongoUserDAO Bean,该 怎么办 ?
我们可以创建一个条件来检查是否存在某种特定类型的现有bean,如下所示:
public class UserDAOBeanNotPresentsCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { UserDAO userDAO = conditionContext.getBeanFactory().getBean(UserDAO.class); return (userDAO == null); } }
如果仅在属性占位符配置文件中设置属性app.dbType = MONGO时才想注册MongoUserDAO bean,该怎么办?
我们可以如下实现该条件:
public class MongoDbTypePropertyCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { String dbType = conditionContext.getEnvironment() .getProperty("app.dbType"); return "MONGO".equalsIgnoreCase(dbType); } }
我们刚刚看到了如何实现各种类型的条件,但是还有使用注释来实现条件的更优雅的方法。除了为MYSQL和MongoDB创建Condition实现之外,我们还可以如下创建DataBaseType批注:
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Conditional(DatabaseTypeCondition.class) public @interface DatabaseType { String value(); }
然后,我们可以实现DatabaseTypeCondition 以使用DatabaseType 值来确定是启用还是禁用bean注册,如下所示:
public class DatabaseTypeCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { Map<String, Object> attributes = metadata.getAnnotationAttributes(DatabaseType.class.getName()); String type = (String) attributes.get("value"); String enabledDBType = System.getProperty("dbType","MYSQL"); return (enabledDBType != null && type != null && enabledDBType.equalsIgnoreCase(type)); } }
现在,我们可以在bean定义上使用@DatabaseType批注,如下所示:
@Configuration @ComponentScan public class AppConfig { @DatabaseType("MYSQL") public UserDAO jdbcUserDAO(){ return new JdbcUserDAO(); } @Bean @DatabaseType("MONGO") public UserDAO mongoUserDAO(){ return new MongoUserDAO(); } }
在这里,我们从DatabaseType 批注中获取元数据,并对照System Property dbType 值进行检查,以确定是启用还是禁用Bean注册。
我们已经看到了很多示例,以了解如何使用@Conditional批注有条件地注册bean 。
Spring Boot广泛使用@Conditional功能根据各种条件有条件地注册bean。
您可以在spring-boot-autoconfigure- {version} .jar的org.springframework.boot.autoconfigure 包中找到SpringBoot使用的各种Condition实现。
spring-boot-autoconfigure- {version} .jar
org.springframework.boot.autoconfigure
现在我们已经知道了Spring Boot如何使用 @Conditional功能有条件地检查是否注册Bean,但是究竟是什么触发了自动配置机制呢?
这就是我们将在下一部分中讨论的内容。
Spring Boot自动配置
Spring Boot自动配置魔术的关键是@EnableAutoConfiguration批注。通常,我们使用@SpringBootApplication注释我们的Application入口点类,或者如果我们要自定义默认值,则可以使用以下注释:
@Configuration @EnableAutoConfiguration @ComponentScan public class Application { }
所述@EnableAutoConfiguration注释使得弹簧的自动配置的ApplicationContext 通过扫描类路径组件并登记所匹配的各种条件的豆类。
@EnableAutoConfiguration
ApplicationContext
SpringBoot在spring-boot-autoconfigure- {version} .jar中提供了各种AutoConfiguration 类,这些类负责注册各种组件。
通常,使用@Configuration注释AutoConfiguration 类以将其标记为Spring配置类,并使用@EnableConfigurationProperties注释以绑定定制属性和一个或多个条件Bean注册方法。
@Configuration
AutoConfiguration
Spring
@EnableConfigurationProperties
例如,考虑 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration类。
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
@Configuration @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @EnableConfigurationProperties(DataSourceProperties.class) @Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }) public class DataSourceAutoConfiguration { ... ... @Conditional(DataSourceAutoConfiguration.EmbeddedDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import(EmbeddedDataSourceConfiguration.class) protected static class EmbeddedConfiguration { } @Configuration @ConditionalOnMissingBean(DataSourceInitializer.class) protected static class DataSourceInitializerConfiguration { @Bean public DataSourceInitializer dataSourceInitializer() { return new DataSourceInitializer(); } } @Conditional(DataSourceAutoConfiguration.NonEmbeddedDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) protected static class NonEmbeddedConfiguration { @Autowired private DataSourceProperties properties; @Bean @ConfigurationProperties(prefix = DataSourceProperties.PREFIX) public DataSource dataSource() { DataSourceBuilder factory = DataSourceBuilder .create(this.properties.getClassLoader()) .driverClassName(this.properties.getDriverClassName()) .url(this.properties.getUrl()).username(this.properties.getUsername()) .password(this.properties.getPassword()); if (this.properties.getType() != null) { factory.type(this.properties.getType()); } return factory.build(); } } ... ... @Configuration @ConditionalOnProperty(prefix = "spring.datasource", name = "jmx-enabled") @ConditionalOnClass(name = "org.apache.tomcat.jdbc.pool.DataSourceProxy") @Conditional(DataSourceAutoConfiguration.DataSourceAvailableCondition.class) @ConditionalOnMissingBean(name = "dataSourceMBean") protected static class TomcatDataSourceJmxConfiguration { @Bean public Object dataSourceMBean(DataSource dataSource) { .... .... } } ... ... }
在这里, DataSourceAutoConfiguration 带有@ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class})注释,这意味着只有在classpath上有DataSource.class和EmbeddedDatabaseType.class 类可用时,才会考虑 DataSourceAutoConfiguration中 的bean的AutoConfiguration 。
DataSourceAutoConfiguration
@ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class})
DataSource.class
EmbeddedDatabaseType.class
DataSourceAutoConfiguration中 的bean的AutoConfiguration
该类还带有@EnableConfigurationProperties(DataSourceProperties.class) 批注,该启用了自动将application.properties中的属性绑定到DataSourceProperties类的属性的功能。
@EnableConfigurationProperties(DataSourceProperties.class)
application.properties
DataSourceProperties
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX) public class DataSourceProperties implements BeanClassLoaderAware, EnvironmentAware, InitializingBean { public static final String PREFIX = "spring.datasource"; ... ... private String driverClassName; private String url; private String username; private String password; ... //setters and getters }
使用此配置,所有以spring.datasource。*开头的属性都将自动绑定到DataSourceProperties 对象。
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=secret spring.datasource.driver-class-name=com.mysql.jdbc.Driver
您还可以看到一些内部类和bean定义方法,这些内部类和bean定义方法使用SpringBoot的条件注释进行注释,例如@ ConditionalOnMissingBean,@ ConditionalOnClass和@ConditionalOnProperty等。
仅当这些条件匹配时,这些Bean定义才会在ApplicationContext中 注册。
您还可以在spring-boot-autoconfigure- {version} .jar中探索许多其他AutoConfiguration类,例如:
原文链接:http://codingdict.com