《Spring实战》笔记 Ch3 高级装配

高级装配

一种方式就是在单独的配置类(或XML文件)中配置每个bean, 然后在构建阶段(可能会使用Maven的profiles,activation属性)确定要将哪一个配置编译到可部署的应用中。这种方式的问题在于要为每种环境重新构建应用。

环境与profile

配置profile bean

Spring为环境相关的bean所提供的解决方案其实与构建时的方案没有太大的差别。当然,在这个过程中需要根据环境决定该创建哪个bean 和不创建哪个bean。不过Spring并不是在构建的时候做出这样的决 策,而是等到运行时再来确定。这样的结果就是同一个部署单元(可 能会是WAR文件)能够适用于所有的环境,没有必要进行重新构建。

在Java配置中,可以使用@Profile注解指定某个bean属于哪一个 profile
在这里插入图片描述
它会告诉 Spring这个配置类中的bean只有在dev profile激活时才会创建

在这里插入图片描述

我们也可以通过元素的profile属性,在XML中配置 profile bean
在这里插入图片描述
在这里插入图片描述

激活profile

Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属 性:spring.profiles.activespring.profiles.default
先找active,再找default
如果都没有找到,那么就没有激活的profile,因此只会创建那些没有定义在profile中的bean
设置active和default的方式:
在这里插入图片描述
在Web应用的web.xml文件中设置默认的profile
在这里插入图片描述
在spring.profiles.active和 spring.profiles.default中,profile使用的都是复数形式。这 意味着你可以同时激活多个profile,这可以通过列出多个profile名称,并以逗号分隔来实现
在这里插入图片描述

条件化的bean

Spring 4引入 了一个新的@Conditional注解,它可以用到带有@Bean注解的方 法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。
在这里插入图片描述
设置给@Conditional的类可以是任意实现了Condition接口的类型
在这里插入图片描述
这个接口实现起来很简单直接,只需提供 matches()方法的实现即可。如果matches()方法返回true,那 么就会创建带有@Conditional注解的bean。如果matches()方法返回false,将不会创建这些bean。

在这里插入图片描述
matches()方法很简单但功能强大。它通过 给定的ConditionContext对象进而得到Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。

在这里插入图片描述
在这里插入图片描述
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方 法上还有什么其他的注解。像ConditionContext一样,AnnotatedTypeMetadata也是一个接口。借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是 不是还有其他特定的注解。借助其他的那些方法,我们能够检查@Bean注解的方法上其他注解的属性。

在这里插入图片描述
在这里插入图片描述

@Profile本身也使用了@Conditional注解,并且引用ProfileCondition作为Condition实 现。如下所示,ProfileCondition实现了Condition接口,并且在做出决策的过程中,考虑到了ConditionContext和AnnotatedTypeMetadata中的多个因素

在这里插入图片描述
AnnotatedTypeMetadata得到了用于@Profile注解的所有属 性。借助该信息,它会明确地检查value属性,该属性包含了bean的 profile名称。然后,它根据通过ConditionContext得到的 Environment来检查[借助acceptsProfiles()方法]该profile是否处于激活状态。

处理自动装配的歧义性

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

Spring提供了多种可选方案来解决 这样的问题。你可以将可选bean中的某一个设为首选(primary)的 bean,或者使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个bean。

标示首选的bean

在这里插入图片描述
@Primary能够与@Component组合用在组件 扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中
在这里插入图片描述

如果你使用XML配置bean的话,同样可以实现这样的功能。 元素有一个primary属性用来指定首选的bean:
在这里插入图片描述

限定自动装配的bean

设置首选bean的局限性在于@Primary无法将可选方案的范围限定到 唯一一个无歧义性的选项中。它只能标示一个优先的可选方案。当首 选bean的数量超过一个时,我们并没有其他的方法进一步缩小可选范围。

