Spring - 3. 注入

高级注入

环境注入

通过 profile, Spring 支持为不同的运行环境,如 dev, qa, prod 注入不同的 bean. 体现在:

  1. 为不同的环境配置不同的 configuration/xml
  2. 在同一个 configuration/xml 中, 为不同的环境配置不同的 bean

Java Config 配置 profile

为不同的环境配置不同的 configuration

@Configuration
@Profile("prod")
public class ProductionProfileConfig {
}

在同一个 configuration 中, 为不同的环境配置不同的 bean

@Configuration
public class DataSourceConfig {
@Bean(destroyMethod="shutdown")
@Profile("dev")
public DataSource embeddedDataSource() {
// ...
}

@Bean
@Profile("prod")
public DataSource jndiDataSource() {
// ...
}
}

XML 配置 profile

为不同的环境配置不同的 xml



<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>

在同一个 xml 中, 为不同的环境配置不同的 bean




<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>



激活 profile

Spring 有两个参数来指定哪个 profile 被激活:

  • spring.profiles.active, 若设置, 则 default 值无效
  • spring.profiles.default

若都没设, 则没有 profile 被激活.

profile 设置的方法有:

  1. DispatcherServlet 的初始化参数

  2. Web application 的 context param

  3. JNDI entries

  4. 环境变量

  5. JVM 系统属性

  6. @ActiveProfiles注解, 一般用于集成测试.

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes={PersistenceTestConfig.class})
    @ActiveProfiles("dev")
    public class PersistenceTest {
    }

条件注入

条件注入适用于以下场景:

  1. classpath 中存在特定的库, 才会创建 bean
  2. 当 某一个 bean 被声明, 才会创建 bean
  3. 当 某环境变量被设置, 才会创建 bean

条件注入方法:

  1. 创建 Bean 时, 使用 @Conditional(条件类.class)

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

  2. 创建 条件类, 该类需要实现 Condition 接口, 重写其 match 方法

    public class MagicExistsCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
    return env.containsProperty("magic");
    }
    }

ConditionContextAnnotatedTypeMetadata

通过 ConditionContext 可以获取的信息有:

public interface ConditionContext {
BeanDefinitionRegistry getRegistry(); // 检查 Bean Definition
ConfigurableListableBeanFactory getBeanFactory(); // 获取 BeanFactory, 检查 Bean 的注入情况
Environment getEnvironment(); // 检查环境变量
ResourceLoader getResourceLoader(); // 获取资源
ClassLoader getClassLoader(); // 获取 ClassLoader
}

通过 AnnotatedTypeMetadata 可以获取的信息有:

public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationType); // 是否被标注
Map<String, Object> getAnnotationAttributes(String annotationType); // 获取标注的属性
Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);
}

自动装配消除二义

如果在装配时有多个 Bean 满足条件, 那么会抛出:

org.springframework.beans.factory.NoUniqueBeanDefinitionException 异常

解决方法:

  1. @Primary, 提高一个 Bean 注入的优先级
  2. @Qualifier("BeanID"), 注入时选择要被注入的 Bean.
  3. 自定义 @Qualifier 标注

@Primary

  1. 可以作用在类名上, 修饰 @Component

    @Component
    @Primary
    public class IceCream implements Dessert

  2. 可以作用在方法上, 修饰 @Bean

    @Bean
    @Primary
    public Dessert iceCream() {
    return new IceCream();
    }

  3. 也可以使用 XML

    <bean id="iceCream class="com.desserteater.IceCream"
    primary="true" />

@Primary的不足在于, 同一种类型可以指定多个 @Primary, 此时, Spring 还是无法决定哪个 Bean 被注入, 会抛出异常.

@Qualifier

@Qualifier 可以和 @Autowired 或者 @Inject 一起连用, 可以指明要注入的 Bean.

@Autowired
@Qualifier("iceCream") // 注入 ID 或者 qualifier-name 为 iceCream 的 Bean
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

@Qualifier("ID/QualifierName") 可以用 qualifier-name 来匹配. qualifier-name 可以通过以下方式指定:

@Component
@Qualifier("cold")
public class IceCream implements Dessert { ... }

或作用在方法上:

@Bean
@Qualifier("cold")
public Dessert iceCream() {
return new IceCream();
}

使用 @Qualifier 的问题是:

  1. Bean 的 ID 是用字符串指定的, 而字符串不会被编译器检查, 相当于硬编码, 日后注入 Bean 的 Id 改动, 就会报错.
  2. 当两个 Bean 的 qualifier-name 相同时, 又会产生二义性.

自定义 @Qualifier 标注

使用自定义的 @Qualifer 标注可以解决:

  1. 首先定义一个注解

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

  2. 然后用注解修饰生成 Bean 的方法或者类

    @Component
    @Cold // 注解 Code
    @Creamy // 注解 Creamy
    public class IceCream implements Dessert

  3. 在注入的时候, 指定注解的 Bean

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

这样即解决了二义性的问题, 又解决了硬编码的问题.

Bean 的作用域

默认 Spring 的 Bean 为 Singleton. Spring 提供了以下作用域:

  • Singleton (默认)
  • Prototype
  • Session
  • Request

如何指定 Prototype 作用域?

  1. JavaConfig 中的 @Scope 注解可以指定 bean 的作用域:

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Notepad notepad() {
    return new Notepad();
    }

  2. XML 的 scope 属性

Request 和 Session 作用域

为什么 Singleton 和 Prototype 不适用于 Web?

比如购物车, 不能所有人公用一个购物车(Singleton), 也不能系统每次需要就注入一个新的购物车.

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

与 Prototype 不同的是, Session 还要指定一个 proxyMode. 这是应为, 当服务启动 Spring 开始注入时, ShoppingCart 要注入到很多服务中:

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

但是此时这个对象还没有被创建. ShoppingCart 的创建是第一个相应用户请求到来时. 而且, Service 实际是要服务于多个用户, 意思是要注入不同的购物车.

所以, Spring 此时选择注入一个代理. 等到真正调用方法时, proxy 会将调用委托到真正的 ShoppingCart 方法上.

proxyMode 的选择

  • 如果 ShoppingCart 是接口, 那么 proxyMode 应该为 proxyMode=ScopedProxyMode.INTERFACES. JDK 实现的动态代理.
  • 如果 ShoppingCart 是具体类, 那么 proxyMode 应该未 proxyMode=ScopeProxyMode.TARGET_CLASS. CGLib 实现.

XML 方式注入 Session Bean

  1. 接口注入


    <aop:scoped-proxy proxy-target-class="false" />

  2. 具体类注入


    <aop:scoped-proxy />

运行时值注入

字符串直接在 JavaConfig 或者 XML 中注入属于 hardcode, 怎么解决呢?

  1. Property 占位符
  2. SpEL
posted @ 2017-02-04 13:35  still_water  阅读(349)  评论(0)    收藏  举报