第三章 高級裝配

完整代碼請見:https://github.com/codercuixi...java

第一部分 @Profile註解的使用

環境與profile 是否啓用某個bean,經常使用於數據庫bean
經過profile啓用不一樣的bean,特別是對於各類不一樣的數據庫(開發線,測試線,正式線),尤爲管用。
1.1第一步 配置profile bean。經過@Profile修飾類或者方法名,來代表這個Bean是能夠動態啓動與否的git

package com.myapp;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean(destroyMethod = "shutdown")
    @Profile("dev")
    public DataSource embeddedDataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:schema.sql")
                .addScript("classpath:test-data.sql")
                .build();
    }

    @Bean
    @Profile("prod")
    public DataSource jndiDataSource() {
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
}

1.2.第二步,激活profile。
經過@ActiveProfile來激活指定的Profile,啓用指定的數據庫Bean。github

package profiles;

import static org.junit.Assert.*;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.myapp.DataSourceConfig;

public class DataSourceConfigTest {

  @RunWith(SpringJUnit4ClassRunner.class)
  @ContextConfiguration(classes=DataSourceConfig.class)
  @ActiveProfiles("dev")
  public static class DevDataSourceTest {
    @Autowired
    private DataSource dataSource;
    
    @Test
    public void shouldBeEmbeddedDatasource() {
      assertNotNull(dataSource);
      JdbcTemplate jdbc = new JdbcTemplate(dataSource);
      List<String> results = jdbc.query("select id, name from Things", new RowMapper<String>() {
        @Override
        public String mapRow(ResultSet rs, int rowNum) throws SQLException {
          return rs.getLong("id") + ":" + rs.getString("name");
        }
      });
      
      assertEquals(1, results.size());
      assertEquals("1:A", results.get(0));
    }
  }

  @RunWith(SpringJUnit4ClassRunner.class)
  @ContextConfiguration(classes=DataSourceConfig.class)
  @ActiveProfiles("prod")
  public static class ProductionDataSourceTest {
    @Autowired
    private DataSource dataSource;
    
    @Test
    public void shouldBeEmbeddedDatasource() {
      // should be null, because there isn't a datasource configured in JNDI
      assertNull(dataSource);
    }
  }
}

1.3.經過兩個參數激活profile
spring.profiles.active和spring.profiles.default,優先使用前者的配置。
設置這兩個參數的方式有以下幾種:(待補充完整)web

  • 做爲DIspatcherServlet的初始化參數
  • 做爲Web應用的上下文參數
  • 做爲JNDI條目
  • 做爲環境變量
  • 做爲JVM的系統屬性
  • 在集成測試類上,使用@ActiveProfile註解設置。(也就是上面第二步演示的)

第二部分 條件化的bean

經過@Conditional, 能夠用到Bean上,當條件爲true,則建立該Bean;不然則不建立。
主要分爲一下三步
1.像往常同樣定義Bean的POJO類
2.編寫org.springframework.context.annotation.Condition接口的類MagicExistsCondition,用來建立是否建立該Bean
3.將@Conditional(MagicExistsCondition.class)應用到Bean的JavaConfig上。正則表達式

package conditional.habuma.restfun;

public class MagicBean {
}
package conditional.habuma.restfun;

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

/**
 * * @Author: cuixin
 * * @Date: 2018/8/30 18:32
 */
public class MagicExistsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        return environment.containsProperty("magic");
    }
}
package conditional.habuma.restfun;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

/**
 * * @Author: cuixin
 * * @Date: 2018/8/30 18:32
 */
@Configuration
public class MagicConfig {

    @Bean
    @Conditional(MagicExistsCondition.class)
    public MagicBean magicBean(){
        return new MagicBean();
    }
}

因爲實現了match方法中帶有兩個參數,咱們能夠經過這兩個參數作到什麼呢?
ConditionContext接口的方法spring

public interface ConditionContext {

    /**
     * 返回BeanDefinitionRegistry,可用來判斷Bean是否認義
     */
    BeanDefinitionRegistry getRegistry();

    /**
     * 返回ConfigurableListableBeanFactory,可用來檢查Bean是否存在,甚至探查Bean的屬性
     */
    @Nullable
    ConfigurableListableBeanFactory getBeanFactory();

    /**
     * 返回Environment,可用來判斷環境變量是否存在,且獲取環境變量的值
     */
    Environment getEnvironment();

    /**
     *返回ResourceLoader,可用來讀取或探查已經加載的資源
     */
    ResourceLoader getResourceLoader();

    /**
     * 返回ClassLoader,可用來加載類或判斷類是否存在
     */
    @Nullable
    ClassLoader getClassLoader();

}

AnnotatedTypeMetadata 用來獲取註解相關信息sql

