03 高级装配

3.1 环境与profile

对于某些特定的bean或配置,在开发环境、QA环境和生产环境所需要的配置是不一致的,例如数据库配置、加密算法、外部系统的集成等

对于这个问题,经典的解决方案是:
在单独的配置类或配置文件中配置每个可能变化的bean,然后在构建阶段决定要将哪一个配置编译到可部署的应用中
这种方式的问题是在于要为每种环境重新构建应用
从开发阶段迁移到QA阶段时,重新构建也许不是什么大问题,但是,从QA阶段迁移到生产阶段时,重新构建可能引入bug并且会在QA团队的成员中带来不安的情绪

对于这个问题,Spring提供了不需要重新构建的解决方案

1.配置profile bean

Spring为环境相关的bean所提供的解决方案其实与构建时的方案没有太大的差别,还是根据环境决定该创建那个bean和不创建哪个bean
不过,Spring并不是在构建的时候做出这样的决策,而是等到运行时再来确定
这样的结果就是一个部署单元能够适用于所有的环境,没有必要进行重新构建
这种思想有点类似静态联编与动态联编的区别、又类似于自己new一个对象和利用反射构造对象的区别

Spring3.1引入了bean profile的功能
要使用profile,首先要将所有不同的bean整理到一个或多个profile之中,然后在将应用部署到每个环境时,要确保对应的profile处于激活状态即可

可使用@Profile指定某个bean所对应的环境,只有对应的环境被激活时,才会构造对应的Bean,否则@Bean注解会被忽略掉

若在XML中,可以重复利用<beans>的profile属性定义对应的环境

2.激活profile

Spring根据spring.profiles.active和spring.profiles.default两个变量来确定哪个profile处于激活状态
如果设置了spring.profiles.active属性,就会用它来确定哪个profile是激活的
如果没有设置spring.profiles.active,那么会去查找spring.profiles.default的值
如果二者都没有设置,表示没有激活的profile,那么只会创建那些没有在profile中的bean

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

DispatcherServlet的初始化参数:<init-param>标签
Web应用上下文参数:<context-param>标签
JNDI条目
作为环境变量
作为JVM的系统属性
在测试类上,使用@ActiveProfiles注解设置

建议的做法:

开发阶段使用DispatcherServlet的参数将spring.profiles.default设置开发环境的profile
一般为了兼顾ContextLoaderListener,还会在Web上下文中设置相同的profile
当应用部署到QA、生产或者其他生产环境时,负责部署的人根据情况使用系统属性、环境变量或者JNDI设置spring.profiles.active即可
当设置了spring.profiles.active后,spring.profiles.default的值已经无所谓了,系统会优先使用spring.profiles.active的值

执行单元测试时,可以使用@ActiveProfiles注解设置profile,已激活相应的生产环境

3.2 条件化的bean

Spring4引入@Conditional注解,用于满足某些条件时,才实例化某些Bean
@Conditional参数为一个Class,且这个类要求实现Condition接口,当调用matches方法的返回值为true时,才创建bean,否则不差UN高进

编写matches方法时,涉及对ConditionContext好AnnotatedTypeMetadata对象的使用

Spring4开始,对@Profile注解进行了重构,使其基于@Conditional和Condition实现

3.3 处理自动装配的歧义性

当有多个同类型的bean,Spring无法按类型自动装配时(可能会尝试按名称),会抛出NoUniqueBeanDefinitionException异常

可以使用@Primary注解标注首选Bean,告知Spring在歧义时首选这个Bean
在XML中可使用bean的primary="true"完成相同的功能

如果配置了多个首选Bean,那么仍然会进一步导致歧义问题

此时可以考虑使用@Qualifier注解为bean设置自己的限定符,然后在注入时用@Qualifier定义要注入的bean的id

其实,@Autowired会先尝试按类型装配,若有多个同类型,则尝试按变量名自动装配,若都没有,自动装配失败
因此,只需@Qualifier定义的名字和@Autowired的变量名称一致即可省略装配时的@Qualifier注解

还可以使用自定义注解,同时添加多个限定条件,来表示某个Bean可以同时具有多个名字

3.4 bean的作用域

Spring中定义了多种作用域,可以基于这些作用域创建bean:
单例:singleton,在整个应用中,只创建bean的一个实例
原型:prototype,每次注入或者获取这个bean时,都会创建一个新的bean实例(如和Struts2整合时就要使用这个作用域)
会话:session,在web应用中,为每个会话创建一个bean实例
请求:在Web应用中,为每个请求创建一个bean实例

默认情况下,Spring应用上下文中的bean都是以单例的形式创建的,如果要使用其他的作用域,可使用@Scope注解

@Scope注解可以与@Component或@Bean注解一起使用

普通工程常见作用域

