Spring Boot Auto-Configuration

Spring 自定义Auto-Configuration

Spring Boot 可以根据classpath中依赖关系自动装配应用程序。通过自动装配机制,可以使开发更快、更简单。今天,学习下如何在Spring Boot 中创建自定义 auto-configuration。

代码运行环境

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

  1. 当容器中存在以 datasource 命名的bean
  2. 当容器中不存在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
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值