Spring 自定义Auto-Configuration
Spring Boot 可以根据classpath中依赖关系自动装配应用程序。通过自动装配机制,可以使开发更快、更简单。今天,学习下如何在Spring Boot 中创建自定义 auto-configuration。
代码运行环境
- JDK17
- MYSQL8
- 源码地址
Maven 依赖
首先添加以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.23</version>
<scope>test</scope>
</dependency>
创建自动装配
为了创建自动装配机制,首先创建一个类,并使用 @Configuration注解标记。以创建一个Mysql数据源为例
@Configuration
public class MySQLAutoconfiguration {
//...
}
接下来,需要将类注册到Spring Boot 容器中。创建 resources/META-INF/spring.factories文件, 内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.spring.boot.autoconfig.MySQLAutoconfiguration
- 如果想要自定义的装配有更高的优先级,需要添加 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 注解即可。
- 开发者可以使用 @Conditional 注解标记 auto-configuration的类,以便在条件满足时激活自动装配.
- bean的默认作用于为单例,如果开发者自定义的bean 装配跟框架内定义的bean一致,则会覆盖默认的bean定义
Class Conditions
Conditions 系列注解允许开发者定义 当指定条件满足时才会加载 auto-configuration.
- @ConditionalOnClass 当指定类存在时 条件满足
- @ConditionalOnMissingClass 当指定类不存在时 条件满足
@Configuration
@ConditionalOnClass(DataSource.class)
public class MySQLAutoconfiguration {
//...
}
上述代码指定当 DataSource.class 存在时,@Configuration 装配的逻辑才生效。
Bean Conditions
指定的bean是否存在,由此来确定是否加载 @Configuration
- @ConditionalOnBean - bean存在时 条件满足
- @ConditionalOnMissingBean - bean不存在时 条件满足
接下来,在 configuration class中增加 数据源定义. 开发者期望同时满足以下两个条件,才会创建bean
- 当容器中存在以 datasource 命名的bean
- 当容器中不存在entityManagerFactory类型的bean
@Bean
@ConditionalOnBean(name = "dataSource")
@ConditionalOnMissingBean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.spring.boot");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
if (additionalProperties() != null) {
em.setJpaProperties(additionalProperties());
}
return em;
}
以同样的方式创建事务管理的bean
@Bean
@ConditionalOnMissingBean(type = "JpaTransactionManager")
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
Property Conditions
开发者可以使用@ConditionalOnProperty注解通过判断Spring 环境中是否存在相关属性来确定是否进行自动装配。
首先,在 resource 目录下创建 mysql.perperties
usemysql=local
mysql-hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
mysql-hibernate.show_sql=true
mysql-hibernate.hbm2ddl.auto=update
其次,使用@PropertySource注解以mysql.perperties是否存在为条件,决定是否加载配置
@PropertySource("classpath:mysql.properties")
public class MySQLAutoconfiguration {
//...
}
接下来,我们使用两种方式创建建数据库连接
- 硬编码方式
@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("root");
dataSource.setPassword("123456");
return dataSource;
}
- 配置文件的方式
@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;
}
上述两个方法将条件装配的特性展现的淋漓尽致,并且对开发者十分友好。可以在配置文件中指定相关属性,实现按需加载。 开发环境加载dataSource,测试环境加载datasource2.
Resource Conditions
@ConditionalOnResource注解表示当指定资源存在时,才会进行自动装配。定义一个additionalProperties() 方法,该方法返回entityManagerFactory bean需要使用的 Hibernate 相关属性。
@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;
}
自定义 Conditions
之前介绍的各种 条件装配注解,基本能满足绝大多数业务需求。此外,Spring Boot 也预留了接口让开发者实现自定义的条件注解。我们来实现一个自定义装配的类,作用是判断 HibernateEntityManager 类是否在classpath中
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() {
//...
}
应用级别 Conditions
此外,开发者可以使用以下注解指定 web 上下文中满足指定条件
- @ConditionalOnWebApplication - web应用才会满足条件
- @ConditionalOnNotWebApplication - 非web应用才会满足条件
验证测试
创建实体文件
package com.spring.boot.entity;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class User {
@Id
private Long id;
private String email;
public User() {
}
public User(String email,Long id) {
super();
this.email = email;
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
创建Repository
public interface MyUserRepository
extends JpaRepository<MyUser, String> { }
开启自动装配
为了开启自动装配启动类上,增加*@SpringBootApplication* 或 @EnableAutoConfiguration注解。
@SpringBootApplication
public class AutoconfigurationApplication {
public static void main(String[] args) {
SpringApplication.run(AutoconfigurationApplication.class, args);
}
}
测试类
import com.spring.boot.AutoConfigurationApplication;
import com.spring.boot.entity.User;
import com.spring.boot.repository.UserRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = AutoConfigurationApplication.class)
@EnableJpaRepositories(basePackages = { "com.spring.boot" })
public class AutoconfigurationTest {
@Autowired
private UserRepository userRepository;
@Test
public void whenSaveUser_thenOk() {
User user = new User("user@email.com",1000L);
userRepository.save(user);
}
}
运行测试方法,并在mysql数据库中相关记录
禁止自动装配
开发者可以通过以下两种方式禁止指定的自动装配
-
代码方式
@Configuration @EnableAutoConfiguration( exclude={MySQLAutoconfiguration.class}) public class AutoconfigurationApplication { //... }
-
配置方式
spring.autoconfigure.exclude=com.baeldung.autoconfiguration.MySQLAutoconfiguration