1. Обзор
Проще говоря, автоматическая настройка Spring Boot помогает нам автоматически настроить приложение Spring на основе зависимостей, присутствующих в пути к классам.
Это может ускорить и упростить разработку, устраняя необходимость определения определенных bean-компонентов, включенных в классы автоконфигурации.
В следующем разделе мы рассмотрим создание нашей пользовательской автоматической конфигурации Spring Boot.
2. Зависимости Maven
Начнем с зависимостей:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
Последние версии spring-boot-starter-data-jpa и mysql-connector-java можно загрузить с Maven Central.
3. Создание пользовательской автоматической конфигурации
Чтобы создать пользовательскую автоконфигурацию, нам нужно создать класс с аннотацией @Configuration
и зарегистрировать его.
Давайте создадим пользовательскую конфигурацию для источника данных MySQL
:
@Configuration
public class MySQLAutoconfiguration {
//...
}
Далее нам нужно зарегистрировать класс в качестве кандидата на автоматическую настройку.
Делаем это, добавляя имя класса по ключу org.springframework.boot.autoconfigure.EnableAutoConfiguration
в стандартном файле resources/META-INF/spring.factories
:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.foreach.autoconfiguration.MySQLAutoconfiguration
Если мы хотим, чтобы наш класс автоконфигурации имел приоритет над другими кандидатами, мы можем добавить аннотацию @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
.
Мы разрабатываем автоконфигурацию, используя классы и bean-компоненты, помеченные аннотациями @Conditional
, чтобы мы могли заменить автоконфигурацию или определенные ее части.
Обратите внимание, что автоконфигурация действует только в том случае, если мы не определяем автоконфигурируемые bean-компоненты в приложении. Если мы определим наш компонент, он переопределит компонент по умолчанию.
3.1. Условия класса
Условия класса позволяют нам указать, что мы хотим включить компонент конфигурации, если указанный класс присутствует с помощью аннотации @ConditionalOnClass
или если класс отсутствует с помощью аннотации @ConditionalOnMissingClass
.
Давайте укажем, что наша MySQLConfiguration
будет загружаться только в том случае, если присутствует класс DataSource
, и в этом случае мы можем предположить, что приложение будет использовать базу данных:
@Configuration
@ConditionalOnClass(DataSource.class)
public class MySQLAutoconfiguration {
//...
}
3.2. Условия бобов
Если мы хотим включить bean-компонент только в том случае, если указанный bean-компонент присутствует или нет , мы можем использовать аннотации @ConditionalOnBean
и @ConditionalOnMissingBean
.
Чтобы посмотреть на это, давайте добавим bean-компонент entityManagerFactory
в наш класс конфигурации.
Во-первых, мы укажем, что мы хотим создать этот bean-компонент только в том случае, если bean-компонент с именем dataSource
присутствует и если bean-компонент с именем entityManagerFactory
еще не определен:
@Bean
@ConditionalOnBean(name = "dataSource")
@ConditionalOnMissingBean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.foreach.autoconfiguration.example");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
if (additionalProperties() != null) {
em.setJpaProperties(additionalProperties());
}
return em;
}
Давайте также настроим bean-компонент transactionManager
, который будет загружаться, только если мы еще не определили bean-компонент типа JpaTransactionManager
:
@Bean
@ConditionalOnMissingBean(type = "JpaTransactionManager")
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
3.3. Условия недвижимости
Мы используем аннотацию @ConditionalOnProperty
, чтобы указать, загружается ли конфигурация на основе наличия и значения свойства Spring Environment.
Во-первых, давайте добавим исходный файл свойств для нашей конфигурации, который будет определять, откуда будут считываться свойства:
@PropertySource("classpath:mysql.properties")
public class MySQLAutoconfiguration {
//...
}
Мы можем настроить основной bean-компонент DataSource
, который мы будем использовать для создания соединений с базой данных, чтобы он загружался только при наличии свойства, называемого usemysql
.
Мы можем использовать атрибут haveValue
, чтобы указать определенные значения свойства usemysql
, которые должны сопоставляться.
Теперь давайте определим bean- компонент dataSource
со значениями по умолчанию, который подключается к локальной базе данных с именем myDb
, если мы установим для свойства usemysql
значение local
:
@Bean
@ConditionalOnProperty(
name = "usemysql",
havingValue = "local")
@ConditionalOnMissingBean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/myDb?createDatabaseIfNotExist=true");
dataSource.setUsername("mysqluser");
dataSource.setPassword("mysqlpass");
return dataSource;
}
Если мы установим для свойства usemysql
значение custom
, мы настроим bean- компонент dataSource,
используя значения пользовательских свойств для URL-адреса базы данных, пользователя и пароля:
@Bean(name = "dataSource")
@ConditionalOnProperty(
name = "usemysql",
havingValue = "custom")
@ConditionalOnMissingBean
public DataSource dataSource2() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl(env.getProperty("mysql.url"));
dataSource.setUsername(env.getProperty("mysql.user") != null
? env.getProperty("mysql.user") : "");
dataSource.setPassword(env.getProperty("mysql.pass") != null
? env.getProperty("mysql.pass") : "");
return dataSource;
}
Файл mysql.properties
будет содержать свойство usemysql
:
usemysql=local
Приложению, использующему MySQLAutoconfiguration
, может потребоваться переопределить свойства по умолчанию. В этом случае нужно просто добавить разные значения для свойств mysql.url
, mysql.user
и mysql.pass
и строку usemysql=custom
в файле mysql.properties
.
3.4. Условия ресурсов
Добавление аннотации @ConditionalOnResource
означает, что конфигурация загружается только при наличии указанного ресурса.
Давайте определим метод с именем AdditionalProperties ()
, который будет возвращать объект Properties
, содержащий специфичные для Hibernate свойства, которые будут использоваться bean- компонентом entityManagerFactory
, только если присутствует файл ресурсов mysql.properties
:
@ConditionalOnResource(
resources = "classpath:mysql.properties")
@Conditional(HibernateCondition.class)
Properties additionalProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto",
env.getProperty("mysql-hibernate.hbm2ddl.auto"));
hibernateProperties.setProperty("hibernate.dialect",
env.getProperty("mysql-hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql",
env.getProperty("mysql-hibernate.show_sql") != null
? env.getProperty("mysql-hibernate.show_sql") : "false");
return hibernateProperties;
}
Мы можем добавить специфичные для Hibernate свойства в файл mysql.properties
:
mysql-hibernate.dialect=org.hibernate.dialect.MySQLDialect
mysql-hibernate.show_sql=true
mysql-hibernate.hbm2ddl.auto=create-drop
3.5. Пользовательские условия
Допустим, мы не хотим использовать какие-либо условия, доступные в Spring Boot.
Мы также можем определить пользовательские условия, расширив класс SpringBootCondition и переопределив метод
getMatchOutcome()
.
Давайте создадим условие с именем HibernateCondition
для нашего метода AdditionalProperties ()
, который будет проверять, присутствует ли класс HibernateEntityManager в пути к классам:
static class HibernateCondition extends SpringBootCondition {
private static String[] CLASS_NAMES
= { "org.hibernate.ejb.HibernateEntityManager",
"org.hibernate.jpa.HibernateEntityManager" };
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message
= ConditionMessage.forCondition("Hibernate");
return Arrays.stream(CLASS_NAMES)
.filter(className -> ClassUtils.isPresent(className, context.getClassLoader()))
.map(className -> ConditionOutcome
.match(message.found("class")
.items(Style.NORMAL, className)))
.findAny()
.orElseGet(() -> ConditionOutcome
.noMatch(message.didNotFind("class", "classes")
.items(Style.NORMAL, Arrays.asList(CLASS_NAMES))));
}
}
Затем мы можем добавить условие в метод AdditionalProperties()
:
@Conditional(HibernateCondition.class)
Properties additionalProperties() {
//...
}
3.6. Условия применения
Мы также можем указать, что конфигурация может загружаться только внутри/вне веб-контекста. Для этого мы можем добавить аннотацию @ConditionalOnWebApplication
или @ConditionalOnNotWebApplication
.
4. Тестирование автоконфигурации
Давайте создадим очень простой пример для проверки нашей автоматической настройки.
Мы создадим класс сущности с именем MyUser
и интерфейс MyUserRepository
, используя Spring Data:
@Entity
public class MyUser {
@Id
private String email;
// standard constructor, getters, setters
}
public interface MyUserRepository
extends JpaRepository<MyUser, String> { }
Чтобы включить автоконфигурацию, мы можем использовать одну из аннотаций @SpringBootApplication
или @EnableAutoConfiguration
:
@SpringBootApplication
public class AutoconfigurationApplication {
public static void main(String[] args) {
SpringApplication.run(AutoconfigurationApplication.class, args);
}
}
Далее давайте напишем тест JUnit
, который сохраняет сущность MyUser
:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(
classes = AutoconfigurationApplication.class)
@EnableJpaRepositories(
basePackages = { "com.foreach.autoconfiguration.example" })
public class AutoconfigurationTest {
@Autowired
private MyUserRepository userRepository;
@Test
public void whenSaveUser_thenOk() {
MyUser user = new MyUser("user@email.com");
userRepository.save(user);
}
}
Поскольку мы не определяли нашу конфигурацию DataSource
, приложение будет использовать созданную нами автоконфигурацию для подключения к базе данных MySQL
с именем myDb
.
Строка подключения содержит свойство createDatabaseIfNotExist=true
, поэтому база данных может не существовать. Однако необходимо создать пользователя mysqluser
или указанного в свойстве mysql.user
, если оно присутствует.
Мы можем проверить журнал приложения, чтобы увидеть, что мы используем источник данных MySQL
:
web - 2017-04-12 00:01:33,956 [main] INFO o.s.j.d.DriverManagerDataSource - Loaded JDBC driver: com.mysql.cj.jdbc.Driver
5. Отключение классов автоконфигурации
Допустим, мы хотим исключить из загрузки автоконфигурацию.
Мы могли бы добавить аннотацию @EnableAutoConfiguration с атрибутом
exclude
или excludeName
в класс конфигурации:
@Configuration
@EnableAutoConfiguration(
exclude={MySQLAutoconfiguration.class})
public class AutoconfigurationApplication {
//...
}
Мы также можем установить свойство spring.autoconfigure.exclude
:
spring.autoconfigure.exclude=com.foreach.autoconfiguration.MySQLAutoconfiguration
6. Заключение
В этой статье мы показали, как создать пользовательскую автоконфигурацию Spring Boot.
Полный исходный код примера можно найти на GitHub .
Тест JUnit можно запустить с помощью профиля автоконфигурации
mvn clean install -Pautoconfiguration
.