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 编写复杂的表达式, 但过分复杂的表达式不适合理解和阅读, 同时也会增大测试的难度. 因此建议尽量编写简洁的表达式.
来源: https://www.cnblogs.com/walkinhalo/p/9613360.html