Spring系列之三:高级装配

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/liu425151047/article/details/97814919

一、环境与profile

在开发软件的时候, 有一个很大的挑战就是将应用程序从一个环境迁移到另外一个环境。

开发阶段中,某些环境相关做法可能并不适合迁移到生产环境中,甚至即便迁移过去也无法正常工作。

数据库配置、加密算法以及与外部系统的集成是跨环境部署时会发生变化的几个典型例子。

1、配置profile bean

要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。

在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile。

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {
    ...
}

 

它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。如果dev profile没有激活的话,那么带有@Bean注解的方法都会被忽略掉。

同样,也可能需要一个适用于生产环境的配置:

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

 

在Spring 3.1中,只能在类级别上使用@Profile注解。不过,从Spring 3.2开始, 你也可以在方法级别上使用@Profile注解,与@Bean注解一同使用。这样的话,就能将这两个bean的声明放到同一个配置类之中。

 

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

 

2、激活profile

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

如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设
置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。

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

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

二、条件化的bean

假设你希望一个或多个bean只有在应用的类路径下包含特定的库时才创建。 或者我们希望某个bean只有当另外某个特定的bean也声明了之后才会创建。 我们还可能要求只有某个特定的环境变量设置之后, 才会创建某个bean。

Spring 4引入了一个新的@Conditional注解, 它可以用到带有@Bean注解的方法上,实现条件化配置。

例如有一个名为MagicBean的类, 我们希望只有设置了magic环境属性的时候, Spring才会实例化这个类。 如果环境中没有这个属
性, 那么MagicBean将会被忽略。

 

可以看到,@Conditional中给定了一个Class,它指明了条件——在本例中,也就是MagicExistsCondition,@Conditional将会
通过Condition接口进行条件对比:

 

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

下面展示了MagicExistsCondition, 这是完成该功能的Condition实现类:

 

它通过给定的ConditionContext对象进而得到Environment对象, 并使用这个对象检查环境中是否存在名为magic的环境属性。

ConditionContext是一个接口, 通过它我们可以做到如下几点:

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

三、处理自动装配的歧义性

仅有一个bean匹配所需的结果时,自动装配才是有效的。如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍Spring自动装配属
性、构造器参数或方法参数。

举个例子来说明什么是歧义,假设我们使用@Autowired注解标注了setDessert()方法:

 

在本例中, Dessert是一个接口, 并且有三个类实现了这个接口,分别为Cake、Cookies和IceCream:

 

因为这三个实现均使用了@Component注解, 在组件扫描的时候, 能够发现它们并将其创建为Spring应用上下文里面的bean。 但是当Spring试图自动装配setDessert()中的Dessert参数时,它并没有唯一、 无歧义的可选值,只能抛出异常。

当确实发生歧义性的时候, Spring提供了多种可选方案来解决这样的问题。 你可以将可选bean中的某一个设为首选(primary) 的
bean, 或者使用限定符(qualifier)

1、标示首选的bean

下面的代码展现了如何通过@Primary将@Component注解的IceCream bean声明为首选的bean:

 

或者, 如果你通过Java配置显式地声明IceCream, 那么@Bean方法应该如下所示:

 

但是,如果你标示了两个或更多的首选bean,那么它就无法正常工作了。这带来了新的歧义性问题,实际上也就是没有首选bean了。

2、限定自动装配的bean

@Qualifier注解是使用限定符的主要方式。 例如,我们想要确保要将IceCream注入到setDessert()之中:

 

为@Qualifier注解所设置的参数就是想要注入的bean的ID。这里的问题在于setDessert()方法上所指定的限定符与要注入的bean的名称是紧耦合的。对类名称的任意改动都会导致限定符失效。

我们可以为bean设置自己的限定符,在bean声明上添加@Qualifier注解,以与@Component组合使用:

 

在这种情况下,cold限定符分配给了IceCreambean。

如果两个bean都命名为cold,我们再次遇到了歧义性的问题,而且Java不允许在同一个条目上重复出现相同类型的多个注解:

(注:Java 8允许出现重复的注解,只要这个注解本身在定义的时候带有@Repeatable注解就可以。 不过, Spring的@Qualifier注解并没有在定义时添加@Repeatable注解。)

 

我们可以创建自定义的限定符注解,借助这样的注解来表达bean所希望限定的特性。

 

这样我们将不再使用@Qualifier("cold"),而是使用自定义的@Cold注解。同样,你可以创建一个新的@Creamy注解来代替@Qualifier("creamy")。

通过在定义时添加@Qualifier注解,它们就具有了@Qualifier注解的特性。它们本身实际上就成为了限定符注解。

 

最终, 在注入点, 我们使用必要的限定符注解进行任意组合, 从而将可选范围缩小到只有一个bean满足需求。 

四、bean的作用域

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

Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

  • 单例(Singleton):在整个应用中,只创建bean的一个实例。

  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。

  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。

  • 请求(Rquest):在Web应用中,为每个请求创建一个bean实例。

单例是默认的作用域,可以使用@Scope注解来选择其它的作用域。

 

这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型作用域。你当然也可以使用@Scope("prototype"),但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。

如果你想在Java配置中将Notepad声明为原型bean,那么可以组合使用@Scope和@Bean来指定所需的作用域:

 

 

展开阅读全文

没有更多推荐了,返回首页