3 Advanced wiring

3.1 环境和profiles

不同的环境需要配置不同的beans,比如dev,qa,prod环境下的DataSource是不一样的。可以使用 @Profile("prod") 这样的注解。

在Spring 3.1中,@Profile注解只能在类级别使用(Java 配置类),从Spring 3.2开始,你可以在方法级别使用@Profile注解,与@Bean注解一起。

Spring用两个属性 spring.profiles.active 和 spring.profiles.default 决定哪些profiles是激活的,首先考虑spring.profiles.active设置的值,然后是spring.profiles.default,如果两个都没设置,那就没有激活的profiles,有几个地方来设置:

  • As initialization parameters on DispatcherServlet
  • As context parameters of a web application
  • As JNDI entries
  • As environment variables
  • As JVM system properties
  • Using the @ActiveProfiles annotation on an integration test class

3.2 Conditional beans

从Spring 4.0开始, 引入的 @Conditional 注解能够更灵活地选择性配置beans,比如当特定的类库在classpath下、或特定的别的bean被声明、或特定的环境变量被设置的情况下才创建bean。

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

MagicExistsCondition是实现了Condition接口的类:

public interface Condition {
  boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata);
}

matches如果返回true,则创建对应的bean,ConditionContext和AnnotatedTypeMetadata可以拿到很多帮助我们判断的信息。

3.3 自动装配的歧义

在自动注入的时候,如果有多个bean符合要求,Spring就会抛出 NoUniqueBeanDefinitionException 异常。

你可以在bean定义的时候加上 @Primary 注解,这样当有多个bean时,Spring会选择有@Primary注解的那个bean,但是当有多个bean标注了@Primary时,歧义还是有。

另一种更好的方法是使用 @Qualifier 注解,在 @Autowired 的同时加上 @Qualifier("iceCream") 注解,这样就选择了bean ID是iceCream的那个bean。

更精确地讲, @Qualifier("iceCream") 指的是拥有 "iceCream" 限定符的bean,所有的beans会被赋予一个默认与bean ID相同的限定符,这样带来的一个问题就是当类名修改的时候,限定符就会改变,导致找不到bean。解决办法是你可以在bean定义的时候,加上 @Qualifier("cold") 注解来设置限定符,cold用来说明该bean的特性,而不是依赖于bean ID。

3.4 beans作用域

默认情况下,所有spring application context中的beans都是单实例的,方便重用。Spring提供以下几种作用域:

  • Singleton—One instance of the bean is created for the entire application.
  • Prototype—One instance of the bean is created every time the bean is injected into or retrieved from the Spring application context.
  • Session—In a web application, one instance of the bean is created for each session.
  • Request—In a web application, one instance of the bean is created for each request.

可以使用 @Scope 注解:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE),指定了scope为prototype,而session和request作用域有所不同:

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

注意到proxyMode属性,这是用来解决把session或request域的bean注入到singleton域bean的问题,因为单例bean需要在context加载的时候创建,但是session或request域的bean此时还没有创建,所以Spring会注入一个代理bean,暴露相同的方法。ScopedProxyMode.INTERFACES表示代理bean应该实现ShoppingCart接口,代理请求到相应的实现bean。不过如果ShoppingCart不是一个接口,而是一个具体类,则必须使用CGLib生成一个基于类的代理,要把proxyMode设为ScopedProxyMode.TARGET_CLASS。

3.5 运行时值注入

我们需要在运行时注入值,而不是硬编码到配置类中。 可以使用 @PropertySource 注解和 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"));
  }
}

还可以使用属性占位符(property placeholders):

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

要使用占位符值,必须配置 PropertyPlaceholderConfigurer 或者 PropertySourcesPlaceholderConfigurer bean,从Spring 3.1开始,推荐 PropertySourcesPlaceholderConfigurer :

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

Spring Expression Language(SpEL)

SpEL功能强大,包括:

引用beans,属性和方法:#{sgtPeppers}、#{sgtPeppers.artist}、#{artistSelector.selectArtist()}、#{artistSelector.selectArtist()?.toUpperCase()},?.操作符保证null-safe,如果为null,就不调用,返回null。

在表达式中引用类型:T(java.lang.Math).PI、T(java.lang.Math).random()

正则表达式匹配:#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.com'}

集合和数组操作:#{jukebox.songs[4].title},#{jukebox.songs.?[artist eq 'Aerosmith']}(筛选所有符合条件的项组成list), #{jukebox.songs.^[artist eq 'Aerosmith']} (返回第一个符合条件的项, .$[]是返回最后一个),#{jukebox.songs.![title]}(返回所有项的title字段组成list)。

results matching ""

    No results matching ""