2017年12月21日 星期四

Java Spring - Configuring profile beans


  • 在開發環境中,遷移是一個挑戰(比如Dev->QA, QA->Prod)
    • 假設有個DB config,在Dev上可能使用的是EmbeddedDatabaseBuilder
    • @Bean(destroyMethod="shutdown")
      public DataSource dataSource() {
          return new EmbeddedDatabaseBuilder()
             .addScript("classpath:schema.sql") //schema會定義在schema.sql中
             .addScript("classpath:test-data.sql")//data使用test-data.sql載入
             .build();
      }
      
    • production環境可能會比較適合用JNDI從container中get DataSource
    • @Bean
      public DataSource dataSource() {
        JndiObjectFactoryBean jndiObjectFactoryBean =
            new JndiObjectFactoryBean();
      jndiObjectFactoryBean.setJndiName("jdbc/myDS"); 
      jndiObjectFactoryBean.setResourceRef(true); 
      jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); 
      return (DataSource) jndiObjectFactoryBean.getObject();
      }
      
    • QA環境可能會使用完全不同的DataSource config
    • @Bean(destroyMethod="close")
      public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUsername("sa");
        dataSource.setPassword("password");
        dataSource.setInitialSize(20);
        dataSource.setMaxActive(30);
        return dataSource;
      }
      
    • 問題:可以發現三種環境有三種不同的dataSource(),雖然都會有一個type是javax.sql.DataSource的bin
    • 想法:必須有一種方法配置DataSource,使能夠在每種環境都能夠選擇最適合的config
      • 如果是在單獨的config file中配置bean,在build時確定要使用哪個config file。這個的方法問題在於要為每個環境rebuild
      • 在QA->Prod會不太安心
    • 解決:Spring提供解法
  • Configuring profile beans 
    • 優點:能夠在runtime的時候決定要使用的profile,而不是build time
    • 要使用profile,要先將所有不同的bean定義到一個或多個profile中,在application deploy到environment的時候,要確認對應的profile已經是active
    • 可以使用@Profile指定某個bean屬於哪個profile,如:
      • @Profile("dev"),表示在dev profile active的時候才會create;若不是active,則class內的@Bean的會被呼略
      • @Configuration
        @Profile("dev")
        public class DevelopmentProfileConfig {
          @Bean(destroyMethod="shutdown")
          public DataSource dataSource() {
              return new EmbeddedDatabaseBuilder()
                  .setType(EmbeddedDatabaseType.H2)
                  .addScript("classpath:schema.sql")
                  .addScript("classpath:test-data.sql")
                  .build();
        } }
        
      • @Profile("prod")
      • @Configuration
        @Profile("prod")
        public class ProductionProfileConfig {
          @Bean
          public DataSource dataSource() {
            JndiObjectFactoryBean jndiObjectFactoryBean =
                new JndiObjectFactoryBean();
            jndiObjectFactoryBean.setJndiName("jdbc/myDS");
            jndiObjectFactoryBean.setResourceRef(true);
            jndiObjectFactoryBean.setProxyInterface(
                javax.sql.DataSource.class);
            return (DataSource) jndiObjectFactoryBean.getObject();
        } }
        
      • 也可以在method level使用@Profile annotation,如此就能夠將@Bean一起放到config中
      • @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();
        } }
        
      • 注意:可能有其他bean沒有宣告profile,這樣的話這個bean始終都會被創建,與哪個profile active沒有關係
    • 在XML中配置profile
      • 使用XML配置,設定beans中的profile值
      • <beans ...(skip xmlns..) 
           profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:schema.sql" />
            <jdbc:script location="classpath:test-data.sql" />
        </jdbc:embedded-database>
        
      • 也可以在root <beans>中嵌入<beans> element,而不是為每個environment都建立profile XML document,下例就是將所有profile bean定義放到同一個XML中
      • <beans profile="dev"> <!-- dev profile的bean-->
          <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:schema.sql" />
            <jdbc:script location="classpath:test-data.sql" />
          </jdbc:embedded-database>
        </beans>
        
        <beans profile="qa"> <!-- qa profile的bean-->
          <bean id="dataSource"
        
        class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:url="jdbc:h2:tcp://dbserver/~/test"
        p:driverClassName="org.h2.Driver"
        p:username="sa"
        p:password="password"
        p:initialSize="20" p:maxActive="30" />
         </beans>
        <beans profile="prod"> <!-- prod profile的bean--> <jee:jndi-lookup id="dataSource"
        jndi-name="jdbc/myDatabase"resource-ref="true"proxy-interface="javax.sql.DataSource" />
        </beans> </beans>
      • 注意上例,ID都是dataSource,但在runtime只會create一個bean,決定權在於active profile是哪個
  • Active profile
    • 會依賴兩個獨立的attribute
      1. spring.profiles.active
      2. spring.profiles.default
    • 決定順序
      1. 有設定spring.profiles.active則會取active的值
      2. 沒有設定spring.profiles.active,則會找spring.profiles.default的值
      3. 都沒有設定,則沒有active profile,如此只會create沒有定義在profile中的bean
    • 設定屬性的方式
      • DispatcherServlet的初始值
      • context parameters of a web application
      • JNDI entries
      • 環境變數
      • JVM system properties
      • 在integration test上使用@ActiveProfiles annotation
    • 可以是:
      • spring.profiles.active ()
      • spring.profiles.default (設定為dev environment),且在Servlet context設定
      • 如:在Web.xml中設定,如此在不同環境時,可視情況設 spring.profiles.active
      • <context-param> // context default profile
          <param-name>spring.profiles.default</param-name>
          <param-value>dev</param-value>
        </context-param>
        <servlet>
          <servlet-name>appServlet</servlet-name>
          <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
          </servlet-class>
          <init-param> // Servlet default profile
            <param-name>spring.profiles.default</param-name>
            <param-value>dev</param-value>
          </init-param>
          <load-on-startup>1</load-on-startup>
        </servlet>
        
      • profiles:表示可以時active多個profile,以list列出並用逗號分開
  • 使用profile測試
    • 通常在測試得時候會希望測試的config和production相同
    • 可以使用@ActiveProfiles annotation,可以使用它來指定running的時候要active哪個profile
    • @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration(classes={PersistenceTestConfig.class})
      @ActiveProfiles("dev")
      public class PersistenceTest {
      ... }

沒有留言:

張貼留言