Spring - 高級裝配

高級裝配

  • Spring profile
  • 條件化的bean
  • 自動裝配與歧義性
  • bean的做用域
  • Spring表達式語言

環境與profile

  • profile能夠爲不一樣的環境(dev、prod)提供不一樣的數據庫配置、加密算法等
  • @Profile註解能夠在類級別和方法級別,沒有指定profile的bean始終都會被建立
  • XML的方式配置 datasource.xml
import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jndi.JndiObjectFactoryBean;

/**
 * 不一樣環境所須要的數據庫配置
 */
public class DataSourceDemo {

    /**
     * EmbeddedDatabaseBuilder會搭建一個嵌入式的Hypersonic數據庫
     * 模式(schema)定義在schema.sql
     * 測試數據經過test-data.sql加載
     */
    @Bean(destroyMethod="shutdown")
    public DataSource dataSource_1(){
        return new EmbeddedDatabaseBuilder()
                .addScript("classpath:schema.sql")
                .addScript("classpath:test-data.sql")
                .build();
    }
    
    /**
     * 使用JNDI從容器中獲取一個DataSource
     */
    @Bean
    public DataSource dataSource_2(){
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDs");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
    
    /**
     * 配置DBCP的鏈接池
     */
    @Bean(destroyMethod="close")
    public DataSource dataSource(){
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setInitialSize(20);
        dataSource.setMaxActive(30);
        return dataSource;
    }
    
}

激活profile

  • 依賴屬性:spring.profiles.active和spring.profiles.defaultjava

    • 做爲DispatcherServlet的初始化參數
    • 做爲Web應用的上下文參數
    • 做爲JNDI條目
    • 做爲環境變量
    • 做爲JVM的系統屬性
    • 在集成測試類上,使用@ActiveProfile註解設置
  • 在web.xml中配置 見web.xml03
import javax.sql.DataSource;

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;

/**
 * 不一樣環境數據源部署配置類
 *
 */
@Configuration
public class DataSourceConfig {

    /**
     * 爲dev profile裝配的bean
     * @return
     */
    @Bean(destroyMethod="shutdown")
    @Profile("dev")
    public DataSource embeddedDataSource(){
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:schema.sql")
                .addScript("classpath:test-data.sql")
                .build();
    }
    
    /**
     * 爲prod profile裝配的bean
     * @return
     */
    @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();
    }
}
import javax.sql.DataSource;

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;

/**
 * 開發環境部署配置類
 *
 */
@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();
    }
}
import org.junit.runner.RunWith;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 使用profile進行測試
 * 當運行集成測試時,Spring提供了@ActiveProfiles註解,使用它來指定運行測試時要激活哪一個profile
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {

}

條件化的bean

  • 假設你但願一個或多個bean只有在應用的類路徑下包含特定庫時才建立
  • 或者但願某個bean只有當另外某個特定的bean也聲明瞭以後纔會建立
  • 還可能要求只有某個特定的環境變量設置以後,纔會建立某個bean
  • Spring4引入一個新的@Conditional註解,它能夠用在帶有@Bean註解的方法上
  • 例如,假設有個名爲MagicBean的類,咱們但願只有設置了magic環境屬性的時候,Spring纔會實例化這個類
  • 經過ConditionContext,能夠:web

    • 藉助getRegistry()返回的BeanDefinitionRegistry檢查bean定義
    • 藉助getBeanFactory()返回的ConfigurableListableBeanFactory檢查bean是否存在,甚至檢查bean的屬性
    • 藉助getEnvironment()返回的Environment檢查環境變量是否存在以及它的值是什麼
    • 讀取並探查getResourceLoader()返回的ResourceLoader所加載的資源
    • 藉助getClassLoader()返回的ClassLoader加載並檢查類是否存在
  • @Profile這個註解自己也使用了@Conditional註解,而且引用ProfileCondition做爲條件正則表達式

    • ProfileCondition會檢查value屬性,該屬性包含了bean的profile名稱。檢查profile是否激活
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 MagicExistsConditional implements Condition {

    /**
     * 方法簡單功能強大
     */
    @Override
    public boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata) {
        Environment env = ctxt.getEnvironment();
        //檢查magic屬性
        return env.containsProperty("magic");
    }

}

處理自動裝配的歧義

  • 僅有一個bean匹配所需的結果時,自動裝配纔是有效的,若是不只有一個bean可以匹配結果的話,這種歧義性會阻礙Spring自動裝配屬性、構造器參數或方法參數
  • 標示首選(primary)的bean算法

    • @Component註解上加@Primary註解
    • 在Java配置顯示聲明 中@Bean註解上加@Primary註解
    • XML配置bean primary="true"
  • 限定自動裝配的beanspring

    • 不止一個限定符時,歧義性問題依然存在
    • 限定bean:在註解@Autowired上加@Qualifier("beanName")
    • 限定符和注入的bean是耦合的,類名稱的任意改動都會致使限定失敗

* 建立自定義的限定符sql

* 在@Component註解上加@Qualifier("selfDefinedName")註解
* 能夠在@Autowired上加@Qualifier("selfDefinedName")
* 也能夠在Java配置顯示定義bean時,在@Bean註解上加@Qualifier("selfDefinedName")
  • 使用自定義的限定符註解數據庫

    • 若是有多個相同的限定符時,又會致使歧義性
    • 在加一個@Qualifier註解:Java不容許在同一條目上出現相同的多個註解
      (JDK8支持,註解自己在定義時帶有@Repeatable註解就能夠,不過Spring的@Qualifier註解上沒有)
    • 定義不一樣的註解,在註解上加@Qualifier,例如在bean上加@Cold和@Creamy(自定義註解)

