Spring 高级装配

一、Spring 高级装配

1.1 配置profile bean

1)在Java类中配置profile

Spring引入了bean profile的功能。要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。
在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile。例如,在配置类中,嵌入式数据库的DataSource可能会配置成如下所示:

import org.springframework.context.annotation.*;

@Configuration
@Profile("dev")
public class MyApplicationContext {
}

希望你能够注意的是@Profile注解应用在了类级别上。它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。如果dev profile没有激活的话,那么带有@Bean注解的方法都会被忽略掉。
在Spring 3.1中,只能在类级别上使用@Profile注解。不过,从Spring 3.2开始,你也可以在方法级别上使用@Profile注解,
与@Bean注解一同使用。

import org.springframework.context.annotation.*;

@Configuration
public class MyApplicationContext {

    @Bean
    @Profile("dev")
    public Apple apple() {
        return  new Apple();
    }
}

2)在XML中配置profile

我们也可以通过<beans>元素的profile属性,在XML中配置profile bean。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/tx
         http://www.springframework.org/schema/tx/spring-tx.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/util
         http://www.springframework.org/schema/util/spring-util.xsd
         http://www.springframework.org/schema/data/jpa
         http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
         profile="dev">
</beans>

你还可以在根<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个profile XML文件。这能够将所有的profile bean定义放到同一个XML文件中,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/tx
         http://www.springframework.org/schema/tx/spring-tx.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/util
         http://www.springframework.org/schema/util/spring-util.xsd
         http://www.springframework.org/schema/data/jpa
         http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <beans profile="dev"></beans>
    <beans profile="qa"></beans>
    <beans profile="prod"></beans>

</beans>

3)激活profile

Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean。你可以同时激活多个profile,这可以通过列出多个profile名
称,并以逗号分隔来实现。

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

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

最常用的一种方式是使用DispatcherServlet的参数将spring.profiles.default设置为开发环境的profile,我会在Servlet上下文中进行设置(为了兼顾到ContextLoaderListener)。
例如,在Web应用中,设置spring.profiles.default的web.xml文件会如下所示:

图片,待上传106p

如果不是Web工程可以使用一下方式设置

System.setProperty("spring.profiles.active","qa" );

1.2 条件化的bean

假设你希望一个或多个bean只有在应用的类路径下包含特定的库时才创建。或者我们希望某个bean只有当另外某个特定的bean也声明了之后才会创建。我们还可能要求只有某个特定的环境变量设置之后,才会创建某个bean。
在Spring 4之前,很难实现这种级别的条件化配置,但是Spring 4引入了一个新的@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。

@Bean
@Conditional(AppleConditional.class)
public Apple apple3() {
    return  new Apple("apple3", "yellow", null);
}

,@Conditional中给定了一个Class,它指明了本例中的条件,也就是AppleConditional。@Conditional注解将会通过Condition接口进行条件对比:

public interface Condition {
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class AppleConditional implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {

        return false;
    }
}

设置给@Conditional的类可以是任意实现了Condition接口的类型。可以看出来,这个接口实现起来很简单直接,只需提供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法
返回false,将不会创建这些bean。

其中ConditionContext类的提供了一些有用的方法

  • 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
  • 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
  • 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
  • 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
  • 借助getClassLoader()返回的ClassLoader加载并检查类是否存在。

AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解。像ConditionContext一
样,AnnotatedTypeMetadata也是一个接口。

public interface AnnotatedTypeMetadata {
    boolean isAnnotated(String var1);

    Map<String, Object> getAnnotationAttributes(String var1);

    Map<String, Object> getAnnotationAttributes(String var1, boolean var2);

    MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);

    MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
}

1.3 处理自动装配的歧义性

1)标示首选的bean

在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。当遇到歧义性的时候,Spring将会使用首选的bean,而不是其他可选的bean。实际上,你所声明就是“最喜欢”的bean。但是,如果你标示了两个或更多的首选bean,那么它就无法正常工作了。

@Bean
@Primary
public Apple apple3() {
    return  new Apple("apple3", "yellow", null);
}
<bean id="apple" class="mine.Apple" primary="true" />

2)限定自动装配的bean

设置首选bean的局限性在于@Primary无法将可选方案的范围限定到唯一一个无歧义性的选项中。它只能标示一个优先的可选方案。当首选bean的数量超过一个时,我们并没有其他的方法进一步缩小可选范围。
与之相反,Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件。如果将所有的限定符都用上后依然存在歧义性,那么你可以继续使用更多的限定符来缩小选择范围。
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。为@Qualifier注解所设置的参数就是想要注入的bean的ID。

