SpringInAction 4 笔记——(二)Spring高级配置

【Spring高级配置】

1.环境与profile

应用程序在不同的环境中可能会出现问题,比如开发环境和生产环境,测试环境等等。而DataSource最为明显,不同的环境获取DataSource的方式不同。

那么如何实现在不同的环境也可以正确运行,而且不需要重构呢?

可以配置 profile bean。

1.JavaConfig中可用@Profile注解来指定某个bean属于哪个profile,如下:

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig{
    @Bean(destroyMethod="shutdown")
    public DataSource dataSource(){
        .....
    }
}

除了在类中使用profile,也可以在方法级别上使用profile,如下:

@Configuration
public class DevelopmentProfileConfig{
    @Bean(destroyMethod="shutdown")
    @Profile("dev")    
    public DataSource dataSource(){
        .....
    }
}

2.可以在XML中配置profile,如下:


    
        
      

需要激活profile才会执行相应的profile,spring确定profile是否激活主要依赖两个属性:

spring.profiles.active 和 spring.profiles.default

如果设置了spring.profiles.active则激活,如果没有,则去找spring.profiles.default

可以在web.xml文本中设置default profile.


    spring.profiles.default
    dev

使用profile进行测试:

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

2.条件化bean

Spring4推出@Conditional注解实现

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

@Conditional会默认实现了Condition接口的:

public interface Condition{
    boolean matches(ConditionContext ctxt,AnnotatedTypeMetadata metadata);
}
public class MagicExistsCondition implements Condition{
    //true才会创建带有@Conditional注解的Bean    
     public boolean matches(ConditionContext ctxt,AnnotatedTypeMetadata metadata){
        Environment env = context.getEnvironment();
        //检查环境中是否存在magic属性,不管这个属性的值是什么,存在即可
        //设置了只有magic环境属性的时候,Spring才会实例化这个类
        return env.containsProperty("magic");
    }
}
ConditionContext 和 AnnotatedTypeMetadata 皆是接口,ConditionContext可以获取环境,而AnnotatedTypeMetadata可以检查带有@Bean注解的方法上还有什么注解。
值得注意的是:从Spring4开始,@Profile注解进行了重构,使其基于@Conditional 和 Condition实现。


3.自动装配的歧义性

如果不仅仅有一个bean能够匹配结果的话就会带来歧义性。如:

@Autowired
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}
@Component
public class Cake implements Dessert{......}
public class Cookies implements Dessert{......}
public class IceCream implements Dessert{......}

Spring试图装配setDessert()时,无法选择Dessert参数,会抛出NoUniqueBeanDefinitionException

解决方法:

1.标识首选的Bean

——用到@Primary注解,如下三种方式

@Component
@Primary
public class IceCream implements Dessert{......}
@Bean
@Primary
public Dessert iceCream(){
    return new IceCream();
}

缺点:无法标识多个首选bean,标识多个首选bean相当于没有首选bean

2.限定自动装配的bean

@Qualifier注解可以和@Autowired 和 @Inject协同使用,如:

@Autowired
@Qualifier("iceCream(bean的ID)")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

使用@Qualifier注解时需要引用bean的ID作为限定符,如果没有定义,会默认把bean类名的第一个字母小写作为限定符,这样存在一个问题就是当你重构了bean,例如 重构了IceCream,将其重命名为Gelato,则bean的ID和默认限定符就会变为gelato,这就无法匹配到setDessert()方法中的限定符。

解决方法:

创建自定义限定符

在bean的声明上加上@Qualifier("自定义限定符")eg:

@Component
@Qualifier("cold")
public class IceCream implements Dessert{......}


4.bean的作用域

单例(Singleton):在整个应用中,只创建bean的一个实例,默认。在大多数情况下,单例bean是一个很理想的方案,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。但是当你所使用的类是易变的,这时重用就会不安全,再用到单例作用域就不合适。

原型(Protorype):每次注入或者通过Spring 应用上下文获取的时候,都会创建一个新的bean实例。

组件扫描的方式声明bean可以用@Scope注解,将其声明为原型bean:

@Component
@Scope(ConfigurableBeanFactory.SCEPE_PROTOTYPE)
public class Notepad{......}

当然,可以用@Scope("protorype")来声明原型bean,但使用SCOPE_PROTOTYPE常量更加安全并且不容易出错。

在JavaConfig中配置:

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

在XML中配置:

会话(Session):在web应用中,为每个会话创建一个bean实例。