bean的做用域

  • 默認狀況下,Spring應用上下文全部bean都是做爲以單例(singleton)的形式建立的
  • Spring定義了多種做用域apache

    • 單例(Singleton):在整個應用中,只建立bean的一個實例
    • 原型(Prototype):每次注入或者經過Spring應用上下文獲取的時候,都會建立一個新的bean實例
    • 會話(Session):在Web應用中,爲每一個會話建立一個bean實例
    • 請求(Request):在Web應用中,每一個請求建立一個bean實例
  • 使用會話和請求做用域數組

    • 購物車bean,會話做用域最合適,由於它與給定的用戶關聯性最大 例如:ShoppingCart
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

/**
 * value=WebApplicationContext.SCOPE_SESSION值是session,每一個會話會建立一個ShoppingCart
 * proxyMode=ScopedProxyMode.INTERFACES建立類的代理,確保當前購物車就是當前會話所對應的那一個,而不是其餘用戶
 * 
 * XML的配置
 * <bean id="cart" class="com.leaf.u_spring.chapter03.ShoppingCart" scope="session">
 *         <aop:scoped-proxy />
 * </bean>
 * 
 * 使用的是CGLib生成代理類
 */
@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
        proxyMode=ScopedProxyMode.INTERFACES)
public class ShoppingCart {

    
}

運行時值注入

  • Spring提供兩種運行的值注入安全

    • 屬性佔位符(Property placeholder)
    • Spring表達式語言(SpEL)
  • 深刻學習Spring的Environment

    • String getProperty(String key) 獲取屬性值
    • String getProperty(String key, String defaultValue) 獲取屬性值,不存在時返回給定默認值
    • <T> T getProperty(String key, Class<T> targetType); 獲取指定類型的屬性值
    • <T> T getProperty(String key, Class<T> targetType, T defaultValue); 獲取指定類型的屬性值,不存在時返回默認值
    • boolean containsProperty(String key); 檢查指定屬性值是否存在
    • String[] getActiveProfiles(); 返回激活profile名稱數組
    • String[] getDefaultProfiles(); 返回默認profile名稱數組
    • boolean acceptsProfiles(String... profiles); 若是environment支持給定profile的話,就返回true
  • 解析屬性佔位符

    • Spring一直支持將屬性定義到外部的屬性文件中,並使用佔位符值將其插入到Spring bean中
    • 在Spring裝配中,佔位符的形式爲使用"${...}"包裝的屬性名稱
    • 在JavaConfig使用 @Value
    • 在XML中 <context:property-placeholder location="classpath:/conf/*.properties" />
  • 使用Spring表達式語言進行裝配

    • SpEl擁有特性:

      • 使用bean的ID來引用bean
      • 調用方法和訪問對象的屬性
      • 對值進行算術、關係和邏輯運算
      • 正則表達式匹配
      • c集合操做
      • Spring Security支持使用SpEL表達式定義安全限制規則
      • 在Thymeleaf模板視圖中使用SpEL表達式引用模型數據
    • SpEL表達式要放到 "#{...}"之中:
* #{1}         - 1
        * #{T(System).currentTimeMillis()}    -當前毫秒數
        * #{sgtPeppers.artist}          - ID爲sgtPeppers的bean的artist屬性
        * #{systemProperties['disc.title']}        -經過systemProperties對象引用系統屬性
* 表示字面值
    * 浮點數:#{3.14159}    科學計數法:#{9.87E4}        字符串:#{'hello'}    boolean類型:#{false}
* 引入bean、屬性和方法
* #{sgtPeppers} -bean        #{sgtPeppers.artist} -屬性
        * #{sgtPeppers.selectArtist()} -方法        
        * #{sgtPeppers.selectArtist()?.toUpperCase()} -?.判斷非空狀況調用toUpperCase方法
* 在表達式中使用類型
    * 若是要在SpEL中訪問類做用域的方法和常量的話,要依賴T()這個關鍵的運算符。
    * 表達Java的Math類    T(java.lang.Math)
    * 把PI屬性裝配待bean屬性    T(java.lang.Math).PI
    * 調用T()運算符所獲得的靜態方法    T(java.lang.Math).random()
* SpEL運算符
* 算術運算、比較運算、邏輯運算、條件運算、正則表達式、三元運算符
        * #{2 * T(java.lang.Math).PI * circle.radius}        -計算圓周長
        * #{T(java.lang.Math).PI * circle.radius ^ 2}        -計算圓面積
        * #{disc.title + 'by' + disc.artist}                -String類型的鏈接操做
        * #{counter.total == 100}或#{counter.total eq 100} -比較運算
        * #{scoreboard.score > 1000 ? 'Winner!' : 'Losser'}    -三元運算
        * #{disc.title ?: 'Rattle and Hum'}                -檢查null,使用默認值代替null
        * #{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}        -匹配有效郵箱
        * #{jukebox.songs[4].title}    -計算ID爲jukebox的bean的songs集合中第五個元素的title屬性
        * #{jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size].title}        -獲取隨機歌曲的title
        * #{jukebox.songs.?[artist eq 'Aerosmith']}        -用來對集合過濾查詢
        * ‘.^[]’ 和 ‘.$[]’分別用來在集合中查詢第一個匹配項和最後一個匹配項
        * 投影運算符 (.![]),會從集合的每一個成員中選擇特定的屬性放到另一個集合中
引用:《Spring In Action 4》第3章
相關文章
相關標籤/搜索