Spring实战(6):装配Bean——运行时注入

我们通常讨论依赖注入的时候,讲的都是将一个Bean引入到另一个Bean的属性或构造器参数中,它通常指的是将一个对象与另一个对象进行关联。而装配Bean的另一个方面指的是将一个值注入到Bean的属性或构造器中,也就是字面量的注入。
比如,将专辑的名字装配到TaylorSwift Bean的构造器或title属性中:

@Bean
public CompactDisc taylorSwift() {
    return new TaylorSwift("titledemo","artistdemo");
}

这实现了我们为BlankDisc Bean设置title和artist的需求,但它在实现的时候是将值硬编码在配置类中的,同理XML也是。但有时候我们可能会希望避免硬编码值,而是想让这些值在运行时再确定。
为了实现,Spring提供了两种在运行时求值的方式:

  • 属性占位符
  • Spring表达式语言(spEL)

使用属性占位符注入外部的值

处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性。

3.21:使用PropertySource注解和Environment

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {
    @Autowired
    Environment env;

    @Bean
    public TaylorSwift disc() {
        return new TaylorSwift(
            env.getProperty("disc.title"),
            env.getProperty("disc.artist"));
    }
}

我们看到上面的代码中@PropertySource引用了app.properties的文件,文件内容可能如下:

3.22:app.properties

disc.title = titledemo;
disc.artist = artistdeomo;

这个文件会加载到Spring的Environment中,稍后会从这个检索属性。同时disc()方法中,会创建一个新的BlankDisc,它的构造器参数是从属性文件中获取的,而这是通过getProperty()实现的。
其实getProperty()不是获取属性值的唯一方法,我们下来就深入学习Spring的Environment。

深入学习Spring的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 defaultValues)
1)第一种就是我们上面代码的例子
2)第二种是针对如果指定属性不存在的时候,我们也可以这样写,使用一个默认值:
env.getProperty("disc.title","aaa");
env.getProperty("disc.artist","bbb");

但是当属性不存在并且也没有指定默认值的情况下,将会抛出IllegalStateException异常。
检查属性是否存在可以调用Environment的containsProperty()方法:

boolean titleExists = env.containsProperty("disc.title");
3)后两种与前两种类似,但是不会将所有的值都认为String类。例如下面的代码,当我们想使用Integer,但从属性文件中得到了String类型,就会这样做:
int connectionCount = env.getProperty("db.connection.count", Integer.class, 30);

Environment还提供了一些其他的方法:

  • String getRequiredProperty(String key):希望这个属性必须定义,当属性未定义并且也没有指定默认值的情况下,将会抛出IllegalStateException异常
  • Class< T> getPropertyAsClass(String key, …class):将属性解析为类
  • String[] getActiveProfiles():返回激活profile名称的数组
  • String[] getDefaultProfiles():返回默认profile名称的数组
  • boolean acceptsProfiles(String… profiles):如果Environment支持给定profile的话,返回true

直接从Environment中检索属性的非常方便的,但Spring也提供了通过占位符装配属性的方法。

解析属性占位符

在Spring装配中,占位符的形式为使用“${…}”包装的属性名称。
比如,在XML中解析TaylorSwift构造器参数:

3.23:在XML中使用占位符解析构造器参数

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

可以看到title和artist构造器参数的值时从一个属性中解析到的,这个属性的名称为disc.title和disc.artist。在XML文件中,并没有使用任何硬编码的值,它的值时从XML配置文件以外的一个源中解析得到的。

如果我们依赖与组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了,在这种情况下,我们可以在Java代码中使用@Value注解来使用占位符,比如在TaylorSwift的类中,它的构造器可以这样写:

3.24:在Java代码中使用占位符

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

为了使用占位符, 我们必须要配置一个PropertySourcesPlaceholderConfigurer Bean.
例如如下的@Bean方法在Java中配置了PropertySourcesPlaceholderConfigurer:

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

或者在XML配置文件中使用Spring context命名空间中的< context:property-placeholder>元素来生成PropertySourcesPlaceholderConfigurer。如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder />
</beans>

解析外部属性能够将值的处理推迟到运行时,但是它的关注点再与根据名称解析来自于Spring Environment和属性源的属性。而Spring表达式语言提供了一种更通用的方式在运行是计算所要注入的值。


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

