Spring学习笔记(三)高级装配

1、 Spring环境

实际的开发过程中,会有不同的环境存在,如测试环境、生产环境等。

针对不同的环境,需要根据当前需要来判断某些bean是否需要创建,因此,Spring在3.1版本中引入了bean profile功能。

1.1 配置profile bean

在Java配置中,有两种方式来设置bean的profile。

  1. 通过@Profile注解指定摸个bean属于哪一个profile;
  2. 通过在XML中配置<beans>元素的profile属性;

第一种方式的示例如下:

@Configuration
@Profile("dev")
public class StudentConfig {

    @Bean
    public Student oneStudent() {
        return new Student();
    }
}

此处的@Profile("dev")应用在了类级别上,它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。

@Bean
@Profile("dev")
public Student oneStudent() {
    return new Student();
}

相对的,@Profile注解也可以应用在方法级别上,表示当前bean只有在dev profile激活时才会创建。


第二种方式的示例如下:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:xsi="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd"
       profile="dev"
>

通过设置根<beans>元素的profile属性来配置环境,只有profile属性与当前激活profile相匹配的配置文件才会被用到。

此外,还可以采用在根<beans>元素中嵌套定义<beans>元素,将所有的profile bean定义放到同一个XML文件

中,如下:

<beans profile="dev">
    <bean>
        ...
    </bean>
</beans>

<beans profile="qa">
    <bean>
        ...
    </bean>
</beans>

<beans profile="prod">
    <bean>
        ...
    </bean>
</beans>

这种形式,可以将所有的bean定义到同一个XML文件中,在运行时,会根据当前的激活状态来决定创建bean。

1.2 激活profile

Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.activespring.profiles.default

(1)如果设置了spring.profiles.active属性,设置的值就会用来确定哪个profile是激活的。

(2)如果没有设置spring.profiles.active属性,就会查找spring.proflies.default的值。

(3)如果两个都没有设置,则没有激活的profile,只会创建那些没有定义在profile中的bean。

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

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

2、 条件化的bean声明

在某些情况下,需要某个bean在满足前置条件下才创建。

Spring 4中引入了@Conditional注解,可以应用在带有@Bean注解的方法上,只有在给定条件的结果为true,该bean才会创建,否则该bean就会被忽略。

@Bean
@Conditional(StudentExistCondition.class)
public Student oneStudent() {
    return new Student();
}

如上,该Student类,只有在满足条件的情况下,才会被实例化。

@Conditional中给定了一个class,指明了条件。

StudentExistCondition.class如下:

public class StudentExistCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        return environment.containsProperty("dev");
    }
}

该类实现了Condition接口,并实现了接口中的matches方法,当matches()方法返回true时,所有@Conditional注解上引用StudentExistCondition的bean就会被创建。

3、 自动装配与歧义性

在使用@Autowired注解,若存在多个可匹配的结果时,这种歧义性会阻碍Spring自动装配属性、构造器参数或方法参数。

例如,如下有一个方法setOwner()

@Autowired
public void setOwner(People people) {
    this.people = people;
}

People是一个接口,有若干个实现类,如下:

@Component
public class Student implements People{ ... }

@Component
public class Teacher implements People{ ... }

由于这两个实现类都使用了@Component注解,在组件扫描中,能够发现它们并将其创建为Spring应用上下文里面的bean。当Spring自动装配setOwner()中的People参数时,造成没有唯一无歧义的可选值,导致抛出异常。

Spring提供了如下方案来解决。

3.1 标示首选的bean

在声明bean的时候,将其中一个可选的bean设置为primary,能够避免自动装配时的歧义性。Spring会在遇到歧义性时使用首选的bean。

(1)@Primary@Component组合用在组件扫描的bean上。

@Component
@Primary
public class Student implements People{ ... }

(2)@Primary@Bean组合用在Java配置的bean声明中。

@Bean
@Primary
public Peolple Student(){
	return new Student();
}

3.2 使用限定符限定自动装配

使用限定符的方式是在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件。

通过使用@Qualifier注解,可以与@Autowired@Inject协同使用。