@Qualifier注解是使用限定符的主要方式。它可以与@Autowired 和@Inject协同使用,在注入的时候指定想要注入进去的是哪个
bean
在这里插入图片描述
为@Qualifier注解所设置的参数 就是想要注入的bean的ID

基于默认的bean ID作为限定符是非常简单的,但这有可能会引入一些问题。如果你重构了IceCream类,将其重命名为Gelato的话,那此 时会发生什么情况呢?如果这样的话,bean的ID和默认的限定符会变 为gelato,这就无法匹配setDessert()方法中的限定符。自动装
配会失败。这里的问题在于setDessert()方法上所指定的限定符与要注入的 bean的名称是紧耦合的。对类名称的任意改动都会导致限定符失效。

创建自定义的限定符

我们可以为bean设置自己的限定符,而不是依赖于将bean ID作为限定 符。在这里所需要做的就是在bean声明上添加@Qualifier注解。例
如,它可以与@Component组合使用,如下所示:
在这里插入图片描述
当通过Java配置显式定义bean的时候,@Qualifier 也可以与@Bean注解一起使用
在这里插入图片描述
如果有两个cold限定符修饰的Bean怎么办?下面的方法有个问题,Java不允许在同一个条目上重复出现相同类型 的多个注解。如果你试图这样做的话,编译器会提示错误。但是,我们可以创建自定义的限定符注解,借助这样的注解来表达 bean所希望限定的特性。这里所需要做的就是创建一个注解,它本身要使用@Qualifier注解来标注。

在这里插入图片描述

Component和Bean的区别

Spring帮助我们管理bean分为两部分:注册Bean和装配Bean
完成这两个动作有三种方式:通过XML配置文件、通过Java配置类、通过隐式自动装配
@Component作用就相当于XML配置
@Bean需要在Java配置类中使用
这两个都可以通过@Autowired进行装配
如果我们想要使用第三方库中的组件装配到我们的应用,而第三方库的源代码不归我们维护,因此没有办法在它的代码中加上@Component注解,只能通过@Bean和XML的方式进行配置
在这里插入图片描述
在这里插入图片描述
当你不想用@Qualifier注解的时候,可以类似地创建 @Soft、@Crispy和@Fruity。通过在定义时添加@Qualifier注 解,它们就具有了@Qualifier注解的特性。它们本身实际上就成为了限定符注解。
在这里插入图片描述
在注入点,我们使用必要的限定符注解进行任意组合,从而将 可选范围缩小到只有一个bean满足需求
在这里插入图片描述
通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会 再有Java编译器的限制或错误。与此同时,相对于使用原始的 @Qualifier并借助String类型来指定限定符,自定义的注解也更为类型安全。

bean的作用域

