大概看了一下第三章的内容,我从项目中仔细寻找,始终没有发现哪里有这种配置,但是看完觉得spring还有这么牛B的功能啊,spring的厉害之处,这种设计程序的思想,很让我感慨。。。
一、环境与profile
(1)配置profile bean
面对这样的需求:想出一种方法来配置DataSource,使其在每种环境下都会选择最为合适的配置,你会如何做呢?看看spring所提供的解决方案。spring中引入了bean profile的功能。在Java配置中,可以使用@Profile 注解指定某个bean属于哪一个profile。
1)在Java类中配置profile bean
package com.myapp; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; //@Profile注解应用在累计别上。它会告诉spring这个配置类中的bean只有在dev profile 激活时才会创建,没有激活,则不会创建 @Configuration @Profile("dev") public class DevelopmentProfileConfig{ return new EmbeddDatabaseBuilder().setType(EmbeddedDatebaseType.H2). addScript("classpath:schema.sql").addScript("classpath:test- data.sql").build(); } package com.myapp; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.jndi.JndiObjectFactoryBean; //@Profile注解应用在累计别上。它会告诉spring这个配置类中的bean只有在prod profile 激活时才会创建,没有激活,则不会创建 @Configuration @Profile("prod") public class ProductiontProfileConfig{ JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean(); jndiObjectFactoryBean.setJndiName("jdbc/myDS"); jndiObjectFactoryBean.setResourceRef(true); jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); return (DataSource) jndiObjectFactoryBean.getObject(); } //可以将这两个bean的声明放置在同一个配置类中 package com.myapp; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.jndi.JndiObjectFactoryBean; @Configuration public class DateSourceConfig{ //为dev profile装配bean public DataSource embeddedDataSource(){ return new EmbeddDatabaseBuilder().setType(EmbeddedDatebaseType.H2). addScript("classpath:schema.sql").addScript("classpath:test- data.sql").build(); } //为prod profile 装配bean public DataSource jbdiDataSource(){ JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean(); jndiObjectFactoryBean.setJndiName("jdbc/myDS"); jndiObjectFactoryBean.setResourceRef(true); jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class); return (DataSource) jndiObjectFactoryBean.getObject(); } }
2)在XML中配置profile bean
//xml中配置的实际效果和在Java类中配置是一样的 //dev profile 的bean <beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:schema.sql" /> <jdbc:script location="classpath:test-data.sql" /> </jdbc:embedded-database> </beans> //qa profile 的bean <beans profile="qa"> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destory-method="close" p:url="jdbc:h2:tcp://deserver/~/test" p:driverClassName="org.h2.Driver" p:username="sa" p:password="password" p:initialSize="20" p:maxActive="30"> </bean> </beans> //prod profile 的bean <beans> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDataSource" resource-ref="true" proxy-interfase="javax.sql.DataSource" /> </beans>
2)激活 profile
profile bean 配置OK了,如何来激活配置的bean呢?依赖两个独立的属性:spring.profiles.active 和 spring.profiles.default。设置了spring.profiles.active属性的话,那么它的值就会来确定哪个profile是激活的,如果设置了spring.profiles.default属性,那么spring 会查找spring.profiles.default的值,如果两个属性都设置的话,肯定会优先spring.profiles.active属性对应的profile bean 装配
多种方式设置这两个属性:
1))作为DispatcherServlet的初始化参数
2))作为web应用的上下文参数
3))作为JNDI条目
4))作为环境变量
5))作为JVM的系统属性
6))在集成测试上,使用@ActiveProfiles注解设置
web.xml 中设置默认的profile
//为上下文设置默认的profile <context-param> <param-name>spring.profile.default</param-name> <param-value>dev</param-value> </context-param> //为Servlet设置默认的profile 可以列出多个profile名称,并以逗号分隔来实现 <servlet> <servlet-name>app-servlet</servlet-name> <servlet-class> org.springframework.web.servlet.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </init-param> </servlet>
二、条件化的bean
spring4引入了一个新的@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,则会创建这个bean,否则的话,这个bean会被忽略。
//条件化的创建bean @Bean @Conditional(MagicExistsCondition.class) public MagicBean magicBean(){ return new MagicBean(); } //@Conditional 将会通过Condition 接口进行条件对比 public interface Condition{ public boolean matches(ConditionContext ctcx,AnnotatedTypeMetadata metadata); } //在Contidion 中检查是否存在magic属性,满足这个条件,matches()方法返回true,则所有的@Conditional 注解上引用MagicExistsCondition的bean都会被创建 public class MagicExistsCondition implements Condition{ public boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata){ Environment env = context.getEnvironment(); return env.containsProperty("magic"); } }
注:这里讲解了两个比较特殊的接口,一个是ConditionContext,一个是AnnotatedTypeMetadata,做一个说明:
public interface ConditionContext{ //检查bean的定义 BeanDefinitonRegistry getRegistry(); //检查bean是否存在,甚至探查bean的属性 ConfigurableListableBeanFactory getBeanFactory(); //检查环境变量是否存在以及它的值是什么 Environment getEnvironment(); //返回ResourceLoader所加载的资源 ResourceLoader getResourceLoader(); //返回ClassLoader加载并检查类是否存在 ClassLoader getClassLoader(); } //检查带有@Bean注解的方法上还有什么其他的注解 public interface AnnotatedTypeMetadata{ boolean isAnnotated(String annotationType); Map<String,Object> getAnnotationAttributes(String annotationType); Map<String,Object> getAnnotationAttributes(String annotationType,boolean classValueAsString); MultiValueMap<String,Object> getAnnotationAttributes(String annotationType); MultiValueMap<String,Object> getAnnotationAttributes(String annotationType,boolean classValueAsString); }
注:spring4 中对@Profile注解进行了重构,使其基于@Conditional 和 Condition实现,@Profile在spring4 中的实现:
/* @Profile本身也使用了@Conditional注解,并且引用了ProfileCondition作为Condition实现,ProfileCondition在做出决策的过程中,考虑到了ConditionContext和AnnotatedTypeMetadata中多个因素 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) @Document @Conditional(ProfileCondition.class) public @interface Profile{ String[] value(); }