Spring 按條件裝配使用方法

條件註冊

Spring 4.0 引入條件註冊機制,暴露給用戶的API是@ConditionalCondition接口,把@Conditional聲明在一個 @Component類上,並接受一組條件(Condition實現),容器初始化期間,會調用每一個Condition實現的matches方法進行測試, 只有全部Condition的matches方法都測試通過,纔會在容器中註冊當前實例,這和 @Profile 註解的功能有些類似,但是@Profile 是粗粒度的控制註冊範圍,而使用 @Conditional 能夠能精細實現控制每一個對象的註冊。

如何使用

@Conditional@Component 一起使用, 這其中也就包括 @Configuration 註解,控制當前@Component聲明的組件 是否需要註冊。當和 @Configuration 配置類聯用時,對 @Import@ComponentScan 進一步導入的組件也會有效。

@Conditional 也可以當作元註解使用,聲明更具體的條件註解,例如 @ConditionalOn... 這類註解。

例如需求是隻有程序運行在Linux類系統上,才應該在容器中註冊某個對象,可以參考下面的Condition實現:

public class ConditionOnLinux implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        try {
            Process process = Runtime.getRuntime().exec(new String[]{"uname", "-s"});
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String ostype = reader.readLine();
                if ("Linux".equalsIgnoreCase(ostype))
                    return true;
            }
            return false;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

然後在配置中把ConditionOnLinux實現傳遞給@Conditional:

@Configuration
public class AppConfig {
    //這樣配置後,程序只有運行在Linux類系統中,纔會註冊LinuxObject
    @Bean
    @Conditional(ConditionOnLinux.class)
    public LinuxObject linuxObject() {
        return new LinuxObject();
    }
}

原理分析

當容器註冊BeanDefinition之前,Spring會先檢查@Conditional條件是否成立,關鍵就是@Conditional註解在什麼時機發生作用, 這個過程可以跟蹤AnnotationConfigApplicationContext 容器的啓動流程,涉及到兩處實現對@Conditional的處理。

(1)對@Configuration配置類的註冊

第一點是對@Configuration類本身的註冊時機,這在我們調用AnnotationConfigApplicationContext#register(annotatetClasses) 方法的流程中發生,這個動作是委託到AnnotatedBeanDefinitionReader對象的register方法實現的,下面是這個方法的關鍵代碼:

public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
    //對配置類創建BeanDefinition對象
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
    //檢測是否跳過註冊,shouldSkip方法返回true就跳過註冊,下面有進一步說明
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
        return;
    }
    
    //這裏省略掉一些解析BeanDefinition的其他屬性的代碼,包括名稱、scope等
    
    //最後註冊BeanDefinition:
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

那繼續看一看ConditionEvaluator#shouldSkip方法的內部細節處理,添加了註釋說明流程細節:

public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
    //表示如果類沒有@Conditional註解修飾,則返回false,返回false則上層調用處不跳過註冊
    if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
        return false;
    }

    //按照註冊所處的階段進行分發,遞歸調用shouldSkip
    if (phase == null) {
        if (metadata instanceof AnnotationMetadata &&
                ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
            return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
        }
        return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
    }

    //從@Conditional中獲取並創建所有的Condition對象,放到conditions列表中
    List<Condition> conditions = new ArrayList<Condition>();
    for (String[] conditionClasses : getConditionClasses(metadata)) {
        for (String conditionClass : conditionClasses) {
            Condition condition = getCondition(conditionClass, this.context.getClassLoader());
            conditions.add(condition);
        }
    }

    //對Condition對象排序,因此用戶在實現Condition接口時也可以實現Order接口,維護Condition的順序
    AnnotationAwareOrderComparator.sort(conditions);

    //對每一個Condition對象進行校驗
    for (Condition condition : conditions) {
        ConfigurationPhase requiredPhase = null;
        if (condition instanceof ConfigurationCondition) {
            requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
        }
        if (requiredPhase == null || requiredPhase == phase) {
            //這裏看到,對於所有Condition對象,只要有一個的matches方法校驗不成立,shouldSkip方法就返回true,
            //這樣調用處就會跳過註冊
            if (!condition.matches(this.context, metadata)) {
                return true;
            }
        }
    }

    return false;
}

(2)ConfigurationClassPostProcessor後處理器工作階段

上述是說明的是@Configuration配置類如何被註冊到容器中,這個階段在容器refresh之前。

還有 @Import、@ComponentScan、@Bean 等這些註解導入的bean是怎麼處理註冊的呢,這是在第二個階段容器refresh過程中發生, 這個過程中會調用容器中所有的BeanFactoryPostProcessor,其中有一個叫做ConfigurationClassPostProcessor的後處理器 會解析已註冊的@Configuration配置類,這個過程中會處理 @Import、@ComponentScan、@Bean 這些註解。

ConfigurationClassPostProcessor實現的是BeanDefinitionRegistryPostProcessor,因此可以在 postProcessBeanDefinitionRegistry方法中跟蹤是如何解析並註冊由@Configuration導入的那些bean的,重點關注在 ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()方法中對@Conditional的處理。

(3)ConfigurationClassPostProcessor本身的註冊機制

最後有一點,ConfigurationClassPostProcessor這個後處理器本身是如何註冊到容器中的呢,它是Spring的一個內置後處理器, 在最開始創建AnnotationConfigApplicationContext的過程中,就在底層beanFacntory容器中註冊了,具體在構造方法創建 其內部的reader對象的過程中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章