Spring装配Bean的一些高级技巧
一、使用@Profile注解来实现在不同环境下创建不同的Bean
- 实现方式:将不同的Bean定义整理到对应环境的Profile中,当应用部署到不同的环境时(开发环境或者是QA环境或者是生产环境),激活对应的Profile,则相应环境的Bean就会在运行时被创建,非当前环境的Profile不会被创建,没有指定@Profile注解的Bean始终会被创建。
- @Profile注解可以用在类级别上或者方法级别上。
举例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource; import javax.sql.DataSource; @Configuration
public class DataSourceProfiles {
@Bean
@Profile("development")
public DataSource deveDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://11.11.11.11:3306/demodb");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
} @Bean
@Profile("qa")
public DataSource qaDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://11.11.11.11:3306/demodb");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
} @Bean
@Profile("product")
public DataSource productDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://11.11.11.11:3306/demodb");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
} @Bean
public SomeOtherBean getBean(){
return new SomeOtherBean();
}
}
上面的例子中,“development”这个Profile激活时,会创建development环境的Bean和SomeOtherBean,其他两个Bean不会被创建。
- 激活某个Profile的方式
Spring有两个独立的属性来确定哪个Profile被激活:spring.profile.active和spring.profile.default,如果设置了spring.profile.active属性,则它的值用来确定激活的Profile,如果没有设置spring.profile.active,但是设置了spring.profile.default,则spring.profile.default的值用来确定激活的Profile,如果spring.profile.active和spring.profile.default均没有设置,则没有激活的Profile,此时只会创建哪些没有定义在Profile中的Bean。有多种方式来定义这两个属性:
- 作为DispatcherServlet的初始化参数;
- 作为Web应用上下文的参数;
- 作为环境变量;
- 作为JNDI条目;
- 作为JVM的系统属性;
- 在集成测试时,使用@ActiveProfile注解设置;
二、条件化的装配Bean
可以设置不同的条件来控制Bean的创建:
- 只有在某个特定环境变量设置之后,才创建某个Bean;
- 应用的类路径下包含特定的库才创建某个Bean(这也是SpringBoot的自动化装配的实现方式,当引入了某个特定依赖时,相应的Bean就会被自动创建);
- 只有某个Bean被创建后,才会创建另一个Bean;
举例:当环境变量中设置了magc属性时,才创建MagicExistCondition这个Bean,进一步的,只有MagicExistCondition创建后,才创建MagicBean这个Bean,具体实现方式如下:
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; public class MagicExistCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}
}
上述代码中MagicExistCondition实现了Condition接口,只有实现了这个接口的类,才可以作为条件类,用在@Conditional注解中。Condition这个接口很简单,如上面的例子,只需要实现matches方法即可。
接下来,判断MagicExistCondition被创建后,才创建MagicBean这个Bean:
@Bean
@Conditional(MagicExistCondition.class)
public MagicBean magicBean(){
reutrn new MagicBean();
}
再次看看Condition接口中的matche方法,这个方法有两个参数:ConditionContext和AnnotatedTypeMetadata。
ConditionContext是一个接口:
package org.springframework.context.annotation; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader; public interface ConditionContext {
BeanDefinitionRegistry getRegistry(); ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader(); ClassLoader getClassLoader();
}
getRegistry()方法返回的BeanDefinitionRegistry可以检查Bean的定义
getBeanFactory()方法返回的ConfigurableListableBeanFactory可以检查Bean是否存在,甚至进一步探查Bean的属性
getEnvironment()方法返回的Environment检查环境变量是否存在以及它的值是什么
getResourceLoader()方法返回的ResourceLoader用于探查所加载的资源
getClassLoader()方法返回的ClassLoader用于加载并检查类是否存在
AnnotatedTypeMetadata也是接口:
import java.util.Map;
import org.springframework.util.MultiValueMap; public interface AnnotatedTypeMetadata {
boolean isAnnotated(String var1); Map<String, Object> getAnnotationAttributes(String var1); Map<String, Object> getAnnotationAttributes(String var1, boolean var2); MultiValueMap<String, Object> getAllAnnotationAttributes(String var1); MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
}
该接口主要用来探查带有@bean注解的类上面是否还有其他的注解,并且检查那些注解的属性值。举个例子,回到@Profile这个注解,这个注解用于控制当前@bean注解的类在特定Profile激活时才被创建,那么,@Profile注解是如何实现这个功能的呢,这里就需要借助AnnotatedTypeMetadata这个接口了,从Spring 4开始,@Profile注解基于@Conditional和Condition来实现:
首先看看@Condtion注解:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
String[] value();
}
其中的ProfileCondition类实现了Condition接口的matches方法:
package org.springframework.context.annotation; import java.util.Iterator;
import java.util.List;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap; class ProfileCondition implements Condition {
ProfileCondition() {
} public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
Iterator var4 = ((List)attrs.get("value")).iterator(); Object value;
do {
if (!var4.hasNext()) {
return false;
} value = var4.next();
} while(!context.getEnvironment().acceptsProfiles((String[])((String[])value))); return true;
}
} return true;
}
}
可以看到:matches方法中,首先给metadata.getAllAnnotationAttributes()方法传递Profile.class.getName()这个Profile Bean的名字,得到所有注解的属性,并逐个遍历判断,借助acceptsProfiles方法来见擦好该Profile是否被激活!!!
三、处理自动装配的歧义性
当一个接口有多个实现类时,如果某个类需要注入接口类,此时会抛出NoUniqueBeanDefinitionException异常,解决方法是:
- 使用@Primary标注首选的Bean
- 使用@Qualifier注解@Qualifier(“specialBean”)限定符
- 创建自定义的限定符注解
Spring装配Bean的一些高级技巧的更多相关文章
- Spring 装配Bean
Spring 装配Bean 装配解释: 创建应用对象之间协作关系的的行为通常称为装配(wiring),这也是依赖注入的本质 依赖注入是Spring的基础要素 一 : 使用spring装配Bean基础介 ...
- Spring装配bean
Spring配置的可选方案 Spring提供了如下三种装配机制: (1)在XML中显式配置 (2)在Java中显式配置 (3)隐式的bean发现机制和自动装配 Spring有多种方式可以装配bean, ...
- Spring装配Bean之XML装配bean
在Spring刚出现的时候,XML是描述配置的主要方式,在Spring的名义下,我们创建了无数行XML代码.在一定程度上,Spring成为了XML的同义词. 现在随着强大的自动化配置和Java代码的配 ...
- Spring装配Bean的过程补充
对上一篇的<Spring装配Bean的过程>的过程说一下,不然真产生了误区. 误区在哪里呢?那就是spring bean的作用域问题. 说哈常用的两种作用域:默认是scope = sing ...
- Spring装配Bean的过程
首先说一个概念:“懒加载” 懒加载:就是我们在spring容器启动的是先不把所有的bean都加载到spring的容器中去,而是在当需要用的时候,才把这个对象实例化到容器中. spring配置文件中be ...
- Spring 装配Bean入门级
装配解释: 创建应用对象之间协作关系的的行为通常称为装配(wiring),这也是依赖注入的本质 依赖注入是Spring的基础要素 一 : 使用spring装配Bean基础介绍 1 :声明Bean B ...
- 【转】spring 装配Bean中构造参数的注入
转载自:http://www.bianceng.cn/Programming/Java/201307/37027.htm spring 装配Bean中构造参数的注入 spring装配bean中还有一种 ...
- Spring装配Bean之组件扫描和自动装配
Spring从两个角度来实现自动化装配: 组件扫描:Spring会自动发现应用上下文中所创建的bean. 自动装配:Spring自动满足bean之间的依赖. 案例:音响系统的组件.首先为CD创建Com ...
- Spring装配Bean之Java代码装配bean
尽管通过组件扫描和自动装配实现Spring的自动化配置很方便也推荐,但是有时候自动配置的方式实现不了,就需要明确显示的配置Spring.比如说,想要将第三方库中的组件装配到自己的应用中,这样的情况下, ...
随机推荐
- metasploit扫描
实验目的: 一. 基于TCP协议收集主机信息 二. 基于SNMP协议收集主机信息 三. 基于SMB协议收集信息 四. 基于SSH协议收集信息 五. 基于FTP协议收集信息 实验环境: ...
- Readiness 探测【转】
除了 Liveness 探测,Kubernetes Health Check 机制还包括 Readiness 探测. 用户通过 Liveness 探测可以告诉 Kubernetes 什么时候通过重启容 ...
- NCSC敦促开发者淘汰Python 2
导读 Python 2.x即将结束生命,英国国家网络安全中心(NCSC)敦促开发人员尽快从Python 2.x迁移到Python 3.x.越快越好.Python 2.x将于2020年1月1日停止使用, ...
- 013.Oracle数据库,SUBSTR取子字符串
/*Oracle数据库查询日期在两者之间*/ SELECT EXPIRE_DATE , SUBSTR(EXPIRE_DATE , , ) FROM ME_EO WHERE ( ISSUE_DATE B ...
- 在excel表格里,为所有数字添上绿色小三角
在excel表格里,为所有数字添上绿色小三角的方法有4种: 1. 为一个单元格添加:直接在单元格里添加一个英文的逗号 2. 为一列数据添加:选中要添加绿色小三角的列,选择 数据-->分列--&g ...
- js数据类型 判断
1. js数据类型(两种数据类型) 基本数据类型:null undefined number boolean symbol string 引用数据类型: array object null: 空对象 ...
- UVA - 225 Golygons (黄金图形)(回溯)
题意:平面有k个障碍点.从(0,0)出发,第一次走1个单位,……,第n次走n个单位,恰好回到(0,0),每次必须转弯90°,图形可以自交,但不能经过障碍点.按字典序输出所有移动序列,并输出序列总数. ...
- UVA - 679 Dropping Balls(二叉树的编号)
题意:二叉树按层次遍历从1开始标号,所有叶子结点深度相同,每个结点开关初始状态皆为关闭,小球从根结点开始下落(小球落在结点开关上会使结点开关状态改变),若结点开关关闭,则小球往左走,否则往右走,给定二 ...
- POJ1611 && POJ2524 并查集入门
The Suspects Time Limit: 1000MS Memory Limit: 20000K Total Submissions: 28293 Accepted: 13787 De ...
- 剑指offer - 栈的压入弹出序列 - JavaScript
题目描述 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 ...