1,环境与profile
在开发软件的时候,有一个很大的挑战就是将应用程序从一个环境迁移到另一个环境。开发阶段中,某些环境相关的做法可能并不适合迁移到生产环境中,甚至即便迁移过去也无法正常工作。数据库配置,加密算法以及与外部系统的集成是跨环境部署时会发生变化的几个典型例子。
比如,考虑一下数据库配置:
在开发测试环境中,我们希望使用JNDI从容器中获取一个DataSource。在生产环境中,我们希望使用mysql数据库连接。
package c3_higherwire;
import javax.sql.DataSource;
import org.apache.tomcat.dbcp.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jndi.JndiObjectFactoryBean;
// @Profile 注解基于激活的 profile 实现bean的装配
@Configuration
public class DataSourceConfig {
@Bean(destroyMethod="close")
@Profile("dev") // 为dev profile装配的bean
public DataSource jndiDataSource() {
JndiObjectFactoryBean factoryBean = new JndiObjectFactoryBean();
factoryBean.setJndiName("jdbc/jndiDataSource");
factoryBean.setResourceRef(true);
factoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource)factoryBean.getObject();
}
@Bean(destroyMethod="close")
@Profile("prod") // 为prod profile装配的bean
public DataSource mysqlDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://com.mysql.yihaoyaocheng");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setInitialSize(20);
dataSource.setMaxActive(30);
return dataSource;
}
/**
* 注意:这里尽管每个 DataSource bean都被声明在一个profile中,但是只有当规定的profile
* 被激活时,相应的bean才会被创建。但是很有可能会有其他的bean并没有声明在一个给定的profile
* 范围内,没有指定的profile的bean始终都会被创建,与激活哪个profile没有关系
*/
}
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:aop="http://www.springframework.org/schema/jdbc"
xmlns:aop="http://www.springframework.org/schema/jee"
xmlns:aop="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/aop/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/aop/spring-jee-3.2.xsd">
<beans profile="dev">
<jee:jndi-lookup id="dataSource"
jndi-name="jdbc/jndiDataSource"
resource-ref="true"
proxy-interface="javax.sql.DataSource"/>
</beans>
<beans profile="prod">
<bean id="dataSource"
class="org.apache.tomcat.dbcp.dbcp.BasicDataSource"
destroy-method="close"
p:url="jdbc:mysql://com.mysql.yihaoyaocheng"
p:driverClassName="com.mysql.jdbc.Driver"
p:username="root"
p:password="root"
p:initialSize="20"
p:maxActive="30"/>
</beans>
<!-- 这里有两个bean,类型都是javax.sql.DataSource,并且ID都是dataSource,
但是在运行时,只会创建一个bean,这取决于处于激活状态的是哪一个bean -->
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/config/spring-*.xml
</param-value>
</context-param>
<!-- 为上下文设置默认的 profile-->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 为Servle设置默认的 profile-->
<init-param>
<param-name>spring.profiles.default</param-name>
<!-- 值可设置多个,以,隔开 -->
<param-value>dev</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--
Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。
如果设置了spring.profiles.active属性,那么他的值会用来确定哪个profile是激活的。如果没有设置spring.profiles.active,
则会查找spring.profiles.default的值,如果两者均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中
的bean。有多种方式来设置这两个属性:
作为DispatcherServlet的初始参数
作为Web应用的上下文参数
作为JNDI条目
作为环境变量
作为jvm系统属性
在集成测试类上,使用@ActiveProfiles注解设置
-->
</web-app>
测试:
package c3_higherwire;
import javax.sql.DataSource;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {DataSourceConfig.class})
@ActiveProfiles("prod")
public class DataSourceTest {
@Autowired
private DataSource dataSource;
@Test
public void test1() {
assertNotNull(dataSource);
System.out.println(dataSource);
}
}
2,条件化bean
假设你希望一个或多个bean只有在应用的类路径下包含特定的库时才创建。或者我们希望某个bean只有当另外某个特定的bean也声明了之后才创建。我们还可能要求只有某个特定的环境变量设置之后才会创建某个bean。在spring4引入了一个新的注解@Conditional可以实现。
如只有存在magic属性时,才创建MagicBean:
package c3_higherwire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
//条件化的创建bean,MagicExistsCondion需要实现org.springframework.context.annotation.Condition接口
@Conditional(MagicExistsCondion.class)
@ComponentScan(basePackages = {"c3_higherwire"})
public class BeanConfig {
@Bean("magicBean")
public MagicBean magicBean() {
return new MagicBean();
}
}
package c3_higherwire;
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 MagicExistsCondion implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断是否包含magic属性
Environment environment = context.getEnvironment();
return environment.containsProperty("magic");
}
/**
* ConditionContext 接口:
* BeanDefinitionRegistry getRegistry():返回BeanDefinitionRegistry检查bean的定义
* ConfigurableListableBeanFactory getBeanFactory():返回ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
* ClassLoader getClassLoader():返回ClassLoader加载并检查类是否存在
* Environment getEnvironment():返回Environment检查环境变量是否存在及其值为多少
* ResourceLoader getResourceLoader():返回ResourceLoader所加载的资源
*
* AnnotatedTypeMetadata 接口:
* boolean isAnnotated(String annotationType):判断带有@Bean注解的方法是不是还有其他特定的注解
* Map<String, Object> getAnnotationAttributes(String annotationType)
* Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString)
* MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType)
* MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString)
*/
}
3,处理自动装配的歧义性
使用@Primary标识首选bean
使用@Qualifier注解限定符,他可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入的是哪一个bean。@Qualifier也可以与@Bean结合使用,标识bean的限定符
4,bean的作用域
package c3_higherwire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
//条件化的创建bean,MagicExistsCondion需要实现org.springframework.context.annotation.Condition接口
@Conditional(MagicExistsCondion.class)
@ComponentScan(basePackages = {"c3_higherwire"})
public class BeanConfig {
@Bean("magicBean")
@Scope(value="prototype")
public MagicBean magicBean() {
return new MagicBean();
}
/**
* 在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,
* 不管给定的一个bean被注入到其他bean多少次,每次注入的都是同一个实例
* Spring定义了多种作用域,可以基于这些作用域创建bean,包括
* 单例(Singleton):在整个应用中,只会创建一个bean的实例
* 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例
* 会话(Session):在Web应用中,为每个会话创建一个bean实例
* 请求(Request):在Web应用中,为每个请求创建一个bean实例
*/
}
package c3_higherwire;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {BeanConfig.class})
public class ConditionBeanTest {
@Autowired
private MagicBean magicBean;
@Autowired
private MagicBean magicBean2;
@Test
public void testMagicBeanScope() {
System.out.println("magicBean : " + magicBean);
System.out.println("magicBean2 : " + magicBean2);
System.out.println(" magicBean == magicBean2 : " + (magicBean == magicBean2));
}
/*@Test
public void testMagicBean() {
assertNotNull(magicBean);
}*/
}