在默认情况下,Spring应用上下文中所有bean都是作为以单例 (singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。
在大多数情况下,单例bean是很理想的方案。初始化和垃圾回收对象 实例所带来的成本只留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。有时候,可能会发现,你所使用的类是易变的(mutable),它们会保 持一些状态,因此重用是不安全的。在这种情况下,将class声明为单 例的bean就不是什么好主意了,因为对象会被污染,稍后重用的时候
会出现意想不到的问题。

在这里插入图片描述
可以在bean的 类上使用@Scope注解,将其声明为原型bean
这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE 常量设置了原型作用域。你当然也可以使 用@Scope(“prototype”),但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。

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

使用会话和请求作用域

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

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

这里,我们将value设置成了WebApplicationContext中的 SCOPE_SESSION常量(它的值是session)。这会告诉Spring为 Web应用中的每个会话创建一个ShoppingCart。这会创建多 个ShoppingCart bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相当于单例的。
要注意的是,@Scope同时还有一个proxyMode属性,它被设置成了 ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。

proxyMode所解决问题的场景:
在这里插入图片描述
因为StoreService是一个单例的bean,会在Spring应用上下文加载 的时候创建。当它创建的时候,Spring会试图将ShoppingCart bean 注入到setShoppingCart()方法中。但是ShoppingCart bean是 会话作用域的,此时并不存在。直到某个用户进入系统,创建了会话之后,才会出现ShoppingCart实例。

另外,系统中将会有多个ShoppingCart实例:每个用户一个。我 们并不想让Spring注入某个固定的ShoppingCart实例 到StoreService中。我们希望的是当StoreService处理购物车 功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。

Spring并不会将实际的ShoppingCart bean注入到StoreService中, Spring会注入一个代理,代理ShoppingCart bean。这 个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调 用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean
proxyMode属性被设置成了 ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。
如果ShoppingCart是接口而不是类的话,这是可以的(也是最为 理想的代理模式)。但如果ShoppingCart是一个具体的类的话, Spring就没有办法创建基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,我们必须要将proxyMode属性设置 为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。
在这里插入图片描述

在XML中声明作用域代理

如果你需要使用XML来声明会话或请求作用域的bean,那么就不能使 用@Scope注解及其proxyMode属性了
元素的scope属性能 够设置bean的作用域,但是该怎样指定代理模式呢?
要设置代理模式,我们需要使用Spring aop命名空间的一个新元素:
在这里插入图片描述
<aop:scoped-proxy>是与@Scope注解的proxyMode属性功能相 同的Spring XML配置元素。它会告诉Spring为bean创建一个作用域代 理。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可 以proxy-target-class属性设置为false进而要求它生成基于接口的代理
在这里插入图片描述

运行时值注入

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

  • 属性占位符
  • Spring表达式语言

注入外部的值

在这里插入图片描述
这个属性文件会加载到Spring的Environment中,稍后可以从这里 检索属性。
getProperty()方法有四个重载的变种形式
在这里插入图片描述
在这里插入图片描述
除了属性相关的功能以外,Environment还提供了一些方法来检查 哪些profile处于激活状态:
在这里插入图片描述
直接从Environment中检索属性是非常方便的,尤其是在Java配置 中装配bean的时候。但是,Spring也提供了通过占位符装配属性的方
法,这些占位符的值会来源于一个属性源。

在Spring装配中,占位符的形式为使用“${ … }”包装的属性名称

在这里插入图片描述

如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话, 那么就没有指定占位符的配置文件或类了。在这种情况下,我们可以使用@Value注解,它的使用方式与@Autowired注解非常相似。
在这里插入图片描述
为了使用占位符,我们必须要配置一 个PropertyPlaceholderConfigurer bean 或PropertySourcesPlaceholderConfigurer bean。从Spring 3.1开始,推荐使 用PropertySourcesPlaceholderConfigurer,因为它能够基于Spring Environment及其属性源来解析占位符。
在这里插入图片描述
如果你想使用XML配置的话,Spring context命名空间中的 context:propertyplaceholder元素将会为你生成PropertySourcesPlaceholderConfigurer bean

通过Spring表达式语言进行装配

Spring Expression Language
在这里插入图片描述

需要了解的第一件事情就是SpEL表达式要放到“#{ ... }”之中,这 与属性占位符有些类似,属性占位符需要放到“${ … }”之中。下
面所展现的可能是最简单的SpEL表达式了:

#{1}
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
从系统属性中获取专辑名 称和艺术家的名字:
在这里插入图片描述
在这里插入图片描述

  • 表示字面值
    • #{3.14159}
    • #{‘Hello’}
    • #{false}
  • 引用bean、属性和方法
    • 通过ID引用其他的bean
    • #{sgtPeppers.artist}
    • 引用类型安全的运算符 ?. 这个运算符能够在访问它右边的内容之前,确保它所对应的元素不是null。所以,如果selectArtist()的返回 值是null的话,那么SpEL将不会调用toUpperCase()方法。表达式的返回值会是null。
      • #{artistSelecter.selectArtist()?.toUpperCase()}
  • 在表达式中使用类型
    • 如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关 键的运算符
    • T(java.lang.Math).random()
    • 这里所示的T()运算符的结果会是一个Class对象
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值