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 
![[附源码]Python计算机毕业设计大学生心理健康管理系统](https://img-blog.csdnimg.cn/43419b3d45cf479ba93eded038b91114.png)



![[附源码]java毕业设计学生信息管理系统](https://img-blog.csdnimg.cn/ce16ce78ce624b45bf4f0cdcacc5ef5d.png)


![[附源码]Python计算机毕业设计大学生项目众筹系统](https://img-blog.csdnimg.cn/43da00c80b0f468a93390f82dacd0593.png)










