1. 程式人生 > >Spring高階裝配(一) profile Spring高階裝配(一) profile

Spring高階裝配(一) profile Spring高階裝配(一) profile

Spring高階裝配(一) profile

Spring高階裝配要學習的內容包括:

  • Spring profile
  • 條件化的bean宣告
  • 自動裝配與歧義性
  • bean的作用域
  • Spring表示式語言

以上屬於高階一點的bean裝配技術,如果你沒有啥特別的需求的話用的還比較少。但是用於解決變態一點的需求還是要學一下留個備份。

環境與Profile

直接上情形吧,一個專案現在有三個階段,不同階段使用的dataSource的來源不一樣,分別是:

  1. 開發階段:使用嵌入式的Hypersonic資料庫
  2. QA階段:使用不同DataSource配置,比如Common DBCP連線池
  3. 生產階段:從JNDI容器中獲取一個DataSource

這三種DataSource bean的生成程式碼分別是:

嵌入式的Hypersonic資料庫:

複製程式碼
1 @Bean(destroyMethod="shutdown")
2 public DataSource dataSource() {
3     return new EmbeddedDataSourceBuilder()
4         .addScript("classpath:schema.sql")
5 .addScript("classpath:test-data.sql") 6 .build(); 7 }
複製程式碼

 

 JNDI:

複製程式碼
1 @Bean
2 public DataSource dataSource() {
3     JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
4     jndiObjectFactoryBean.setJndiName("jdbc/myDS");
5 jndiObjectFactoryBean.setResourceRef(true); 6 jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); 7 return (DataSource) jndiObjectFactoryBean.getObject(); 8 }
複製程式碼

 

 

Common DBCP:

複製程式碼
 1 @Bean(destroyMethod="close")
 2 public DataSource dataSource() {
 3     BasicDataSource dataSource = new BasicDataSource();
 4     dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
 5     dataSource.setDriverClassName("org.h2.Driver");
 6     dataSource.setUserName("sa");
 7     dataSource.setPassword("password");
 8     dataSource.setInitialSize(20);
 9     dataSource.setMaxActive(30);
10     return dataSource;
11 }
複製程式碼

 

也就是說每個階段都是用了完全不同的策略來生成DataSource的bean。現在有一個需求是:如何優雅地切換這三種DataSource?

如果只用到基礎的Spring bean的裝配知識的話,我們必須每次手動的加上要轉入的階段對應的DataSource bean定義程式碼。這樣的話容易引入bug,而且不優雅。這種情況其實可以抽象一下:根據不同的情況,生成不同的bean

Spring針對這種根據環境來決定建立哪個bean和不建立哪個bean提供了了一種解決方案:profile。profile使用的大致流程:

 

配置profile bean

Spring利用profile來感覺環境決定建立哪個bean和不建立哪個bean,並不是在構建的時候做出決策,而是在執行時再決定。這樣的話程式碼就可以適用於所有的環境,而不是需要額外重構。

在使用profile的時候(since 3.1),首先要把不同的bean定義整理到一個或者多個profile中,在將應用部署到每個環境時,要確保對應的profile處於啟用(active)狀態。

在Java配置中使用@Profile指定某個bean屬於哪個profile。先來一個直接一點的例子:

複製程式碼
 1 @Configuration
 2 @Profile("dev")
 3 public class DevelopmentProfileConfig {
 4 
 5     @Bean(destroyMethod="shutdown")
 6     public DataSource dataSource() {
 7         return new EmbeddedDataSourceBuilder()
 8             .addScript("classpath:schema.sql")
 9             .addScript("classpath:test-data.sql")
10             .build();
11     }
12 }
複製程式碼

 

解釋說明:

  • @Profile應用在了類級別上
  • 這個配置類中的bean只有在dev profile被啟用的時候才會被建立。
  • 如果dev profile沒有被啟用,那麼帶有@Bean註解的方法都會被忽略。

在給出一個適用於生產環境的配置:

複製程式碼
 1 @Configuration
 2 @Profile("prod")
 3 public class ProductionProfileConfig {
 4     @Bean
 5     public DataSource dataSource() {
 6         JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
 7         jndiObjectFactoryBean.setJndiName("jdbc/myDS");
 8         jndiObjectFactoryBean.setResourceRef(true);
 9         jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
10         return (DataSource) jndiObjectFactoryBean.getObject();
11     }
12 }
複製程式碼

在Spring 3.1中只能在類級別上使用@Profile註解,3.2開始,也可以在方法級別上使用@Profile註解,與@Bean註解一同使用;這樣的話可以把這兩個bean的宣告放到同一個配置類中:

