返回

高级装配

Spring实战-第3章——读书笔记

  • Spring profile
  • 条件化的bean声明
  • 自动装配与歧义性
  • bean的作用域
  • Spring表达式语言

环境与profile

软件运行在不同的环境,需要装配不同的bean。比如:在开发环境、QA环境和生产环境可能会用到不同类型的数据库配置。

常规的处理手段是构建针对性的独立配置类(或者XML),在构建阶段确定要将哪一个配置编译到应用中。这种方式需要为每种环境重新构建应用,这将导致各种问题,比如从QA阶段迁移到生产环境时,重新构建可能会引入BUG。

Spring提供了不需要重新构建的解决方案。首先,在Java配置中,通过**@Profile**注解来指定某个bean属于哪一个Profile(或者在XML配置中通过<beans>元素的profile属性置顶);然后,用spring.profiles.active和spring.profiles.default 来指定激活的profile和默认的profile(激活和默认的profile可以同时指定多个)。

指定了profile的bean只有在对应的profile被激活时才会被创建。没有指定profie的bean始终都会被创建。

设置active和default属性的方式

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

条件化的bean

有些时候,对于某些bean,我们希望只有在某些特定的情况满足之后在创建这个bean。Spring提供了**@Conditional**注解来实现根据条件动态配置bean的功能。

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

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class MagicExistsCondition implements Condition {

  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
    return env.containsProperty("magic");
  }
  
}

当matches()方法返回true时bean才会被创建。

处理自动装配的歧义

  1. 通过**@Primary**注解,设置首选bean(XML中对应的是<bean>元素的bool型属性primary)

  2. 通过**@Qualifier**注解,指定注入时要选中的bean

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

    @Qualifier注解所设置的参数就是要注入bean的ID

使用自定义限定符

bean的ID和默认的限定符会因类名的修改而发生变化,从而导致自动装配失败,可以在bean声明上添加**@Qualifier**注解来设置自定义的限定符。

使用自定义限定符注解

使用自定义的限定符,还是会遇到指向多个可选bean的情况,这个时候需要增加多个限定符来进一步缩小范围,但是Java不允许同一个条目上出现相同类型的多个注解。针对这种情况,Spring提供了自定义限定符注解的能力。

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
         ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold { }

在使用时不再用**@Qualifier(“cold”)**,而是使用自定义的@Cold注解,这样就就可以在同一个条目上增加多个自定义注解,将可选范围缩小到只有一个bean满足需求。

@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) {
    this.dessert = dessert;
}

bean的作用域

默认情况下,Spring应用上下文中的bean都是以单例形式创建的——不论给定的bean被注入到其它的bean多少次,每次注入的都是同一个实例。

一般情况下,单例模式就能满足需求。但是仍有单例模式不适用的场景。

Spring定义的作用域:

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

设定作用域要使用**@Scope**注解。

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

对应的XML配置

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

会话和请求作用域

会话作用域例子-购物车

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

value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session),表示Spring需要为Web应用中的每一个会话创建一个ShopeingCart。

proxyMode设置成ScopedProxyMode.INTERFACES。用于解决将会话货请求作用域的bean注册到单例bean中遇到的问题。

假设存在一个单例的StoreService bean:

@Component
public class StoreService {

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

StoreService作为一个单例bean,会在应用上下文加载时创建,当它创建时又会试图将ShoppingCart bean注入到setShoppingCart()方法。而ShoppingCart bean又是会话作用域,需要有用户进入系统,创建了会话之后才会出现其实例。proxyMode设置为ScopedProxyMode.INTERFACES之后,Spring会注入一个到ShoppingCart的代理,这个代理暴露的方法与ShoppingCart相同,当StoreService调用ShoppingCart方法是,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。

proxyMode设置成ScopedProxyMode.INTERFACES时,要求ShoppingCart时接口,而不是类。如果是一个具体的类,需要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS。

运行时值注入

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

注入外部值

  1. 使用**@PropertySource**注解和Environment:
package com.soundsystem;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class EnvironmentConfig {

  @Autowired
  Environment env;
  
  @Bean
  public BlankDisc blankDisc() {
    return new BlankDisc(
        env.getProperty("disc.title"),
        env.getProperty("disc.artist"));
  }
  
}

@PropertySource注解引用路径对应的属性文件会加载到Spring的Environment中。

  1. Environment中getProperty()方法的重载:
  • String getProperty(String key)
  • String getProperty(String key,String defaultValue)
  • T getProperty(String key,Class type)
  • T getProperty(String key,Class type,T defaultValue)
  1. 占位符

    XML中的属性占位符:

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

Spring表达式语言

SpEL(Spring Expression Language,SpEL)表达式要放到“#{ ··· }”之内,格式上与属性占位符类似。

  • 使用bean的ID来引用bean

    #{sgtPeppers.artist}
    
  • 调用方法和访问对象的属性

    #{T(System).currentTimeMillis()}
    
  • 对值进行算术、关系和逻辑运算

    #{2 * T(java.lang.Math).PI * circle.radius}
    
  • 正则表达式匹配

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

    #{jukebox.songs[4].title}
    
Licensed under CC BY-NC-SA 4.0
最后更新于 6月8日, 2021 17:25 CST
当前页面阅读次数: 0
Built with Hugo
Theme Stack designed by Jimmy