public interface AnnotatedTypeMetadata {

    
    boolean isAnnotated(String annotationName);
    
    @Nullable
    Map<String, Object> getAnnotationAttributes(String annotationName);
    
    @Nullable
    Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
    
    @Nullable
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
    
    @Nullable
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);

第三部分 處理自動裝配的歧義性。

3.1@AutoWired 註解只能夠裝配只有一個實現類的Bean
例以下面的Dessert有三個實現類,自動裝配時,Spring就會不知道選哪個,於是會報NoUniqueBeanDefinitionException錯誤。數據庫

public interface Dessert {
}
@Component
public class Cake implements Dessert {
}
@Component
public class Cookies implements Dessert {
}
@Component
public class IceCream implements Dessert {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CakeConfig.class)
public class CakeTest {
    @Autowired
    private Dessert dessert;//Spring: emmm.... I don't which one to choose
    @Test
    public void getDessert(){
        assertNotNull(dessert);
    }
}

3.2 @Primary 能夠指定某個實現類做爲優先Bean建立
給蛋糕加個@Primary,代表首選蛋糕做爲首選項。而後在執行Test,發現就不抱錯了。
@Primary能夠配合@Component, @Bean, @Autowired使用。cookie

@Component
@Primary
public class Cake implements Dessert {
}

3.3 @Qualifie將使用的Bean限定到具體的實現類
因爲@Qualifier是基於字符串去匹配Bean id的,因此你修改了類名就可能致使找不到對應的Bean了。但我嘗試了一下,若是使用IDEA的Refactor->Rename,會幫咱們自動更改多處的。
@Qualifie能夠配合@Component, @Bean, @Autowired使用。app

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CakeConfig.class)
public class CakeTest {
    @Autowired
    @Qualifier("cookies")
    private Dessert dessert;
    @Test
    public void getDessert(){
        assertNotNull(dessert);
    }
}

Spring實戰中,爲了解決@Qualifier「不夠用」,拼命地創建自定義註解,我感受是沒有必要,有點多此一舉的感受。

四. bean的做用域

四種不一樣的做用域
單例(Singleton默認):在整個應用中,只建立bean的一個實例。
好比

@Bean
    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    public Notepad notepad() {
        return new Notepad();
    }

原型(Prototype):每次注入或者經過spring應用上下文獲取的時候,都會建立一個新的bean實例。
好比:

@Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Notepad notepad() {
        return new Notepad();
    }

會話(Session):在web應用中,爲每一個會話建立一個bean實例, 舉個例子:

@Bean
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public Notepad notepad() {
        return new Notepad();
    }

請求(Request):在web應用中,爲每一個請求建立一個bean實例,舉個栗子:

@Bean
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public Notepad notepad() {
        return new Notepad();
    }

這裏須要注意的是proxyMode這個屬性。(TODO:經過例子加深理解)
當值爲ScopedProxyMode.TARGET_CLASS時,表示的該bean類型是具體類,只能使用CGLib來生成基於類的代理。
當值爲ScopedProxyMode.INTERFACES時。
Spring做用域代理

五.運行時注入

1.使用@PropertySource, @Environment注入外部值

app.properties的內容
disc.title=Sgt. Peppers Lonely Hearts Club Band
disc.artist=The Beatles

public class BlankDisc {
    private final String title;
    private final String artist;
    public BlankDisc(String title, String artist){
        this.title = title;
        this.artist = artist;
    }
    public String getArtist() {
        return artist;
    }

    public String getTitle() {
        return title;
    }
}

@Configuration
@PropertySource("classpath:/externals/com/soundsystem/app.properties")
public class EnvironmentConfig {
    @Autowired
    private Environment env;

    @Bean
    public BlankDisc blankDisc(){
        return new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"));
    }

}

另外Environment的getProperty有4個重載方式能夠選擇

String getProperty(String key); //獲取指定key的內容;若是找不到key就返回null
String getProperty(String key, String defaultValue);//獲取指定key的內容;若是找不到key,就返回默認值
<T> T getProperty(String key, Class<T> targetType);//targetType用於說明該key的值類型
<T> T getProperty(String key, Class<T> targetType, T defaultValue);

2.屬性佔位符
${...}表示屬性佔位符,常配合@Value使用,舉個栗子。

@Bean
    public BlankDisc blankDisc2(@Value("${disc.title}") String title, @Value("${disc.artist}")String artist){
        return new BlankDisc(title, artist);

    }

3.使用Spring表達式語言進行裝配

1.使用bean的id來引用Bean TODO 待補充實例2.調用方法和訪問對象的屬性3.對峙進行算數,關係和邏輯運算4.正則表達式匹配5.集合操做

相關文章
相關標籤/搜索