В моем предыдущем посте « Почему Spring Boot? » Мы рассмотрели, как создать приложение Spring Boot, но вы можете или не можете понять, что происходит за кулисами. Возможно, вы захотите понять магию автоконфигурации Spring Boot.
Перед этим вы должны знать о функции SpringConnect @Conditional , от которой зависит вся магия автоконфигурации Spring Boot.
Изучение силы @Conditional
При разработке приложений на основе Spring может возникнуть необходимость условной регистрации bean-компонентов.
Например, вы можете зарегистрировать bean-компонент DataSource, указывающий на базу данных dev при локальном запуске приложения, и указывать на другую производственную базу данных при работе в рабочей среде.
Вы можете перенести параметры соединения с базой данных в файл свойств и использовать файл, соответствующий среде, но вам нужно изменить конфигурацию всякий раз, когда вам нужно указать другую среду и создать приложение.
Для решения этой проблемы в Spring 3.1 была введена концепция профилей . Вы можете зарегистрировать несколько бинов одного типа и связать их с одним или несколькими профилями. При запуске приложения вы можете активировать нужные профили и компоненты, связанные с активированными профилями, и только эти профили будут зарегистрированы.
@Configuration
public class AppConfig
{
@Bean
@Profile("DEV")
public DataSource devDataSource() {
...
}
@Bean
@Profile("PROD")
public DataSource prodDataSource() {
...
}
}
Затем вы можете указать активный профиль, используя Системное свойство -Dspring.profiles.active = DEV.
Этот подход работает для простых случаев, таких как включение или отключение регистрации компонентов на основе активированных профилей. Но если вы хотите зарегистрировать bean-компоненты, основанные на некоторой условной логике, тогда сам подход профилей недостаточен.
Чтобы обеспечить большую гибкость условной регистрации bean-компонентов Spring, Spring 4 ввел концепцию @Conditional . Используя подход @Conditional, вы можете зарегистрировать компонент, условно основанный на любом произвольном условии.
Например, вы можете зарегистрировать компонент, когда:
- Определенный класс присутствует в classpath
- Spring bean определенного типа еще не зарегистрирован в ApplicationContext
- Определенный файл существует в местоположении
- Конкретное значение свойства настраивается в файле конфигурации
- Определенное системное свойство присутствует / отсутствует
Это всего лишь несколько примеров, и вы можете иметь любое условие, которое захотите.
Давайте посмотрим, как работает Spring’s @Conditional.
Предположим, у нас есть интерфейс UserDAO с методами для получения данных из хранилища данных. У нас есть две реализации интерфейса UserDAO, а именно JdbcUserDAO, который общается с базой данных MySQL, и MongoUserDAO, который общается с MongoDB .
Мы можем захотеть включить только один интерфейс JdbcUserDAO и MongoUserDAO на основе системного свойства, скажем, dbType .
Если приложение запускается с использованием java -jar myapp.jar -DdbType = MySQL , то мы хотим включить JdbcUserDAO. В противном случае, если приложение запускается с использованием java -jar myapp.jar -DdbType = MONGO , мы хотим включить MongoUserDAO .
Предположим , что мы имеем UserDAO фасоль и JdbcUserDAO фасоль. MongoUserDAO реализация выглядит следующим образом :
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"));
}
}
Теперь мы можем условно настроить bean- компоненты JdbcUserDAO и MongoUserDAO, используя @Conditional следующим образом:
@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, тогда будет зарегистрирован только bean- компонент JdbcUserDAO. Но если вы установите свойство System, например -DdbType = MONGODB, то будет зарегистрирован только bean- компонент MongoUserDAO .
Теперь, когда мы увидели, как условно зарегистрировать бин на основе системного свойства.
Предположим, что мы хотим зарегистрировать bean- компонент MongoUserDAO только в том случае , если в classpath доступен класс драйвера Java MongoDB «com.mongodb.Server» , если нет, мы хотим зарегистрировать bean- компонент JdbcUserDAO .
Для этого мы можем создать условия для проверки наличия или отсутствия класса драйвера 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-компоненты условно на основании наличия или отсутствия класса в classpath.
Что если мы хотим зарегистрировать bean- компонент MongoUserDAO, только если другой Spring-компонент типа UserDAO уже не зарегистрирован.
Мы можем создать Условие, чтобы проверить, существует ли какой-либо bean-компонент определенного типа, следующим образом:
public class UserDAOBeanNotPresentsCondition implements Condition
{
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata)
{
UserDAO userDAO = conditionContext.getBeanFactory().getBean(UserDAO.class);
return (userDAO == null);
}
}
Что если мы хотим зарегистрировать bean- компонент MongoUserDAO, только если в файле конфигурации заполнителя свойств установлено свойство app.dbType = MONGO ?
Мы можем реализовать это условие следующим образом:
public class MongoDbTypePropertyCondition implements Condition
{
@Override
public boolean matches(ConditionContext conditionContext,
AnnotatedTypeMetadata metadata)
{
String dbType = conditionContext.getEnvironment()
.getProperty("app.dbType");
return "MONGO".equalsIgnoreCase(dbType);
}
}
Мы только что увидели, как реализовать различные типы Условий. Но есть еще более элегантный способ реализовать Условия с использованием Аннотаций. Вместо создания реализации Condition для MYSQL и MongoDB мы можем создать аннотацию aDatabaseType следующим образом:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DatabaseTypeCondition.class)
public @interface DatabaseType
{
String value();
}
Затем мы можем реализовать DatabaseTypeCondition, чтобы использовать значение DatabaseType, чтобы определить, включить или отключить регистрацию компонента следующим образом:
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));
}
}
Теперь мы можем использовать аннотацию @DatabaseType в наших определениях компонентов следующим образом:
@Configuration
@ComponentScan
public class AppConfig
{
@DatabaseType("MYSQL")
public UserDAO jdbcUserDAO(){
return new JdbcUserDAO();
}
@Bean
@DatabaseType("MONGO")
public UserDAO mongoUserDAO(){
return new MongoUserDAO();
}
}
Здесь мы получаем метаданные из аннотации DatabaseType и проверяем значение dbType системного свойства, чтобы определить, включить или отключить регистрацию компонента.
Мы видели множество примеров, чтобы понять, как мы можем зарегистрировать бины условно, используя аннотацию @Conditional .
Spring Boot широко использует функцию @Conditional для регистрации bean-компонентов условно на основе различных критериев.
Вы можете найти различные реализации Условий, которые SpringBoot использует в org.springframework.boot.autoconfigure в пакете spring-boot-autoconfigure- {version} .jar .
Теперь, когда мы узнали о том, как Spring Boot использует функцию @Conditional для условной проверки, регистрировать бин или нет, но что именно запускает механизм автоконфигурации?
Это то, что мы рассмотрим в следующем разделе.
Автоконфигурация Spring Boot
Ключ к волшебству автоконфигурации Spring Boot — аннотация @EnableAutoConfiguration . Обычно мы аннотируем наш класс точки входа Application либо с помощью @SpringBootApplication, либо, если мы хотим настроить значения по умолчанию, мы можем использовать следующие аннотации:
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application
{
}
@EnableAutoConfiguration аннотаций позволяет автоматическую конфигурацию Spring ApplicationContext путем сканирования компонентов пути к классам и регистрирует бобы, которые соответствующие различные условия.
SpringBoot предоставляет различные классы AutoConfiguration в spring-boot-autoconfigure- {version} .jar , которые отвечают за регистрацию различных компонентов.
Обычно автонастройки классов аннотированные с @Configuration , чтобы пометить его как класс конфигурации Spring и аннотированный с @EnableConfigurationProperties , чтобы связывать свойство настройки и один или более условных методы регистрации фасоли.
Например, рассмотрим класс 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}) , который означает , что AutoConfiguration бобов в DataSourceAutoConfiguration будет рассматриваться только если DataSource.class и EmbeddedDatabaseType.class классы доступны по классам.
Класс также аннотируется @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-компонентов, которые аннотированы условными аннотациями SpringBoot, такими как @ConditionalOnMissingBean, @ConditionalOnClass, @ConditionalOnProperty и т. Д.
Эти определения bean-компонентов будут зарегистрированы в ApplicationContext, только если эти условия будут выполнены.
Вы также можете изучить многие другие классы автоконфигурации в spring-boot-autoconfigure- {version} .jar, такие как:
- org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
- org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
- org.springframework.boot.autoconfigure.jackson.JacksonAutoConfigurationetc и т. д.
Я надеюсь, что теперь у вас есть понимание того, как работает автоконфигурация Spring Boot, используя различные классы автоконфигурации вместе с функциями @Conditional .