美文网首页我爱编程
10、高级装配2(spring笔记)

10、高级装配2(spring笔记)

作者: yjaal | 来源:发表于2017-03-20 11:40 被阅读232次

    四、bean的作用域

    默认情况下,spring应用上下文所有bean都是作为以单例的形式创建的。但是有时候,我们所使用的的类是易变的,因此重用是不安全的。spring定义了多种作用域,可以基于这些作用域创建bean,包括:

    • 单例(Singleton):在整个应用中,只创建bean的一个实例
    • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
    • 会话(Session):在Web应用中,为每个会话创建一个bean实例。
    • 请求(Request):在Web应用中,为每个请求创建一个bean实例。

    单例是默认的作用域,但是正如之前所述,对于易变类型,这并不合适,如果要选择其他作用域,可以使用@Scope注解,可以和@Component@Bean一起使用。

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

    说明:当然我们可以这样使用@Scope("prototype"),但是上面那样更为安全且不易出错。如果要在XML中配置的话,可以这样:

    <bean id="notepad" class="com.myapp.Notepad" scope="prototype" />
    

    说明:不管使用哪种方式来声明原型作用域,每次注入或从spring上下文中检索该bean的时候,都会创建新的实例,于是每次操作都有自己独有的Notepad实例。

    4.1 使用会话和请求作用域

    Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事。比如在电商网站上购买商品,就希望购物车在此会话中是共享的:

    @Component
    @Scope(value = WebApplicationContext.SCOPE_SESSION, 
                   proxyMode = ScopedProxyMode.INTERFACES)
    public ShoppingCart cart(){}
    

    说明:

    • 上述作用域是会话,于是在一个购买商品的会话中,购物车是共享的。而@Scope同时还有一个proxyMode属性,其值为ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。

    • 假设要将ShoppingCart bean注入到单例StoreService beanSetter方法中:

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

    因为StoreService是一个单例的bean,会在Spring应用上下文加载的时候创建,此时,Spring会试图将ShoppingCart bean注入到setShoppingCart()方法中去,但是ShoppingCart bean是会话作用域,此时并不存在(此时没有发起会话)。同时,系统中每次会话就有一个ShoppingCart实例,我们也并不希望注入某个固定的ShoppingCart实例到StoreService中。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。

    • 实际上Spring并不会将实际的ShoppingCart bean注入到StoreService中,而是会注入一个到ShoppingCart bean的代理,如图所示。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并调用委托给会话作用域内真正的ShoppingCart bean

      1
    • 如果ShoppingCart是接口的话,这是可以的。但是如果是一个具体的类,Spring就没有办法创建基于接口的代理了,需要使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来标明要以生成目标扩展的方式创建代理(而不是基于接口的代理扩展)。

    4.2 在 XML 中声明作用域代理

    XML中要设置代理模式,需要使用Spring aop命名空间的一个新元素:

    <bean id="cart" class="com.myapp.ShoppingCart" scope="session">
        <aop:scoped-proxy />
    </bean>
    

    说明:这种命名方式和@Scope注解的proxyMode属性功能相同的Spring XML配置元素。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:

    <bean id="cart" class="com.myapp.ShoppingCart" scope="session">
        <aop:scoped-proxy proxy-target-class="false" />
    </bean>
    

    当然要使用这种配置,必须在头部声明:

    xmln:aop="http://www.springframework.org/shema/aop"
    

    五、运行时值注入

    前面也讲过,有时候需要将具体的值注入。但是有时候硬编码是可以的,但是有时候却又希望这些值在运行再确定。为了实现这些功能,Spring提供了两种在运行时求值的方式:

    • 属性占位符(Property placeholder
    • Spring表达式语言(SpEL

    5.1 注入外部的值

    Spring中,处理外部值的最简单方式就是声明属性源并通过SpringEnvironment来检索属性。下面看一个例子:

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

    说明:@PropertySource中配置了名为app.properties的文件:

    disc.title=Sgt. Peppers Lonely Hearts Club Band
    disc.artist=The Beatles
    

    这个属性文件会加载到SpringEnvironment中,稍后可以从这里检索属性。于是就可以从中取得相关的 值了。

    5.1.1 深入学习 Spring 的 Environment

    Environment类中,上述的getProperty()方法并不是获取属性值的唯一方法,还有另外三个:

    String getProperty(String key);
    String getProperty(String key, String defaultValue);
    T getProperty(String key, Class<T> type);
    T getProperty(String key, Class<T> type, T defaultValue);
    

    使用第二个方法可以为相关属性设置一个默认值,如果在配置文件中取不到相关值,则可以使用默认值:

    @Bean
    public BleankDisc disc(){
        return new BlankDisc(env.getProperty("disc.title", "Rattle and Hum"), 
                             env.getProperty("disc.artist", "U2"));//检索属性值
    }
    

    后面两种方法与前面两种类似,但是不会将所有的值都视为String类型。假如你想要获取的值所代表的含义是连接池中所维持的连接数量。如果从属性配置文件中得到的是一个String类型的值,那么在使用之前还要将其转换为Integer类型:

    int connectCount = env.getProperty("db.connection.count", Integer.class, 30);
    

    Environment还提供了几个与属性相关的方法,如果在使用getProperty()方法时还没有指定默认值,并且这个属性没有定义,获取到的值就是null。可以使用getRequiredProperty()方法指定某个属性必须要定义:

    @Bean
    public BleankDisc disc(){
        return new BlankDisc(env.getRequiredProperty("disc.title"), 
                             env.getRequiredProperty("disc.artist");//检索属性值
    } 
    

    如果上述两个属性值没有定义的话将会抛出IllegalStateException异常。如果想检查某个属性是否存在的话可以:

    boolean titleExists = env.containsProperty("disc.title");
    

    最后,如果想将属性解析为类的话,可以这样:

    Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class);
    

    除了上述的这些方法,Environment还提供了一些方法来检查哪些profile处于激活状态:

    String[] getActiveProfiles():返回激活profile名称的数组
    String[] getDefaultProfiles():返回默认profile名称的数组
    boolean acceptsProfiles(String ... profiles):如果Environment支持给定的profile,返回true
    

    5.1.2 解析属性占位符

    可以在XML中使用占位符按照如下方式解析BlankDisc构造函数参数:

    <bean id="sgtPeppers" class="soundsystem.BlankDisc" 
            c:_title="${disc.title}"
            c:_artist="${disc.artist}" />
    

    稍后会讨论这些属性是如何解析的。如果我们依赖组件扫描和自动装配来创建和初始化应用组件,那么就不需要指定占位符的配置文件或类了。

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

    为了使用占位符,必须配置一个PropertyPlaceholderConfigurer beanPropertySourcePlaceholderConfigurer bean。从Spring 3.1开始,推荐使用后者,因为它能够基于Spring Environment及其属性源来解析占位符。

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

    而在XML文件应这样配置:

    <context:property-palceholder />
    

    当然必须在头中配置contextschema。以上就是两种解析占位符的方式。

    5.2 使用 Spring 表达式语言进行装配

    Spring表达式语言SpEL中有很多特性:

    • 使用beanID来引用bean
    • 调用方法和访问对象的属性
    • 对值进行算术、关系和逻辑运算
    • 正则表达式匹配
    • 集合操作

    5.2.1 SpEL 样例

    需要了解的第一件事就是SpEL表达式要放到"#{...}"之中,这和属性占位符类似。下面直接看例子:

    #{1}
    

    这就表示数字1

    #{T(System).currentTimeMillis()}
    

    最终结果是当前时刻的毫秒数。T()表示会将java.lang.System视为Java中对应的类型,因此可以调用其static修饰的方法。我们还可以引用其他的bean或其他bean的属性:

    #{sgtPeppers.artist}
    

    这表示引用IDsgtPeppers bean的属性。还可以通过systemProperties对象引用系统属性:

    #{systemProperties['disc.title']}
    

    如果将之前配置注入相关属性值应用上SpEL表达式,则就是这样:

    public BlankDisc(@Value("#{systemProperties['disc.title']}") String title, 
                     @Value("#{systemProperties['disc.title']}") String artist){
        this.title = title;
        this.artist = artist;
    }
    
    <bean id="sgtPeppers" class="soundsystem.BlankDisc" 
            c:_title="#{systemProperties['disc.title']}"
            c:_artist="#{systemProperties['disc.title']}" />
    

    5.2.2 表示字面值

    #{3.14159}//浮点值
    #{9.87E4}//科学计数法
    #{'Hello'}//字符串
    #{false}//布尔值
    

    5.2.3 引用bean、属性和方法

    #{sgtPeppers}//使用bean ID作为SpEL表达式
    #{sgtPeppers.artist}//引用bean的属性artist
    #{artistSelector.selectArtist()}//引用bean中的方法
    #{artistSelector.selectArtist().toUpperCase()}//前一个方法返回String,则还可以继续调用方法
    #{artistSelector.selectArtist()?.toUpperCase()}//先判断前一个方法是否返回String,如果是,则调用方法,否则返回null,不调用后续方法
    

    5.2.4 在表达式中使用类型

    如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键的运算符:

    #{T(java.lang.Math)}
    #{T(java.lang.Math).PI}
    #{T(java.lang.Math).random()}
    

    5.2.5 SpEL运算符

    运算符类型 运算符
    算术运算 +、-、*、/、%、^
    比较运算 <、>、==、<=、>=、lt、gt、eq、le、ge
    逻辑运算 and、or、not、逻辑或
    条件运算 ?:(ternary)、?:(Elvis)
    正则表达式 matches

    5.2.6 计算正则表达式

    #{admin.email matches '{[a-zA-Z0-9._%+-]+@[a-zA-Z0-9._]+\\.com}'}
    

    5.2.7 计算集合

    #{jukebox.songs[4].title}//引用集合中的一个元素
    #{jukebox.songs[T(java.lang.Math).random() * jukebox.songs.size()].title}//随机选取集合中的一个元素
    #{'This is a test'[3]}//返回第四个位置的字符's'
    

    5.2.8 查询运算符

    #{jukebox.songs.?[artist eq 'Aerosmith']}//查询名字为Aerosmith的歌曲,返回一个集合
    #{jukebox.songs.^[artist eq 'Aerosmith']}//查询集合中第一个artist属性为Aerosmith的歌曲
    #{jukebox.songs.$[artist eq 'Aerosmith']}//查询集合中最后一个artist属性为Aerosmith的歌曲
    #{jukebox.songs.![title]}//不想要歌曲对象的集合,而是所有歌曲名称的集合(没有artist属性的集合),返回一个新集合
    

    注意:以上都是相关的值注入,而对于相关属性的注入是不同的,spring是没法自动解析如Date这种类型的,所以我们需要编写相关自定义的属性编辑器,请参看前面小节:《2.spring的IOC容器注入(spring笔记)》

    相关文章

      网友评论

        本文标题:10、高级装配2(spring笔记)

        本文链接:https://www.haomeiwen.com/subject/hkpbnttx.html