本节主要讲:

  • Spring profile
  • 自动装配与歧义性
  • Spring表达式语言

Spring profile

当需要根据环境决定该创建哪个 bean 和不创建哪个 bean,可以使用bean profile 的功能,确保是等到运行时再来确定,这样的结果就是同一个部署单元(可能会是 WAR 文件)能够适用于所有的环境,没有必要进行重新构建。

配置profile bean

在 Java 配置中,可以使用 @Profile 注解指定某个 bean 属于哪一个 profile,例如,在配置类中,嵌入式数据库的 DataSource 可能会配置成如下所示:

package com.myapp;  
  
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 {  
  
    @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();  
    }  
  
}

激活 profile

有多种方式来设置这两个属性:

  • 作为 DispatcherServlet 的初始化参数;
  • 作为 Web 应用的上下文参数;
  • 作为 JNDI 条目;
  • 作为环境变量;
  • 作为 JVM 的系统属性;
  • 在集成测试类上,使用 @ActiveProfiles 注解设置。

处理自动装配的歧义性

仅有一个 bean 匹配所需的结果时,自动装配才是有效的。如果不仅有一个 bean 能够匹配结果的话,这种歧义性会阻碍 Spring 自动装配属性、构造器参数或方法参数。

标示首选的 bean

采用@Primary只能标记一个首选bean,如果你标示了两个或更多的首选 bean,那么它就无法正常工作了。

package com.food;  
  
import org.springframework.beans.factory.annotation.Qualifier;  
import org.springframework.context.annotation.Primary;  
import org.springframework.stereotype.Component;  
  
@Component  
@Primary  
public class Cake implements Dessert {  
}

限定自动装配的 bean

设置首选 bean 的局限性在于 @Primary 无法将可选方案的范围限定到唯一一个无歧义性的选项中。它只能标示一个优先的可选方案。当首选 bean 的数量超过一个时,我们并没有其他的方法进一步缩小可选范围。

我们这里使用自定义的限定符注解。当使用自定义的 @Qualifier 值时,最佳实践是为 bean 选择特征性或描述性的术语,而不是使用随意的名字。

创建自定义的限定符注解,这里要使用@Qualifier来标注一下:

package com.food;  
  
import javax.inject.Qualifier;  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,  
        ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Qualifier 
public @interface Cold {  
}

为组件添加@Cold注解:

package com.food;  
  
import org.springframework.beans.factory.annotation.Qualifier;  
import org.springframework.stereotype.Component;  
  
@Component  
@Cold  
public class IceCream implements Dessert {  
}

为了得到 IceCream bean,DiningTable() 方法可以这样使用注解:

package com.food;  
  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Qualifier;  
import org.springframework.stereotype.Component;  
  
@Component  
public class DiningTable {  
    private Dessert dessert;  
    
    @Autowired  
    @Creamy  
    public DiningTable(Dessert dessert) {  
        this.dessert = dessert;  
    }  
}

运行时值注入

当讨论依赖注入的时候,我们通常所讨论的是将一个 bean 引用注入到另一个 bean 的属性或构造器参数中,但是 bean 装配的另外一个方面指的是将一个注入到 bean 的属性或者构造器参数中。
例如:

@Bean  
public CompactDisc sgtPeppers()  {  
    return new BlankDisc(  
            "Sgt. Pepper's Lonely Hearts Club Band",  
            "The Beatles"  
  );  
}

BlankDisc bean 设置 title 和 artist,但它在实现的时候是将值硬编码在配置类中的,有时候硬编码是可以的,但有的时候,我们可能会希望避免硬编码值,而是想让这些值在运行时再确定。

Spring提供了两种在运行时求值的方式:

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

属性占位符

属性占位符需要放到 ${ ... } 之中。
在本例中,@PropertySource 引用了类路径中一个名为 app.properties 的文件。它大致会如下所示:

disc.title=Sgt. Peppers Lonely Hearts Club
disc.artisc=The Beatles

如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。比如,在 BlankDisc 类中,构造器可以改成如下所示:

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

@Value的使用方式与 @Autowired 注解非常相似。

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

SpEL 拥有很多特性,包括:

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

SpEL样例

需要了解的第一件事情就是 SpEL 表达式要放到 #{ ... } 之中。

如果通过组件扫描创建 bean 的话,在注入属性和构造器参数时,我们可以使用 @Value 注解,例如:

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

表示字面值

#{3.14159}
#{false}
#{'3.14159'}
#{9.78E4}//科学计数法

引用 bean、属性和方法

#{sgtPeppers}//通过 ID 引用其他的 bean
#{sgtPeppers.artist}//引用sgtPeppers的artist属性
#{sgtPeppers.selectArtist()}//引用sgtPeppers的方法
#{sgtPeppers.selectArtist()?.toUpperCase()}//?.这个运算符能够在访问它右边的内容之前,确保它所对应的元素不是 null。所以,如果 selectArtist() 的返回值是 null 的话,那么 SpEL 将不会调用 toUpperCase() 方法。表达式的返回值会是 null。

在表达式中使用类型

T(java.lang.Math)//这里所示的 T() 运算符的结果会是一个 Class 对象,代表了 java.lang.Math。

T() 运算符的真正价值在于它能够访问目标类型的静态方法和常量。

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

计算正则表达式

matches 返回的是一个boolean类型的值

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

计算集合

//引用一个jukebox bean的一个songs集合的title属性
#{jukebox.songs[4].title} 

//.?[] 运算符用来对集合进行过滤,得到集合的一个子集
//得到jukebox 中 artist 属性为 Aerosmith 的所有歌曲
#{jukebox.songs.?[artist eq 'Aerosmith']}

//.^[] 用在集合中查询第一个匹配项
#{jukebox.songs.^[artist eq 'Aerosmith']}

//.$[] 用在集合中查询最后一个匹配项
#{jukebox.songs.$[artist eq 'Aerosmith']}

//.![]从集合的每个成员中选择特定的属性放到另外一个集合中
#{jukebox.songs.![title]}

WinRT
21 声望4 粉丝

临渊羡鱼,不如退而结网