複製程式碼
 1 @Configuration
 2 public class DataSourceConfig {
 3     @Bean(destroyMethod="shutdown")
 4     @Profile("dev")
 5     public DataSource dataSource() {
 6         return new EmbeddedDataSourceBuilder()
 7             .addScript("classpath:schema.sql")
 8             .addScript("classpath:test-data.sql")
 9             .build();
10     }
11 
12     @Bean
13     @Profile("prod")
14     public DataSource dataSource() {
15         JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
16         jndiObjectFactoryBean.setJndiName("jdbc/myDS");
17         jndiObjectFactoryBean.setResourceRef(true);
18         jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
19         return (DataSource) jndiObjectFactoryBean.getObject();
20     }
21 }
複製程式碼

 

這樣一來每個DataSource的bean都被宣告在配置類中,並且只有當規定的profile啟用時,相應的bean才會被建立;沒有指定profile的bean始終都會被建立,與啟用哪個profile沒有關係。

在XML中配置profile

通過<beans>元素的profile屬性,在XML中配置profile bean:

1 <beans profile="dev">
2     <jdbc:embedded-database id="dataSource">
3         <jdbc:script location="classpath:schema.sql" />
4         <jdbc:script location="classpath:test-data.sql" />
5     </jdbc:embedded-database>
6 </beans>

 

同理,可以通過把profile設定為prod,建立適用於生產環境的從JNDI獲取的DataSource bean;也可以建立基於連線池定義的dataSource bean,將其放在另一個XML檔案中,並標註為qa profile。所有的配置檔案都會放在部署單元之中(如WAR檔案),但是隻有profile屬性與當前啟用的profile相匹配的配置檔案才會被用到。

如果覺得定義的配置檔案太多,你可以在根<beans>中巢狀定義<beans>元素,而是不是為每個環境建立一個profile XML檔案,配置程式碼如下:

複製程式碼
 1 <beans>
 2 
 3     <beans profile="dev">
 4         <jdbc:embedded-database id="dataSource">
 5             <jdbc:script location="classpath:schema.sql" />
 6             <jdbc:script location="classpath:test-data.sql" />
 7         </jdbc:embedded-database>
 8     </beans>
 9 
10     <beans profile="qa">
11         <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
12             p:url="jdbc:h2:tcp://dbserver/~/test"
13             p:driverClassName="org.h2.Driver"
14             p:username="sa"
15             p:password="password"
16             p:initialSize="20"
17             p:maxActive="30" />
18     </beans>
19 
20     <beans profile="prod">
21         <jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatabase"
22             resource-ref="true" proxy-interface="javax.sql.DataSource" />
23     </beans>
24 
25 </beans>
複製程式碼

 

啟用profle

把profile配置好了之後,問題是怎麼啟用這些profile?

Spring在確定哪個profile處於啟用狀態時,需要依賴兩個獨立的屬性:

  • spring.profiles.active
  • spring.profiles.default

如果設定了spring.profiles.active屬性的話,那麼它的值就會用來確定哪個profile是啟用的。

如果沒有設定spring.profiles.active屬性的話,那Spring將會查詢spring.profiles.default的值。

如果active和default都沒有設定,那麼就沒有啟用的profile,因此只會啟用那些沒有定義在profile中的bean。

設定啟用屬性的方法:

  • 作為DispatcherServlet的初始化引數
  • 作為Web應用的上下文引數
  • 作為JNDI條目
  • 作為環境變數
  • 作為JVM的系統屬性
  • 在整合測試類上,使用@ActiveProfiles註解設定

推薦的方式使用時DispatcherServlet的引數將spring.profiles.default設定為開發環境的profile,會在Servlet上下文中進行設定,在web.xml中:

複製程式碼
 1 <web-app>
 2 
 3     <!-- 為上下文設定預設的profile -->
 4     <context-param>
 5         <param-name>spring.profiles.default</param-name>
 6         <param-value>dev</param-value>
 7     </context-param>
 8     
 9     <!-- 為Servlet設定預設的profile -->
10     <servlet>
11         <servlet-name>appServlet</servlet-name>
12         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
13         <init-param>
14             <param-name>spring.profiles.default</param-name>
15             <param-value>dev</param-value>
16         </init-param>
17         <load-on-startup>1</load-on-startup>
18 </web-app>
複製程式碼

可以通過列出多個profile名稱並以逗號分隔來同時啟用多個profile。不過同時啟用dev和prod可能沒有太大的意義,但是可以同時設定多個彼此不相關的profile。

整合測試時使用@ActiveProfiles註解來指定測試時要啟用的profile。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {

}

 

Spring的profile提供了一種很好的條件化建立bean的方法,這裡的條件是基於哪個profile處於啟用狀態來判斷。Spring 4.0提供了一種更為通用的機制來實現條件化的bean定義。

