一环境与profile
1,背景
软件开发的时候,Application从一个环境到另一个环境,某些环境的做法可能不适合迁移到生产环境,比如数据库配置,加密算法,及外部系统的继承是跨环境部署时发生变化的例子
举例:
数据库DataSource获取
1)EmbeddedDataBaseBuilder会搭建一个嵌入式Hypersonic数据库,模式定义在schema.sql,测试数据在testdata.sql,每次启动的时候都能让数据库处于一个给定的状态
@Bean(destroyMethod = "shutdown") public DataSource getDataSorce() { return new EmbeddedDataBaseBuilder() .addScript("classpath:schema.sql") .addScript("classpath:testdata.sql"") .build(); }
2)在生产环境中可能需要使用JNDI从容器中获取一个DataSource,这种方式能够让容器决定如何创建这个DataSource,甚至包括切换为容器管理的连接池(JNDI简单地理解为它是一种将对象和名字绑定的技术,对象工厂负责生产出对象,每个对象都和它的唯一的名字绑定,外部程序可以通过名字来获得这些对象的引用。)
@Bean
public DataSource getDataSorce() {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("jdbc/myDs");
bean.setResourceRef(true);
bean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource)bean.getObject();
}
3)在QA环境中可以选择DBCP连接池,具体代码网上都有
注意:这里讲的是每种环境下都会选择合适的配置,并不是数据源的解释,配置的效果就是能够适用所有的环境,不用重新构建,环境配置的方式有单独的配置类,或者XML配置
2,配置profile bean
1)注解
@Profile 注解指定某一个bean属于哪一个profile,比如
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {}
他会告诉Spring这个配置类中bean只有在dev profile激活的时候才会创建
@Configuration
@Profile("prod")
public class DevelopmentProfileConfig {}
他会告诉Spring这个配置类中bean只有在prod profile激活的时候才会创建
?:在Spring3.1只能类级别使用@Profile,在此以后,可以和@Bean在方法级别使用,这样就能将两个Bean的声明放在同一个配置类中,另外没有指定profile的bean始终都会创建,与激活那个profile没有关系
2)Xml配置profile
<beans profile="dev">
...配置
</beans>
<beans profile="prod">
...配置
</beans>
3)激活profile
首先:Spring在确定哪个profile处于激活状态的时候,需要依赖两个独立的属性,spring.profiles.actice和spring.profiles.default
如果设置了spring.profiles.actice属性(系统优先使用),它的值就决定那个profile是激活的
如果没有设置spring.profiles.actice,那么看spring.profiles.default,如果都没有,那么Spring指挥创建没有定义profile的 bean
其次:怎么设置这两个属性
a) 作为dispatcherServlet的初始化参数
b),作为web应用的上下文参数
c),作为jndi的条目
d),作为环境变量
e),作为JVM的系统属性
f),在集成测试类上,使用@ActiveProfiles注解配置
二 条件化bean
1,加入你希望一个或者多个bean只有在应用的类路径包含特定的库时才创建,或者我们希望某个bean只有在某个特定的bean也声明之后才会创建,或者只有某个特定的环境变量设置之后才会创建某个bean
Spring4引入一个新的注解@Conditional,它可以用到带有@Bean注解的方法上,如果给定的条件计算结果为true的时候才会创建这个bean
否则会忽略
举例,MagicBean ,只有设置了magic的环境属性的时候,spring才会创建这个bean
@Bean
@Conditional(MagicExitsCondition.class)
public MagicBean magicBean(){
return new MagicBean();
}
@Conditional指明了条件MagicExitsCondition,@Conditional会通过Condition接口进行条件对比
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
设置@Conditional注解的类可以是任意实现Condition接口的类型,只需要实现matches()方法即可,
public class MagicExitsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
return environment.containsProperty("magic");
}
}
2,ConditionContext是一个接口,通过这个接口我们可以做到如下几点
1)借助getRegistry()返回的BeanDefinitionRegistry检查bean的定义
2)借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
3)借助getEnvironment()返回的Environment,检查环境变量是否存在,以及他的值是什么
4) 读取并探查getResourceLoader()返回的ResourceLoaders所加载的资源
5)借助getClassLoader返回的ClassLoader加载并检查类是否存在
3,AnnotatedTypeMetadata也是一个接口,则能让我们能够检查所有带@Bean注解的方法上还有什么其他的注解,
1)借助isAnnotated()我们能够判断带有@bean的注解方法是不是还有其他的特定的注解,
借助其他的那些方法,我们能够检查@Bean注解的方法上其他注解的属性
4,Spring4之后,@Profile注解进行了重构,使其基于@Conditional和Condition的实现
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
注意:@Profile也使用了@Conditional注解,并且引用ProfileCondition作为Condition的实现
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
ProfileCondition 通过AnnotatedTypeMetadata得到了用于@Profile的所有属性。,借助该信息,会明确的检查value属性,该属性包含了bean的profile名称,
三自动装配的歧义性
1,背景
@Bean
public void setDessert(Dessert dessert){
return this.dessert=dessert;
}
Dessert有多个类实现,并通过@Compnent注解标识
会抛出NoUniqueBeanDefinitionException异常
2,解决
1)标示首选的bean,使用@Primary注解,@Primary能够和@Component注解组合使用在组件扫描的bean上,也可以和@Bean组合用在java配置的声明上,使用xml也可以表示只需要在bean标签中加上profile=“true”。
注意:标示了两个或者多个首选的bean,那么也就无法正常工作了
2)限定自动装配的bean,@Qualifier(“iceCream”)注解是使用限定符的主要方式,他可以和@Autowired,@Inject协同使用,注解设置的参数就是想要注入的beanID,
注意:如果重构了IceCream类,将其命名为BigCream,那么自动装配就会失败,对类名称的任意改动都会导致自动装配的失败
3)创建自定义限定符,就是在bean的声明上加上@Qualifie(“cold”)注解,最佳实践就是bean选择特征性或者描述性的术语,而不是使用随意的名字
注意:如果多个bean都具有相同的特性的话,也会出现问题,比如两个带@Qualifie(“cold”)的bean
4)使用自定义限定符注解
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold{
}
四,Bean的作用域
1,作用域类型
- singleton(单例) :在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
- prototype(原型): 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
- request(请求): 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring
WebApplicationContext环境。 session: 同一个HTTP - Session(会话):共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的 SpringWebApplicationContext环境。
- application: 限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。
五运行时值注入
1,运行时求值两种方式
1)属性占位符(property placeholder)
2)Spring表达式语言(Spel)
2,注入外部的值
1)声明属性源 @PropertySource(“classpath:/com/it/xxx.properties”)
2)检索属性值 env.getProperty(key)(自动注入Environment env)
3,解析属性占位符
1)占位符的形式为使用 “${…}”包装的属性名称
注解:
public BlankDisc(@Value("${disc.title}" String title ,@Value("${disc.artist}" String artist )){
this.title=title;
this.artist=artist;
}
为了能够使用占位符,我们必须先声明一个占位符的启用,用一个java配置或者xml
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
或
<context:property-placeholder>
4,Spel表达式进行装配
1)spel表达式yao放到“#{…}”之中,从系统属性中获取
public BlankDisc("#{systemProperties['disc.title']}" String title ,"#{systemProperties['disc.artist ']}" String artist )){
this.title=title;
this.artist=artist;
}