完整代碼請見:https://github.com/codercuixi...java
環境與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
經過@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「不夠用」,拼命地創建自定義註解,我感受是沒有必要,有點多此一舉的感受。
四種不一樣的做用域
單例(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時。
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); }
1.使用bean的id來引用Bean TODO 待補充實例2.調用方法和訪問對象的屬性3.對峙進行算數,關係和邏輯運算4.正則表達式匹配5.集合操做