分類: Spring 標籤: spring, profile, 條件化, bean 好文要頂 關注我 收藏該文 tuhooo
關注 - 1
粉絲 - 50 +加關注 0 0 « 上一篇: mysql的騷操作:自增長的欄位同時插入到另一個欄位
» 下一篇: Spring高階裝配(二) 條件化的bean
posted @ 2018-06-29 17:25 tuhooo 閱讀( 29) 評論( 0) 編輯 收藏

Spring高階裝配要學習的內容包括:

  • Spring profile
  • 條件化的bean宣告
  • 自動裝配與歧義性
  • bean的作用域
  • Spring表示式語言

以上屬於高階一點的bean裝配技術,如果你沒有啥特別的需求的話用的還比較少。但是用於解決變態一點的需求還是要學一下留個備份。

環境與Profile

直接上情形吧,一個專案現在有三個階段,不同階段使用的dataSource的來源不一樣,分別是:

  1. 開發階段:使用嵌入式的Hypersonic資料庫
  2. QA階段:使用不同DataSource配置,比如Common DBCP連線池
  3. 生產階段:從JNDI容器中獲取一個DataSource

這三種DataSource bean的生成程式碼分別是:

嵌入式的Hypersonic資料庫:

複製程式碼
1 @Bean(destroyMethod="shutdown")
2 public DataSource dataSource() {
3     return new EmbeddedDataSourceBuilder()
4         .addScript("classpath:schema.sql")
5         .addScript("classpath:test-data.sql")
6         .build();
7 }
複製程式碼

 

 JNDI:

複製程式碼
1 @Bean
2 public DataSource dataSource() {
3     JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
4     jndiObjectFactoryBean.setJndiName("jdbc/myDS");
5     jndiObjectFactoryBean.setResourceRef(true);
6     jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
7     return (DataSource) jndiObjectFactoryBean.getObject();
8 }
複製程式碼

 

 

Common DBCP:

複製程式碼
 1 @Bean(destroyMethod="close")
 2 public DataSource dataSource() {
 3     BasicDataSource dataSource = new BasicDataSource();
 4     dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
 5     dataSource.setDriverClassName("org.h2.Driver");
 6     dataSource.setUserName("sa");
 7     dataSource.setPassword("password");
 8     dataSource.setInitialSize(20);
 9     dataSource.setMaxActive(30);
10     return dataSource;
11 }
複製程式碼

 

也就是說每個階段都是用了完全不同的策略來生成DataSource的bean。現在有一個需求是:如何優雅地切換這三種DataSource?

如果只用到基礎的Spring bean的裝配知識的話,我們必須每次手動的加上要轉入的階段對應的DataSource bean定義程式碼。這樣的話容易引入bug,而且不優雅。這種情況其實可以抽象一下:根據不同的情況,生成不同的bean

Spring針對這種根據環境來決定建立哪個bean和不建立哪個bean提供了了一種解決方案:profile。profile使用的大致流程:

 

配置profile bean

Spring利用profile來感覺環境決定建立哪個bean和不建立哪個bean,並不是在構建的時候做出決策,而是在執行時再決定。這樣的話程式碼就可以適用於所有的環境,而不是需要額外重構。

在使用profile的時候(since 3.1),首先要把不同的bean定義整理到一個或者多個profile中,在將應用部署到每個環境時,要確保對應的profile處於啟用(active)狀態。

在Java配置中使用@Profile指定某個bean屬於哪個profile。先來一個直接一點的例子:

複製程式碼
 1 @Configuration
 2 @Profile("dev")
 3 public class DevelopmentProfileConfig {
 4 
 5     @Bean(destroyMethod="shutdown")
 6     public DataSource dataSource() {
 7         return new EmbeddedDataSourceBuilder()
 8             .addScript("classpath:schema.sql")
 9             .addScript("classpath:test-data.sql")
10             .build();
11     }
12 }
複製程式碼

 

解釋說明:

  • @Profile應用在了類級別上
  • 這個配置類中的bean只有在dev profile被啟用的時候才會被建立。
  • 如果dev profile沒有被啟用,那麼帶有@Bean註解的方法都會被忽略。

在給出一個適用於生產環境的配置:

複製程式碼
 1 @Configuration
 2 @Profile("prod")
 3 public class ProductionProfileConfig {
 4     @Bean
 5     public DataSource dataSource() {
 6         JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
 7         jndiObjectFactoryBean.setJndiName("jdbc/myDS");
 8         jndiObjectFactoryBean.setResourceRef(true);
 9         jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
10         return (DataSource) jndiObjectFactoryBean.getObject();
11     }
12 }
複製程式碼

在Spring 3.1中只能在類級別上使用@Profile註解,3.2開始,也可以在方法級別上使用@Profile註解,與@Bean註解一同使用;這樣的話可以把這兩個bean的宣告放到同一個配置類中:

