Spring裝配Bean的一些高級技巧

1、使用@Profile註解來實如今不一樣環境下建立不一樣的Beanjava

  • 實現方式:將不一樣的Bean定義整理到對應環境的Profile中,當應用部署到不一樣的環境時(開發環境或者是QA環境或者是生產環境),激活對應的Profile,則相應環境的Bean就會在運行時被建立,非當前環境的Profile不會被建立,沒有指定@Profile註解的Bean始終會被建立。
  • @Profile註解能夠用在類級別上或者方法級別上。

舉例:mysql

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

@Configuration
public class DataSourceProfiles {
    @Bean
    @Profile("development")
    public DataSource deveDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://11.11.11.11:3306/demodb");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean
    @Profile("qa")
    public DataSource qaDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://11.11.11.11:3306/demodb");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean
    @Profile("product")
    public DataSource productDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://11.11.11.11:3306/demodb");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
    
    @Bean
    public SomeOtherBean getBean(){
        return new SomeOtherBean();
    }
}
View Code

上面的例子中,「development」這個Profile激活時,會建立development環境的Bean和SomeOtherBean,其餘兩個Bean不會被建立。spring

  • 激活某個Profile的方式

Spring有兩個獨立的屬性來肯定哪一個Profile被激活:spring.profile.active和spring.profile.default,若是設置了spring.profile.active屬性,則它的值用來肯定激活的Profile,若是沒有設置spring.profile.active,可是設置了spring.profile.default,則spring.profile.default的值用來肯定激活的Profile,若是spring.profile.active和spring.profile.default均沒有設置,則沒有激活的Profile,此時只會建立哪些沒有定義在Profile中的Bean。有多種方式來定義這兩個屬性:sql

  1. 做爲DispatcherServlet的初始化參數;
  2. 做爲Web應用上下文的參數;
  3. 做爲環境變量;
  4. 做爲JNDI條目;
  5. 做爲JVM的系統屬性;
  6. 在集成測試時,使用@ActiveProfile註解設置;

2、條件化的裝配Beanide

能夠設置不一樣的條件來控制Bean的建立:測試

  • 只有在某個特定環境變量設置以後,才建立某個Bean;
  • 應用的類路徑下包含特定的庫才建立某個Bean(這也是SpringBoot的自動化裝配的實現方式,當引入了某個特定依賴時,相應的Bean就會被自動建立);
  • 只有某個Bean被建立後,纔會建立另外一個Bean;

舉例:當環境變量中設置了magc屬性時,才建立MagicExistCondition這個Bean,進一步的,只有MagicExistCondition建立後,才建立MagicBean這個Bean,具體實現方式以下:spa

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class MagicExistCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){
        Environment env = context.getEnvironment();
        return env.containsProperty("magic");
    }
}
View Code

上述代碼中MagicExistCondition實現了Condition接口,只有實現了這個接口的類,才能夠做爲條件類,用在@Conditional註解中。Condition這個接口很簡單,如上面的例子,只須要實現matches方法便可。code

接下來,判斷MagicExistCondition被建立後,才建立MagicBean這個Bean:blog

    @Bean
    @Conditional(MagicExistCondition.class)
    public MagicBean magicBean(){
        reutrn new MagicBean();
    }
View Code

再次看看Condition接口中的matche方法,這個方法有兩個參數:ConditionContext和AnnotatedTypeMetadata。接口

ConditionContext是一個接口:

package org.springframework.context.annotation;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;

public interface ConditionContext {
    BeanDefinitionRegistry getRegistry();

    ConfigurableListableBeanFactory getBeanFactory();

    Environment getEnvironment();

    ResourceLoader getResourceLoader();

    ClassLoader getClassLoader();
}
View Code
  • getRegistry()方法返回的BeanDefinitionRegistry能夠檢查Bean的定義
  • getBeanFactory()方法返回的ConfigurableListableBeanFactory能夠檢查Bean是否存在,甚至進一步探查Bean的屬性
  • getEnvironment()方法返回的Environment檢查環境變量是否存在以及它的值是什麼
  • getResourceLoader()方法返回的ResourceLoader用於探查所加載的資源
  • getClassLoader()方法返回的ClassLoader用於加載並檢查類是否存在

AnnotatedTypeMetadata也是接口:

import java.util.Map;
import org.springframework.util.MultiValueMap;

public interface AnnotatedTypeMetadata {
    boolean isAnnotated(String var1);

    Map<String, Object> getAnnotationAttributes(String var1);

    Map<String, Object> getAnnotationAttributes(String var1, boolean var2);

    MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);

    MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
}
View Code

該接口主要用來探查帶有@bean註解的類上面是否還有其餘的註解,而且檢查那些註解的屬性值。舉個例子,回到@Profile這個註解,這個註解用於控制當前@bean註解的類在特定Profile激活時才被建立,那麼,@Profile註解是如何實現這個功能的呢,這裏就須要藉助AnnotatedTypeMetadata這個接口了,從Spring 4開始,@Profile註解基於@Conditional和Condition來實現:

首先看看@Condtion註解:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

其中的ProfileCondition類實現了Condition接口的matches方法:

package org.springframework.context.annotation;

import java.util.Iterator;
import java.util.List;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;

class ProfileCondition implements Condition {
    ProfileCondition() {
    }

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        if (context.getEnvironment() != null) {
            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((String[])((String[])value)));

                return true;
            }
        }

        return true;
    }
}
View Code

能夠看到:matches方法中,首先給metadata.getAllAnnotationAttributes()方法傳遞Profile.class.getName()這個Profile Bean的名字,獲得全部註解的屬性,並逐個遍歷判斷,藉助acceptsProfiles方法來見擦好該Profile是否被激活!!!

3、處理自動裝配的歧義性

當一個接口有多個實現類時,若是某個類須要注入接口類,此時會拋出NoUniqueBeanDefinitionException異常,解決方法是:

  1. 使用@Primary標註首選的Bean
  2. 使用@Qualifier註解@Qualifier(「specialBean」)限定符
  3. 建立自定義的限定符註解
相關文章
相關標籤/搜索