Spring Boot 可以根据classpath中依赖关系自动装配应用程序。通过自动装配机制,可以使开发更快、更简单。今天,学习下如何在Spring Boot 中创建自定义 auto-configuration。
首先添加以下依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.7.5version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
<version>2.7.5version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.19version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-testartifactId>
<version>2.7.5version>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>javax.xml.bindgroupId>
<artifactId>jaxb-apiartifactId>
<version>2.3.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.23version>
<scope>testscope>
dependency>
为了创建自动装配机制,首先创建一个类,并使用 @Configuration注解标记。以创建一个Mysql数据源为例
@Configuration
public class MySQLAutoconfiguration {
//...
}
接下来,需要将类注册到Spring Boot 容器中。创建 resources/META-INF/spring.factories文件, 内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.spring.boot.autoconfig.MySQLAutoconfiguration
Conditions 系列注解允许开发者定义 当指定条件满足时才会加载 auto-configuration.
@Configuration
@ConditionalOnClass(DataSource.class)
public class MySQLAutoconfiguration {
//...
}
上述代码指定当 DataSource.class 存在时,@Configuration 装配的逻辑才生效。
指定的bean是否存在,由此来确定是否加载 @Configuration
接下来,在 configuration class中增加 数据源定义. 开发者期望同时满足以下两个条件,才会创建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;
}
开发者可以使用@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.
@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;
}
之前介绍的各种 条件装配注解,基本能满足绝大多数业务需求。此外,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() {
//...
}
此外,开发者可以使用以下注解指定 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;
}
}
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("[email protected]",1000L);
userRepository.save(user);
}
}
运行测试方法,并在mysql数据库中相关记录
开发者可以通过以下两种方式禁止指定的自动装配
代码方式
@Configuration
@EnableAutoConfiguration(
exclude={MySQLAutoconfiguration.class})
public class AutoconfigurationApplication {
//...
}
配置方式
spring.autoconfigure.exclude=com.baeldung.autoconfiguration.MySQLAutoconfiguration