複製程式碼
 1 @Configuration
 2 public class DataSourceConfig {
 3     @Bean(destroyMethod="shutdown")
 4     @Profile("dev")
 5     public DataSource dataSource() {
 6         return new EmbeddedDataSourceBuilder()
 7             .addScript("classpath:schema.sql")
 8             .addScript("classpath:test-data.sql")
 9             .build();
10     }
11 
12     @Bean
13     @Profile("prod")
14     public DataSource dataSource() {
15         JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
16         jndiObjectFactoryBean.setJndiName("jdbc/myDS");
17         jndiObjectFactoryBean.setResourceRef(true);
18         jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
19         return (DataSource) jndiObjectFactoryBean.getObject();
20     }
21 }
複製程式碼

 

這樣一來每個DataSource的bean都被宣告在配置類中,並且只有當規定的profile啟用時,相應的bean才會被建立;沒有指定profile的bean始終都會被建立,與啟用哪個profile沒有關係。

在XML中配置profile

通過<beans>元素的profile屬性,在XML中配置profile bean:

1 <beans profile="dev">
2     <jdbc:embedded-database id="dataSource">
3         <jdbc:script location="classpath:schema.sql" />
4         <jdbc:script location="classpath:test-data.sql" />
5     </jdbc:embedded-database>
6 </beans>

 

同理,可以通過把profile設定為prod,建立適用於生產環境的從JNDI獲取的DataSource bean;也可以建立基於連線池定義的dataSource bean,將其放在另一個XML檔案中,並標註為qa profile。所有的配置檔案都會放在部署單元之中(如WAR檔案),但是隻有profile屬性與當前啟用的profile相匹配的配置檔案才會被用到。

如果覺得定義的配置檔案太多,你可以在根<beans>中巢狀定義<beans>元素,而是不是為每個環境建立一個profile XML檔案,配置程式碼如下:

複製程式碼
 1 <beans>
 2 
 3     <beans profile="dev">
 4         <jdbc:embedded-database id="dataSource">
 5             <jdbc:script location="classpath:schema.sql" />
 6             <jdbc:script location="classpath:test-data.sql" />
 7         </jdbc:embedded-database>
 8     </beans>
 9 
10     <beans profile="qa">
11         <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
12             p:url="jdbc:h2:tcp://dbserver/~/test"
13             p:driverClassName="org.h2.Driver"
14             p:username="sa"
15             p:password="password"
16             p:initialSize="20"
17             p:maxActive="30" />
18     </beans>
19 
20     <beans profile="prod">
21         <jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatabase"
22             resource-ref="true" proxy-interface="javax.sql.DataSource" />
23     </beans>
24 
25 </beans>
複製程式碼

 

啟用profle

把profile配置好了之後,問題是怎麼啟用這些profile?

Spring在確定哪個profile處於啟用狀態時,需要依賴兩個獨立的屬性:

  • spring.profiles.active
  • spring.profiles.default

如果設定了spring.profiles.active屬性的話,那麼它的值就會用來確定哪個profile是啟用的。

如果沒有設定spring.profiles.active屬性的話,那Spring將會查詢spring.profiles.default的值。

如果active和default都沒有設定,那麼就沒有啟用的profile,因此只會啟用那些沒有定義在profile中的bean。

設定啟用屬性的方法:

  • 作為DispatcherServlet的初始化引數
  • 作為Web應用的上下文引數
  • 作為JNDI條目
  • 作為環境變數
  • 作為JVM的系統屬性
  • 在整合測試類上,使用@ActiveProfiles註解設定

推薦的方式使用時DispatcherServlet的引數將spring.profiles.default設定為開發環境的profile,會在Servlet上下文中進行設定,在web.xml中:

複製程式碼
 1 <web-app>
 2 
 3     <!-- 為上下文設定預設的profile -->
 4     <context-param>
 5         <param-name>spring.profiles.default</param-name>
 6         <param-value>dev</param-value>
 7     </context-param>
 8     
 9     <!-- 為Servlet設定預設的profile -->
10     <servlet>
11         <servlet-name>appServlet</servlet-name>
12         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
13         <init-param>
14             <param-name>spring.profiles.default</param-name>
15             <param-value>dev</param-value>
16         </init-param>
17         <load-on-startup>1</load-on-startup>
18 </web-app>
複製程式碼

可以通過列出多個profile名稱並以逗號分隔來同時啟用多個profile。不過同時啟用dev和prod可能沒有太大的意義,但是可以同時設定多個彼此不相關的profile。

整合測試時使用@ActiveProfiles註解來指定測試時要啟用的profile。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {

}

 

Spring的profile提供了一種很好的條件化建立bean的方法,這裡的條件是基於哪個profile處於啟用狀態來判斷。Spring 4.0提供了一種更為通用的機制來實現條件化的bean定義。