@Profile註解 -【Spring底層原理】

blog56

1、註解用法

在咱們開發開發測試部署當中,有不一樣的環境,好比有:開發環境、測試環境、上產環境,不一樣的環境有不一樣的組件,這聽着怎麼那麼像springboot中的多環境配置呢?今天,我們來看看在spring中是如何實現的。java

爲了多環境開發,Spring爲咱們提供的能夠根據當前環境,動態的激活和切換一系列組件的功能,好比數據源組件的配置,不一樣開發環境鏈接的數據源可能會不一樣,就可使用@Profile註解進行配置,根據環境動態切換數據源組件。mysql

@Profile:指定組件在哪一個環境下才能被註冊到容器中,不指定則任何環境都能註冊這個組件,加了環境標識的bean,只有這個環境被激活的時候才能註冊到容器spring

2、實例分析

就以數據源配置爲例,不一樣環境數據源的配置每每不一樣,如何使用@Profile註解在不一樣環境下進行數據源的註冊呢,經過實例來進行分析。sql

【1】@Profile環境搭建springboot

// 啓動測試類
@Test
public void TestMain() {
    // 建立IOC容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
    for (String string : namesForType) {
        System.out.println(string);
    }
    // applicationContext.close();
}

// 配置類
@Configuration
public class AppConfig {

    // 測試環境
    // @Profile("test")
    @Bean("testDataSource")
    public DataSource dataSourceTest() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("806188");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/vhr");
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    // 開發環境
    // @Profile("dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("806188");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/vhr");
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    // 生產環境
    // @Profile("pro")
    @Bean("proDataSource")
    public DataSource dataSourcePro() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("806188");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/vhr");
        dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }
}
複製代碼

能夠看到,在配置類中,有不一樣的數據源配置,分別是測試環境、開發環境、生產環境,咱們能夠經過@Profile註解來指定注入哪一種環境,當不指定則任何環境都能註冊這個組件,也就是上面代碼,運行測試類,輸出結果以下,三個數據源組價都進行了注入:markdown

image-20210316111408626

將數據源使用@Profile註解進行標識,也就是將上面代碼三個@Profile註解打開,此時由於沒有激活註冊環境,因此這個三個數據源都不能被注入。下面來進行激活。app

【2】激活註冊環境oop

  • default默認環境:@Profile("default")
  • 使用命令行動態參數:-Dspring.profiles.active=dev
  • 使用代碼手動激活指定環境:要使用無參構造方法
  • 配置在配置類上:只有在指定環境的時候,整個配置類的全部配置才能生效
  1. default默認環境,使用@Profile("default"),標識默認當前環境
// 測試環境
@Profile("default")
// @Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest() throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser("root");
    dataSource.setPassword("806188");
    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/vhr");
    dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
    return dataSource;
}
複製代碼

image-20210316114109583

  1. 使用命令行動態參數,編輯運行配置的VM options,參數爲:-Dspring.profiles.active=dev,經過該配置來標註是何種環境

image-20210316114355679

運行啓動類,能夠看到devDataSource被注入:測試

image-20210316114612732

  1. 使用代碼手動激活指定環境,使用這種方法不能讓有參構造器代碼執行,由於執行有參構造器加載配置類的時候,執行refresh()方法容器就啓動刷新了,就將配置寫死了,因此這裏要用無參構造器,手動激活指定環境。修改啓動類:
@Test
public void TestMain() {
    // 建立IOC容器
    // AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    // 設置須要激活的環境
    applicationContext.getEnvironment().setActiveProfiles("pro");
    // 註冊主配置類
    applicationContext.register(AppConfig.class);
    // 啓動刷新容器
    applicationContext.refresh();
    String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
    for (String string : namesForType) {
        System.out.println(string);
    }
}
複製代碼

修改後,手動設置須要激活的環境,運行啓動類,輸出結果以下:spa

image-20210316142824671

  1. 在配置類上加@Profile註解,則只有在指定環境的時候,整個配置類的全部配置才能生效,以下:
@Profile("test")
@Configuration
public class AppConfig {...}
複製代碼

運行啓動類,此時啓動類是手動代碼配置了pro環境,由於配置類上是@Profile("test"),只有在test環境下該配置類纔會生效,因此沒有輸出:

image-20210316144725462

3、源碼追蹤

查看@Profile註解源碼,咱們能夠看到,@Profile其實是一個@Conditional註解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
    String[] value();
}
複製代碼

@Conditional註解在前面的文章講解過,能夠進行參考:@Conditional註解 -【Spring底層原理】,這裏簡單分析一下

@Conditinal是一個條件註解,參數是一個class,這個class都要實現Condition接口,重寫matches()方法。例如,上面示例代碼中的ProfileCondition.class

class ProfileCondition implements Condition {
    ProfileCondition() {
    }
	// Spring從ConditionContext中拿到激活的Profile和註解上的字符串進行比對,以決定是否實例化這個bean
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            Iterator var4 = ((List)attrs.get("value")).iterator();

            Object value;
            do {
                if (!var4.hasNext()) {
                    return false;
                }

                value = var4.next();
            } while(!context.getEnvironment().acceptsProfiles(Profiles.of((String[])((String[])value))));

            return true;
        } else {
            return true;
        }
    }
}
複製代碼
  • mathces()方法的返回值是一個布爾值,返回true時,spring就會建立這個bean,返回false時就不會建立
  • mathes()方法上有兩個參數,分別是ConditionContextAnnotatedTypeMetadata,這連個參數中包含了大量的信息,ConditionContext中有Environment, ClassLoader等信息,AnnotatedTypeMetadata能夠得到註解的信息
  • Spring從ConditionContext中拿到激活的Profile和註解上的字符串進行比對,以決定是否實例化這個bean

4、總結

@Profile註解是用來指定組件在哪一個環境下才能被註冊到容器中的,只有這個環境被激活的時候才能註冊到容器,激活總結爲如下幾種方式:

  • default默認環境:@Profile("default")
  • 使用命令行動態參數:-Dspring.profiles.active=dev
  • 使用代碼手動激活指定環境:要使用無參構造方法
  • 配置在配置類上:只有在指定環境的時候,整個配置類的全部配置才能生效
相關文章
相關標籤/搜索