@Autowired
@Qualifier("apple")
private Apple apple;

3)创建自定义的限定符

我们可以为bean设置自己的限定符,而不是依赖于将bean ID作为限定符。在这里所需要做的就是在bean声明上添加@Qualifier注解。

@Component
@Qualifier("apple")
public class Apple implements Fruit{...}
@Bean
@Qualifier("apple")
public Apple apple3() {
    return  new Apple("apple3", "yellow", null);
}

4)创建自定义的限定符注解

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface AppleOne {
}

1.4 bean的作用域

在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

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

单例是默认的作用域,但是正如之前所述,对于易变的类型,这并不合适。如果选择其他的作用域,要使用@Scope注解,它可以与@Component或@Bean一起使用。

// 声明为原型bean,也可以使用 @Scope("prototype")来声明
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Apple apple3() {
    return  new Apple("apple3", "yellow", null);
}
// 声明为原型bean
<bean id="apple" class="mine.Apple" scope="prototype" />

1)使用会话和请求作用域

在Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方往购物车中添加商品,在应用的另外一个地方可能就不可用了,因为在这里注入的是另外一个原型作用域的购物车。
就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用@Scope注解,它的使用方式与指定原型作用域是相同的:

@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES)

这里,我们将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相当于单例的。
@Scope同时还有一个proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。
如果被注释的是类而不是接口的话需要使用如下配置:

@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)

2)在XML中声明作用域代理

如果你需要使用XML来声明会话或请求作用域的bean,那么就不能使用@Scope注解及其proxyMode属性了。<bean>元素的scope属性能够设置bean的作用域,但是该怎样指定代理模式呢?
要设置代理模式,我们需要使用Spring aop命名空间的一个新元素:

<bean id="apple" class="mine.Apple" scope="session" >
    <aop:scoped-proxy />
</bean>

<aop:scoped-proxy>是与@Scope注解的proxyMode属性功能相同的Spring XML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:

<bean id="apple" class="mine.Apple" scope="session" >
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

1.5 运行时值注入

有时候硬编码是可以的,但有的时候,我们可能会希望避免硬编码值,而是想让这些值在运行时再确定。为了实现这些功能,Spring提供了两种在运行时求值的方式:

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

1)注入外部的值

在Spring中,处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;

@Configuration
@ComponentScan
@PropertySource("classpath:/mine/app.properties")
public class MyApplicationContext {

    @Autowired
    Environment env;

    @Bean
    public Apple apple() {
        String name = env.getProperty("apple.name");
        String color = env.getProperty("apple.color");
        return  new Apple(name, color, null);
    }
}

app.properties文件内容如下

apple.name=apple
apple.color=red

2)深入学习Spring的Environment

当我们去了解Environment的时候会发现,程序上述示列所示的getProperty()方法并不是获取属性值的唯一方法,getProperty()方法有四个重载的变种形式:

public interface Environment extends PropertyResolver {
	// 获取指定的值
	String getProperty(String key);
	// 如果没有指定的值反回defaultValue
	String getProperty(String key, String defaultValue);
	// 获取指定的值,并且转化为对应的类型
	String getProperty(String key, Class<T> type);
	// 获取指定的值,并且转化为对应的类型,如果没有指定的值反回defaultValue
	String getProperty(String key, Class<T> type, T defaultValue);
	// 将属性解析为类
	<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
	// 检查某个属性是否存在
	boolean containsProperty(String key);
	// 返回激活profile名称的数组
	String[] getActiveProfiles();
 	// 返回默认profile名称的数组
 	String[] getDefaultProfiles();
 	// 如果environment支持给定profile的话,就返回true
 	boolean acceptsProfiles(String... profiles);
}

3)解析属性占位符

Spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到Spring bean中。在Spring装配中,占位符的形式为使用“${… }”包装的属性名称。作为样例,我们可以在XML中按照如下的配置设置:

<bean id="apple" class="mine.Apple" c:name="${apple.name}" c:color="${apple.color}">

如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。在这种情况下,我们可以使用@Value注解,它的使用方式与@Autowired注解非常相似。比如,在Apple类中,构造器可以改成如下所示:

public Apple(@Value("${apple.name}") String name) {
    this.name = name;
}

为了使用占位符,我们必须要配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。
从Spring3.1开始,推荐使用PropertySourcesPlaceholderConfigurer,因为它能够基于Spring Environment及其属性源来解析占位符。

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

如果你想使用XML配置的话,Spring context命名空间中的<context:propertyplaceholder>元素将会为你生成PropertySourcesPlaceholderConfigurer bean:

<context:property-placeholder />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书香水墨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值