Spring系列(三) Bean装配的高级技术
profile
不同于maven的profile, spring的profile不需要重新打包, 同一个版本的包文件可以部署在不同环境的服务器上, 只需要激活对应的profile就可以切换到对应的环境.
@Profile({"test","dev"})
Java Config 通过这个注解指定bean属于哪个或哪些profile. 参数value是一个profile的字符串数组. 此注解可以添加到类或方法上. XML Config 对应的节点是beans的属性profile="dev"
, 可以在根<beans>
节点下嵌套定义分属不同profile的节点, 形成如下结构
<beans ...>
<beans profile="dev">
<bean ...>
<bean ...>
</beans>
<beans profile="prod">
<bean ...>
</beans>
...
</beans>
- 通过
spring.profiles.active
和spring.profiles.default
可以设置激活哪个profile,如果是多个就用","分开, spring优先使用spring.profiles.active
的设置, 如果找不到, 就使用spring.profiles.default
设置的值, 如果二者都没设置, spring会认为没有要激活的profile, 它只会创建不加@Profile
的那些bean.
可以通过多种方式设置这两个属性:
- DispacherServlet的初始化参数
- Web应用的上下文参数
- JNDI
- 环境变量
- JVM系统属性
- 测试类上使用
@ActiveProfiles
注解
下面是web.xml中在上下文以及在servlet中设置spring.profiles.default
的代码
<web-app>
<!--上下文设置default profile-->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
<servlet>
...
<!--设置default profile-->
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
</servlet>
</web-app>
条件化的 bean
Spring4.0 引入了条件化bean, 这些bean只有在满足一定条件下才会创建. profile就是条件化的一种使用方式, 事实上4.0版本后的profile实现机制与其他条件化bean完全一样, 我们可以通过profile的源码一窥条件化bean的机制.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class) //注意这里, 条件化bean的注解
public @interface Profile {
String[] value();
}
@Conditional(ConditionClass.class)
Java Config 可以将这个注解添加到@Bean
注解的方法上, 参数value是一个Class类型的变量, 这个类必须实现Condition
接口, 方法matchs()
返回true则加载bean, 否则忽略.
Condition
接口定义如下:
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
ProfileCondition
的定义
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取profile注解的属性
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
// 获取value属性的值
for (Object value : attrs.get("value")) {
// 测试profile是否激活
if (context.getEnvironment().acceptsProfiles((String[]) value)) {
return true;
}
}
return false;
}
return true;
}
}
- 通过
matchs()
方法的两个参数我们可以做到 (1)通过ConditionContext获取到上下文所需信息;(2)通过AnnotatedTypeMetadata获取到bean的注解
public interface ConditionContext {
// 获取bean的注册信息, 从而可以检查bean定义
BeanDefinitionRegistry getRegistry();
// 获取bean工厂,从而可以判断bean是否存在,获取其他bean,获取bean的状态信息
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
// 获取环境变量,profile中正是使用此对象的方法acceptsProfiles()检查profile是否激活
Environment getEnvironment();
// 获取加载的资源
ResourceLoader getResourceLoader();
// 获取classLoader
@Nullable
ClassLoader getClassLoader();
}
public interface AnnotatedTypeMetadata {
// 检查是否由某注解
boolean isAnnotated(String annotationName);
// 下面几个方法可以获取bean的注解,包括其属性
@Nullable
Map<String, Object> getAnnotationAttributes(String annotationName);
@Nullable
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}
自动装配的歧义
自动装配大大简化了spring的配置, 对于大多数应用对象的依赖bean, 程序实现的时候一般只有一个匹配, 但也存在匹配到多个bean的情况, Spring处理不了这种情况, 这时候就需要由开发人员为其消除装配的歧义.
@Primary
注解用来指定被注解的bean为首选bean. 但如果多个匹配的bean都添加了该注解依然无法消除歧义.@Qualifier
注解用来限定bean的范围. 与@AutoWired
和@Inject
共同使用时,参数value用来指定beanid, 但使用默认的beanid对重构不友好. 与@Component
或@Bean
一起使用时,参数value为bean指定别名, 别名一般是带有描述bean特征的词描述, 然后在注入时就可以使用别名. 但别名可能需要定义多个, Qualifier不支持数组, 也不允许定义多个(原因见下面).@Qualifier
不允许对同一bean重复标记, 这是因为Qualifier注解定义没有添加@Repeatable
注解.
可以使用自定义限定注解的方式达到缩小bean范围的目的.
下面代码使用Qualifier的方式定义bean
@Component
@Qualifier("cheap")
public class QQCar implements Car{
}
@Component
@Qualifier("fast")
public class BenzCar implements Car{
}
@Component
@Qualifier("safe")
//@Qualifier("comfortable") //行不通,不能加两个Qualifier
public class BMWCar implements Car{
}
再使用自定义限定注解的方式
// 定义
...
@Qualifier
public @interface Cheap{}
...
@Qualifier
public @interface Fast{}
...
@Qualifier
public @interface Safe{}
...
@Qualifier
public @interface Comfortable{}
// 使用
@Component
@Safe
@Comfortable
public class BMWCar implements Car{
}
...
使用自定义限定的方式可以随意组合来限定bean的范围, 因为没有任何使用string类型, 所以也是类型安全的.
bean 作用域
bean的四种作用域:
- 单例(Singleton) 默认为此作用域, 全局唯一
- 原型(Prototype) 注入或从上下文获取时均会创建
- 会话(Session) Web程序使用, 同一会话保持唯一
- 请求(Request) Web程序使用,每次请求唯一
@Scope
Java Config 用此注解指定bean的作用域, 参数value用来指定作用域, 如value=ConfigurableBeanFactory.SCOPE_PROTOTYPE
, 原型和单例的常数定义在ConfigurableBeanFactory
中, 会话和请求的常数定义在WebApplicationContext
中 ; 另一个参数proxyMode=ScopedProxyMode.INTERFACES
用来指定创建代理的方式, 示例中指定了使用基于接口的代理方法; 如果使用proxyMode=ScopedProxyMode.TARGET_CLASS
则会使用CGLib来生成基于类的代理.
XML Config对应的时bean的scope="prototype"
属性, 针对web作用域,还需要指定使用代理,示例代码如下.
<bean id="beanid" class="..." scope="session">
<!--需要引用aop命名空间的. proxy-target-class 默认为true, 使用CGLib创建代理, 指定为false则使用接口方式创建代理-->
<aop:scoped-proxy proxy-target-class="false">
</bean>
- 作用域为什么使用代理模式? 如果bean不是单例的, spring会为其创建一个代理对象, 再将这个代理对象注入进去, 实际运行过程中由代理对象委托调用实际的bean. 这样有两点好处:(1)懒加载, bean可以在需要时才创建;(2)便于作用域扩展
属性占位符和SpEL
spring提供了两种运行时注入值的方式, 这样就避免了将字面量硬编码到程序或配置文件里面.
- 属性占位符
- SpEL
一. 属性占位符
先看下面的例子:
@Configuration
@PropertySource("classpath:/path/app.property")
public class MyConfig{
@AutoWired
Environment env;
@Bean
public Car getCar(){
return new QQCar(env.getProperty("qqcar.price"));
}
}
@PropertySource("classpath:/path/app.property")
可以指定加载资源文件的位置, 结合使用Environment类的实例env,可以获取到资源文件中配置的节点.Environment
不仅可以获取到配置的字面量, 它也可以获取复杂类型, 将其转化为对应Class的实例.<T> T getProperty(String key, Class<T> targetType);
- XML Congif 可以使用占位符, 形如
${qqcar.price}
, 前提是配置了context命名空间下的<context:property-placeholder />
它会生成类型为PropertySourcesPlaceholderConfigurer
的bean, 该bean用来解析占位符 - 如果开启组件扫描和自动装配的话, 就没有必要指定资源文件或类了, 可以使用
@Value(qqcar.price)
. 同样, 需要一个类型为PropertySourcesPlaceholderConfigurer
的bean.
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
二. 功能更强大的SpEL
SpEL的特性如下:
- 使用bean的Id来引用bean
- 调用方法和属性: 可以通过beanid来调用bean的方法和属性, 就像在代码中一样.
- 对值进行算术,逻辑,关系运算: 特别注意:
^
乘方运算符;?:
为空运算符 - 正则表达式匹配: 形如#{beanid.mobile matches '1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}'}
- 集合操作:
.?[]
查找运算符;.^[]
查找匹配的第一项;.$[]
查找匹配的最后一项;.![]
投影运算符(比如获取集合中符合条件的对象, 并取其中某个属性映射到结果集合中);
SpEL的示例 #{1},#{T(System).out.print("test")},#{beanId.name},#{systemProperies["path"]}
虽然SpEL功能强大, 我们可以通过SpEL编写复杂的表达式, 但过分复杂的表达式不适合理解和阅读, 同时也会增大测试的难度. 因此建议尽量编写简洁的表达式.
Spring系列(三) Bean装配的高级技术的更多相关文章
- Spring系列(二) Bean装配
创建应用对象之间协作关系的行为称为装配(wiring), 这也是DI的本质. Spring中装配Bean的方式 Spring提供了三种装配Bean的方式. 隐式的Bean发现机制和自动装配 Java ...
- 使用Spring IoC进行Bean装配
Spring概述 Spring的设计严格遵从的OCP(开闭原则),保证对修改的关闭,也就是外部无法改变spring内部的运行流程:提供灵活的扩展接口,也就是可以通过extends,implements ...
- 【ASP.NET Identity系列教程(三)】Identity高级技术
注:本文是[ASP.NET Identity系列教程]的第三篇.本系列教程详细.完整.深入地介绍了微软的ASP.NET Identity技术,描述了如何运用ASP.NET Identity实现应用程序 ...
- Spring系列之bean的使用
一.Bean的定义 <bean id="userDao" class="com.dev.spring.simple.MemoryUserDao"/> ...
- Spring系列(三):Spring IoC中各个注解的理解和使用
原文链接:1. http://www.cnblogs.com/xdp-gacl/p/3495887.html 2. http://www.cnblogs.com/xiaoxi/p/5935 ...
- Spring系列三:IoC 与 DI
水晶帘动微风起,满架蔷薇一院香. 概述 在软件工程中,控制反转(IoC)是一种设计思想,对象之间耦合在一起,在运行时自动绑定,并且它们编译时对所需要引用的对象是不确定的.在这个spring教程中,通过 ...
- ASP.NET Identity系列教程-4【Identity高级技术】
https://www.cnblogs.com/r01cn/p/5194257.html 15 ASP.NET Identity高级技术 In this chapter, I finish my de ...
- Spring框架系列(三)--Bean的作用域和生命周期
Bean的作用域 Spring应用中,对象实例都是在Container中,负责创建.装配.配置和管理生命周期(new到finalize()) Spring Container分为两种: 1.BeanF ...
- Spring学习之旅(九)--SpringMVC高级技术
文件上传 在 Web 应用中,允许用户上传文件是很常见的需求.文件上传通常是采用 multipart 格式,而 DispatcherServlet 并没有任何解析 multipart 请求数据的功能, ...
随机推荐
- mysql中group by和order by混用 结果不是理想结果(转)
文章转自 https://www.cnblogs.com/myphper/p/3767572.html 在使用mysql排序的时候会想到按照降序分组来获得一组数据,而使用order by往往得到的不是 ...
- future builder
import 'package:flutter/material.dart';import 'dart:convert';import 'package:http/http.dart' as http ...
- python购物车demo
product_list = [ ('Iphone',11800), ('Mac Pro',13800), ('BMW CAR',480000), ...
- UNICODE与ASCII
1.ASCII的特点 ASCII 是用来表示英文字符的一种编码规范.每个ASCII字符占用1 个字节,因此,ASCII 编码可以表示的最大字符数是255(00H—FFH).这对于英文而言,是没有问题的 ...
- Centos7 利用crontab定时执行任务及配置方法
crond是什么? crond 和crontab是不可分割的.crontab是一个命令,常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令.该命令从标准输入设备读取指令,并将其存放于 ...
- Eclipse常用设置及快捷键
1. Eclipse常用设置 1.1 代码自动提示 选择菜单:Window -> Preferences -> Java -> Editor -> Content Assist ...
- Python——OS模块
OS模块 OS模块 #os模块就是对操作系统进行操作,使用该模块必须先导入模块: import os #getcwd() 获取当前工作目录(当前工作目录默认都是当前文件所在的文件夹) result = ...
- Tensorflow--矩阵切片与连接
博客转载自:https://blog.csdn.net/davincil/article/details/77893185 函数原型:slice(input_, begin, size, name=N ...
- CF95C Volleyball
题意翻译 给出一个图,双向边,边上有权值代表路的距离,然后每个点上有两个值,t,c,t代表能从这个点最远沿边走t,且不能在半路下来,花费是c 现在告诉你起点终点,问最少的花费 点个数1000,边个数1 ...
- cdq分治(hdu 5618 Jam's problem again[陌上花开]、CQOI 2011 动态逆序对、hdu 4742 Pinball Game、hdu 4456 Crowd、[HEOI2016/TJOI2016]序列、[NOI2007]货币兑换 )
hdu 5618 Jam's problem again #include <bits/stdc++.h> #define MAXN 100010 using namespace std; ...