Spring 3引入了Spring表达式语言,它能够以一种强大和简洁的方式将值装配到Bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。
spEL功能很强大,它有很多特性:

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

spEL是一种非常灵活的表达式语言,它放在“#{…}”之中,与属性占位符有些类似。而{}里面的表达式体多种多样,简单的可以是一个数字,比如#{1};

复杂的可以是如下这样,最终结果为计算表达式的那一刻的当前时间的毫秒数。T()会将java.lang.System视为Java中对应的类型,因此可调用static修饰的currentTimeMillis()方法:

#{T(System).currentTimeMillis()}

spEL还可以引用其他的Bean或其他Bean的属性,如下计算ID为taylorSwift的Bean的artist属性:

#{taylorSwift.artist}

还可以通过systemProperties对象引用系统属性:

#{systemProperties['disc.title']}

也可以使用@Value注解,将里面的属性占位符替换为spEL表达式:

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

同理XML中也可以通过spEL表达式设置构造器参数:

<bean id = "idemo"
      class = "soundsystem.TaylorSwift"
      c:_title = "#{systemProperties['disc.title']}"
      c:_artist = "#{systemProperties['disc.artist']}" />

接下来我们就来详细看看spEL所支持的基础表达式~

1)表示字面值

#{1}         表示整数
#{3.14159}   表示浮点数
#{9.87E4}    表示科学计数法
#{'HELLO'}   表示String类型
#{false}     表示boolean类型

2)引用Bean、属性和方法

引用Bean:

#{taylorSwift}

引用Bean的属性:

#{taylorSwift.artist}

调用Bean的方法:

#{taylorSwift.selectMusic()}

调用Bean方法返回值的方法:

#{taylorSwift.selectMusic().toUpperCase()}

在这个例子中,如果selectMusic()返回值不是null的话,再调用方法是没问题的,但是为了避免出现NullPointerException,可以使用类型安全的运算符,它会判断在访问右边内容前确保不是null,否则返回null:

#{taylorSwift.selectMusic()?.toUpperCase()}

3)在表达式中使用类

如果要在SpEL中访问类的方法或者常量的话,要使用T()这个关键的运算符。
比如为了在SpEL中表达Java的Math类,要按照下面的方式使用T()运算符:

T(java.lang.Math)

使用Math类的常量PI:

T(java.lang.Math).PI

调用Math类的方法:

T(java.lang.Math).random()

4)spEL运算符

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

计算圆的周长:

#{2 * T(java.lang.Math).PI * circle.radius}

计算圆的面积:

#{T(java.lang.Math).PI * circle.radius ^ 2}

String类的连接:

#{disc.title + ' by ' + disc.artist}

比较数字是否相等,结果返回boolean类型:

#{counter.total == 100}
#{counter.total eq 100}

三元运算符:

#{scoreboard.score > 1000 ? "Winner!" : "Loser"}

Elvis运算符,检查null值:

#{disc.title ?: 'Rattle and Hum'}

5)计算正则表达式

匹配邮箱:

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

6)计算集合

通过下标来获得集合或数组中某一元素的值:

#{taylorswift.songs[4].title}

从String中获取一个字符:

#{'hello world'[0]}

7)其他运算符

SpEL还提供了一些其它比较特殊的运算符~

查询运算符(.?[ ])
专门用来对集合进行过滤,得到集合的一个子集:

#{jukebox.songs.?[artist eq 'Aerosmith']}

查询运算符(.^[ ])和(.$[ ])
.^[ ]用来在集合中查询第一个匹配项
.$[ ]用来在集合中查询最后一个匹配项:

#{jukebox.songs.^[artist eq 'Aerosmith']}
#{jukebox.songs.$[artist eq 'Aerosmith']}

投影运算符(.![ ])
从集合的每个成员中选择特定的属性放到另一个集合中

#{jukebox.songs.![title]}

它还可以与其它SpEL运算符任意使用:

#{jukebox.songs.^[artist eq 'Aerosmith'].![title]}

以上就是spEL功能的一个皮毛。要注意的是,不要让表达式太复杂和智能,否则会给测试造成很多麻烦。我们要尽可能让表达式保持简洁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值