eg:在典型的电子商务应用中,可能会有一个bean代表用户的购物车,如果用单例代表bean的话,那么将会导致所有的用户都会向同一个购物车中添加商品,如果用原型,那么在应用中某一个地方往购物车中添加商品,在应用的另一个地方可能就不可用了,因为在这里注入的是另一个原型的购物车。

@Component
//value值告诉Spring为WEB应用中的每个会话创建一个ShoppingCart
//proxyMode属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题
@Scope(value=WebApplicationContext.SCOPE_SESSION,proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){.......}

proxyMode是java代理模式

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

若不指明proxyMode,当把一个session或request的bean注入到sigleton的bean时,会出现问题。如把购物车bean注入到service bean

@Component
public class StoreService {
    @Autowired
    public void setShoppingCart(ShoppingCart shoppingCart) {
            this.shoppingCart = shoppingCart;
        }
        ...
}

因为StoreService是signleton,是在容器启动就会创建,而shoppingcart是session,只有用户访问时才会创建,所以当StoreService企图要注入shoppingcart时,很有可能shoppingcart还没创建。spring用代理解决这个问题,当ShoppingCart是接口时,指定 ScopedProxyMode.INTERFACES。当ShoppingCart是一个类时,则指定ScopedProxy- Mode.TARGET_CLASS,srping会通过CGLib来创建基于类的代理对象。当request注入到signleton bean时,也是一样。

proxyMode属性被设置成ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并调用委托给实现bean

在XML中指定代理模式:

 1 
 2 "
 3 http://www.springframework.org/schema/aop
 4 http://www.springframework.org/schema/aop/spring-aop.xsd
 5 http://www.springframework.org/schema/beans
 6 http://www.springframework.org/schema/beans/spring-beans.xsd">
 7 
 8     class="com.myapp.ShoppingCart" scope="session">
 9         
10     
11 
与@Scope注解的proxyMode属性功能相同


5.运行时值注入

笔记一中提到,将BlankDisc属性注入到compactDisc中时使用了硬编码格式:


    
    

    c:_title="参数title的值"
    c:_artist="参数artist的值"/>

    c:_0="参数title的值"
    c:_1="参数artist的值"/>

而避免硬编码Spring提供了两种在运行时求值的方式:

1.属性占位符Property placeholder

注入外部值:1.声明属性源@PropertySource;  2.根据Spring的Environment来检索属性

@Configuration
//声明属性源
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig{
    @Autowired
    Environment env;
    
    @Bean
    public BlankDisc disc(){
        //检索属性值
        return new BlankDisc(env.getProperty("disc.title"),env.getProperty("disc.artist"));
    }
}

app.properties文件的内容大致如下:

disc.title=title的值
disc.artist=artist的值

当然,可以设置默认值,当所查的属性不存在时:

在JavaConfig中:

@Configuration
//声明属性源
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig{
    @Autowired
    Environment env;
    
    @Bean
    public BlankDisc disc(){
        //检索属性值
        return new BlankDisc(env.getProperty("disc.title","title默认值"),env.getProperty("disc.artist","artist默认值"));
    }
}

在XML中可以为:

在组件扫描和自动装配中:

pubic BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist){
    this.title = title;
    this.artist = artist;
}

为了使用占位符,必须要配置一个PropertySourcesPlaceholeerConfigurer bean ,如下:

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
    return new PropertySourcesPlaceholderConfigurer();
}

如果要使用XML配置,Spring context命名空间中的元素将会为你生成PropertySourcesPlaceholderConfigurer bean :




      



2.Spring表达式语言 SpEL(复杂但常用)

SpEL特性:

a.使用bean的ID来引用bean

b.调用方法和访问对象的属性

c.对值进行算术、关系和逻辑运算

d.正则表达式匹配

e.集合操作

属性占位符放到“${......}”中,而SpEL表达式要放到"#{......}"之中。eg:

#{1}:最简单的SqEL表达式.    

#{T{System}.currentTimeMillis()}:T()表达式会将java.lang.System视为java中对应的类型,因此可以调用其static

                                                        修饰currentTimeMillis() 方法.

#{sgtPeppers.artist}:得到ID为sgtPeppers的bean的artist属性

#{systemProperties['disc.title']}:通过systemproperties对象引用系统属性

组件扫描和自动装配:

pubic BlankDisc(
        @Value("#{systemProperties['disc.title']}") String title,
        @Value("#{systemProperties['disc.title']}") String artist){
    this.title = title;
    this.artist = artist;
}

在XML配置中:















你可能感兴趣的:(SpringInAction 4 笔记——(二)Spring高级配置)