@Autowired
@Qualifier("student")
public void setOwner(People people){
    this.people = people;
}

其中,为@Qualifier注解所设置的参数就是要注入的bean的ID。

注:使用限定符装配,其所指定的限定符与要注入的bean的名称是紧耦合的,对类名称的任意改动都会导致限定符失效。

3.2.1 自定义限定符

此外,也可以给bean设置自己的限定符,而不依赖于bean ID作为限定符。

通过在bean声明上添加@Qualifier注解,如下:

@Component
@Qualifier("boyStudent")
public class Student implements People{ ... }

同时,在需要注入的地方,只要引用自己设定的限定符,此处为“boyStudent”,如下:

(1)与@Autowired一起使用

@Autowired
@Qualifier("boyStudent")
public void setOwner(People people){
    this.people = people;
}

(2)与@Bean一起使用

@Bean
@Qualifier("boyStudent")
public People Student(){
    return new Student();
}
3.2.2 使用自定义的限定符注解

假设有多个bean使用了相同的限定符,也会产生歧义,而Java不允许在一个条目上出现多个相同类型的注解。

因此,可以通过创建自定义的限定符注解来解决这个问题。

做法:将如上的@Qualifier("bodyStudent")替换为自定义的@BoyStudent注解。

注解的定义如下所示:

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

通过定义时添加@Qualifier注解,就具有了@Qualifier注解的特性。

这样,上例中就可以将@Qualifier("bodyStudent")替换成@BoyStudent,其作用相同。

@Autowired
@BoyStudent
public void setOwner(People people){
    this.people = people;
}

通过自定义的限定符注解,可以对必要的限定符进行任意组合,进一步缩小可选范围。

4、 bean的作用域

Spring中默认bean是单例的。

除此之外,Spring定义了多种作用域,如下所示:

  1. 单例(Singleton):在整个应用中,值创建bean的一个实例;
  2. 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例;
  3. 会话(Session):在Web应用中,为每个会话创建一个bean实例;
  4. 请求(Request):在Web应用中,为每个请求创建一个bean实例;

如果选择其他的作用域,要使用@Scope注解,它可以与@Component@Bean一起使用。

4.1 单例作用域

Spring中默认是单例作用域。

4.2 原型作用域

例如:将bean声明为原型bean:

(1)使用@Scope("prototype")注解

@Component
@Scope("prototype")
public class Notepad{ ... }

(2)使用@Scope("ConfigurableBeanFactory.SCOPE_PROTOTYPE")注解

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

4.3 会话与请求作用域

在Web应用中,在会话范围内共享bean,即用到会话作用域。

常见场景:在电商项目中,有一个bean表示用户购物车,若采用单例作用域,则不同用户在添加商品时都会将商品添加到同一个购物车中;若采用原型作用域,则用户在某一个地方向购物车添加商品,在另一个地方则不可用。

对于购物车bean,采用会话作用域,可以将该实例与给定的用户相关联。

设置会话作用域如下:

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

此处,将value设置成WebApplicationContext中的SCOPE_SESSION常量(值为session)。告诉Spring为Web应用中的每个会话创建一个ShopCar。

相当于在当前会话中,bean相当于单例的。

另一个proxyMode属性,设置成ScopeProxyMode.INTERFACES。该属性解决了将会话作用域及请求作用域的bean注入到单例bean中会遇到的问题。

适用场景:如下有一个单例的StoreServicebean的Setter方法,需要将ShopCar bean注入。

@Component
public class StoreService{
    
    @Autowired
    public void setShopCar(ShopCar shopCar){
        this.shopCar = shopCar;
    }
}

由于StoreService是单例bean,在Spring应用上下文加载的时候创建。当创建时,Spring会将ShopCar的bean注入到setShopCar()方法中,但由于ShopCar bean是会话作用域的,此时不存在,只有在创建了会话之后才会有ShopCar实例。

如配置,proxyMode属性被设置成ScopedProxyMode.INTERFACES,Spring会注入一个到ShopCar bean的代理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值