@Scope("singleton") = @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON),这个也是默认值
@Scope("prototype") = @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

web工程常见的作用域

@Scope("request") = @Scope(WebApplicatonContext.SCOPE_REQUEST)
@Scope("session") = @Scope(WebApplicatonContext.SCOPE_SESSION)

在XML中,可以使用<bean>元素的scope属性来设置作用域

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

注意使用请求会话作用域时,要设置proxyMode属性,该属性用于解决将会话或请求作用域的bean注入到单例的bean中所导致的问题

对于一个单例的bean,Spring上下文加载时就会进行创建,这个bean可能要求注入一个机遇请求或会话作用域的bean
但容器启动吧阶段,用于没有发出请求,这个bean是不存在的,因此Spring注入的是一个代理的Bean,这个bean暴露的接口和真正的bean的接口是一致的

当proxyMode设置为ScopedProxyMode.INTERFACES,该代理技术要求这个bean要实现接口,以把接口委托给代理类
我猜测底层估计使用的是JDK的动态代理实现的,JDK的动态代理技术要求被代理的类必须实现接口

当proxyMode设置为ScopedProxyMode.TARGET_CLASS时,采用CGLIB来生成代理
由于CGLib是采用继承技术实现的代理,因此对于被代理的类只要不是final,能被继承即可

2.在XML中生命作用域代理

可以使用<bean>标签的子标签<aop:scoped-proxy/>设置代理技术
默认情况下,它会使用CGLib创建目标类的代理,若想更改,可以将proxy-target-class属性设置为false
<aop:scoped-proxy proxy-target-class="false"/>

3.5 运行时注入

有时可能希望避免硬编码,而是在运行时再确定,Spring提供两种在运行时求值的方式

可使用注解@PropertySource("classpath:/db.properties")引入相关配置文件,然后在代码中自动注入Environment,获取properties

Environment简单介绍

String getProperty(String key)
String getProperty(String key, String defaultValue)
T getProperty(String key, Class<T> type)
T getProperty(String key, Class<T> type, T defaultValue)

前两个方法以String类型返回,且第二个具有默认值

后两个可以指定目标类型,例如连接池数量,可以指定为Integer类型,并且可以设置一个默认值

如果对应的key不存在,则返回null,如果希望属性必须定义,可以使用getRequiredProperty()方法
此时如果对应的属性没有定义,会抛出IllegalStateException

可以使用containsProperty检查属性是否存在

Environment还提供了一系列方法来检查哪些profile处于激活状态
String[] getActiveProfiles():返回激活profile名称的数组
String[] getDefaultProfiles():返回默认profile名称的数组
boolean acceptsProfiles(String... profiles):如果environment支持给定的profile,就返回true

解析属性占位符

在XML中,占位符的使用形式为${...}
在JavaConfig中,使用@Value注解使用占位符设置资源

为了使用占位符,必须配置一个PropertyPlaceholderConfigurer或PropertySourcesPlaceholderConfigurer
Spring3.1后推荐使用PropertySourcePlaceholderConfigurer,因为它能基于Environment及其属性源来解析占位符

如果使用XML配置,<context:property-placeholder/>会自动生成PropertySourcePlaceholderConfigurer

使用Spring表达式语言进行装配

Spring3引入了Spring表达式语言,SpEL,它能以一种强大和简介的方式将值装配到bean属性和构造器参数中,使用的表达式会在运行时计算得到值

SpEL有很多特性,包括:
使用bean的ID来引用bean
调用方法和访问对象的属性
对值进行算术、关系和逻辑运算
正则表达式匹配
集合操作

与属性占位符${}类似,SpEL表达式要放到#{...}之中

T()表达式会将java.lang下的包作为基础路径,如#(System)表示java.lang.System类,T(String)表示String类

在SpEL表达式中可以使用ID引用其他bean

可以通过systemProperties对象引用系统属性

如果使用JavaConfig配置,则利用@Value注解填写SpEL表达式
如果使用XML,则一般填写在property或construct-arg的value属性中

常见字面值:
#{3.1415}为浮点数
#{9.87E4}为科学计数法表示的浮点数
#{‘Hello‘}为字符串
#{true}为Boolean类型

可以使用.来引用属性或者调用方法
此外,?.运算符可以确保对象的非空性,若为空则不引用属性或者调用方法,表达式返回null
例如,#{artist.selectArtist()?.toUpperCase()}

在SpEL中使用类型

T()运算符的结果其实是一个Class对象,如果需要的话,甚至可以装配到一个Class类型的bean属性中
但T()真正的价值在于可以访问目标类型的静态方法和常量,例如#{T(java.lang.Math).random()}

SpEL常用运算符

*, ^, +, ==, eq, ? :, ?:

计算正则表达式

spEL通过matches运算符支持表达式中的模式匹配,返回true或者false
#{admin.email matches ‘[a-zA-Z0-9._%+-][email protected][a-zA-Z0-9.-]+\\.com‘}

计算集合

选取集合中的元素:#{jukebox.song[4].title}
选取String中给定下标字符:#{‘This id a test‘[3]}
.?[]查询运算符,可用于对集合元素进行过滤:#{jukebox.songs.?[artist eq ‘Aerosmith‘]}
.^[]在集合中查询第一个匹配的项
.$[]在集合中查询最后一个匹配的项
.![]可用于投影操作:#{jukebox.song.![title]}

时间: 2024-10-16 20:01:00

03 高级装配的相关文章

spring(3)高级装配

[0]README 0)本文部分文字描述转自:"Spring In Action(中/英文版)",旨在review  spring(3)高级装配 的相关知识: [1]环境与profile(考虑数据库配置) 1)使用嵌入式数据库 @Bean(destroyMethod="shutdown") public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .addScript("cl

Spring 之高级装配

[环境与Profile] 暂略 [条件化的bean] 暂略 [处理自动装配歧义性] 暂略 [ bean 的作用域] 在 @Componen . @Bean 下以及 XML 中的声明方式如下所示, @Component("LonelyHeartsClub") @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // Singleton 作用域是默认的,Prototype 作用域需要特别声明,每次注入都会创建一个新的 Bean 实例 public

spring—高级装配

1. 配置profile Bean 在3.1版本中,Spring引入了bean frofile的功能.要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)状态. @profile("dev") @profile注解应用在类级别上.他会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建.如果dev profile没有激活的话,那么带有@Bean注

第三章 高级装配

Spring profile 条件化的bean声明 自动装配与歧义性 bean的作用域 Spring表达式语言 配置profile bean 在3.1版本中, Spring引入了bean profile的功能. 要使用profile, 你首先要将所有不同的bean定义整理到一个或多个profile之中, 在将应用部署 到每个环境时, 要确保对应的profile处于激活(active) 的状态. 在Java配置中, 可以使用@Profile注解指定某个bean属于哪一个profile. 例如, 在

高级装配

环境与profile 在开发软件的时候,将应用程序从开发环境,迁移到测试环境,或者是迁移到生产环境都是一项挑战.最常见的就是对于DataSource的配置,这三种环境我们会根据不同的策略来生成DataSource bean.我们需要有一种方式来配置DataSource,使其在每种环境下都会选择对应的配置. 配置profile bean 要使用profile,首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到不同环境中,要确保对应的profile文件处于激活状态. 在

第3章 高级装配

3.1 环境与profile 在 3.1 版本中, Spring 引入了 bean profile 的功能.要使用 profile ,你首先要将所有不同的 bean 定义整理到一个或多个 profile 之中,在将应用部署到每个环境时,要确保对应的 profile 处于激活( active )的状态. 在 Java 配置中,可以使用 @Profile 注解指定某个 bean 属于哪一个 profile . @Service @Profile("prod") public class T

spring学习总结——高级装配学习一(处理自动装配的歧义性)

我们已经看到如何使用自动装配让Spring完全负责将bean引用注入到构造参数和属性中.自动装配能够提供很大的帮助.不过,spring容器中仅有一个bean匹配所需的结果时,自动装配才是有效的.如果不仅有一个bean能够匹配结果的话,Spring此时别无他法,只好宣告失败并抛出异常.更精确地讲,Spring会抛出NoUniqueBeanDefinitionException. 当确实发生歧义性时,Spring提供了多种可选方案来解决这样的问题.你可以将可选bean中的某一个设为首选(primar

spring学习总结——高级装配学习三(Bean的作用域)

一.bean的作用域 在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的.也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例.如果你所使用的类是易变的(mutable),它们会保持一些状态,因此重用是不安全的.在这种情况下,将class声明为单例的bean就不是什么好主意了,因为对象会被污染,稍后重用的时候会出现意想不到的问题. 1.Spring定义了多种作用域,可以基于这些作用域创建bean,包括: 单例(Si

Spring框架学习(八):@Qualifier实现高级装配

在开发的时候,你肯定也想要自己的程序更加智能,我指的是自动装配.前面我们专门讲过@Autowired注解能够自动的在上下文中获取满足要求的bean,并将其注入到你想注入的属性中.就像这个样子: 这个例子要完成的内容就是:在程序运行时,自动注入一个Student类的对象到student引用中.问题来了,如果我现在上下文中有多个bean都是Student的对象,会发生什么?答案是装配失败,原因可想而知,因为满足条件的对象有多个,所以Spring不知道该选择哪一个,导致抛出异常. 为了解决这个问题,我