创建应用对象之间协作关系的行为,通常称为装配,这也就是依赖注入的本质。
配置spring容器有三种方法
-
在xml中进行显示配置
-
在Java中进行显示配置
-
隐式的bean发现机制和自动装配机制
一般而言,spring配置的风格有多种:可以相互搭配。
- 但是一般而言建议是使用自动装配机制,显示配置越少越好
- 如果有些源代码不是由你来维护,但是你要为其装配,这里只能使用显示配置,一般建议使用基于Java的显示配置
- 只有当你想使用便利的xml命名空间,并且Javaconfig中没有相对应的功能。
1.自动装配
- 组件扫描:spring会自动发现应用上下文中创建的bean
- 自动装配:spring自动满足bean之间的依赖
使用主要注解是:@component @configuration @conmponentScan @Contextconfiguration
@Autowrite
使用@configuration注解配置的类是属于配置类
使用component表示该类会作为组建类,并告诉spring为这个类创建bean。bean默认名字就是类名 可以使用@component(“xxx”)
使用componentScan开启扫描模式
- 默认扫描配置类所在的基础包
- 如果配置类单独在一个包之中,就要另外指定包的路径 basepackages
- 根据某一个类来查找基础包信息。
自动装配:
- 在满足依赖注入的过程中,会在spring应用上下文中寻找匹配某个bean需求的其他bean。使用的是@Autowired注解,表示当spring创建bean时候(根据构造器注入),会通过这个构造器来进行实例化并且会传入一个bean对象。
- @Autowired可以用required修饰 当在应用上下文中找不到时候机会设置为空值(否则会报错,所以要检查是否为空值),自动装配中发生歧义也会自动抛出。
- 可以使用@Inject代替@Autowired
- 有field注入 构造器注入 setter注入 普通方法注入
2.显示装配之---基于Java装配
Q:为什么要使用显示配置
A:当你想调用第三方库中的组件装配到你的应用时候,你无法在源代码处加@component以及@Autowired,所以只有显示装配了,选择基于Java的显示装配原因是 易于重构 类型安全。
自动化装配的确有很大的便利性,但是却并不能适用在所有的应用场景,比如需要装配的组件类不是由自己的应用程序维护,而是引用了第三方的类库,这个时候自动装配便无法实现,Spring对此也提供了相应的解决方案,那就是通过显示的装配机制——Java配置和XML配置的方式来实现bean的装配。
1 Java配置类装配bean
我们还是借助上篇博文中的老司机开车的示例来讲解。Car接口中有开车的drive方法,该接口有两个实现——QQCar和BenzCar
package spring.impl;
import spring.facade.Car;
public class QQCar implements Car {
@Override
public void drive() {
System.out.println("开QQ车");
}
}
既然是通过Java代码来装配bean,那就是不是我们上一篇讲的通过组件扫描的方式来发现应用程序中的bean的自动装配机制了,而是需要我们自己通过配置类来声明我们的bean。我们先通过@Configuration注解来创建一个Spring的配置类,该类中包含了bean的创建细节——
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.QQCar;
/**
* @Configuration 表明该类是Spring的一个配置类,该类中会包含应用上下文创建bean的具体细节
* @Bean 告诉Spring该方法会返回一个要注册成为应用上下文中的bean的对象
*/
@Configuration
public class CarConfig {
@Bean
public Car laoSiJi() {
return new QQCar();
}
}
以上类中创建的bean实例默认情况下和方法名是一样的,我们也可以通过@Bean注解的name属性自定义ID,例如 @Bean(name = "chenbenbuyi") ,那么在获取bean的时候根据你自己定义的ID获取即可。接着我们测试——
package spring.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import spring.config.CarConfig;
import spring.facade.Car;
public class CarTest {
@Test
public void carTest() {
ApplicationContext context = new AnnotationConfigApplicationContext(CarConfig.class);
//根据ID从容器容获取bean
Car car = (Car) context.getBean("chenbenbuyi");
car.drive();
}
}
以上测试能够成功输出,这就表明我们能够获取到QQCar的实例对象的,而这也是最简单的基于Java配置类来装配bean的示例了。但是你可能会说,明明是我们自己创建的Car的实例,怎么就成了Spring为我们创建的呢?好吧,我们把@Bean注解拿开,测试当然是无法通过,会抛NoSuchBeanDefinitionException异常。这里,你可能需要好好理解控制反转的思想了:因为现在对于bean创建的控制权我们是交给了Spring容器的,如果没有@Bean注解,方法就只是一个普通方法,方法体返回的实例对象就不会注册到应用上下文(容器)中,也就说,Spring不会为我们管理该方法返回的实例对象,当我们在测试类中向容器伸手要对象的时候,自然就找不到。
上述示例过于简单,现在,我们要更进一步,给简单的对象添加依赖,来完成稍微复杂一点的业务逻辑。车是需要老司机来开的,于是我们同上篇一样定义一个Man类,Man的工作就是开车——
package spring.impl;
import spring.facade.Car;
public class Man {
private Car car;public Man(Car car) {
this.car = car;
}
public void work() {
car.drive();
}
}
Car的对象实例是通过构造器注入,而Car的实例对象在配置类中通过方法laoSiJi()返回,所以我们在配置类中可以直接调用laoSiJi方法获取bean注入到Man的实例对象——
package spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.BenzCar;
import spring.impl.Man;
@Configuration
public class CarConfig {
@Bean
public Car laoSiJi() {
return new BenzCar();
}
@Bean
public Man work() {
return new Man(laoSiJi());
}
}
测试类中通过上下文对象的getBean("work")方法就可以获取到Man的实例对象,从而完成对老司机开车的测试。或许,你会觉得,work方法是通过调用laoSiJi方法才获取的Car的实例的,实际上并非如此。因为有了@Bean注解,Spring会拦截所有对该注解方法的调用,直接返回该方法创建的bean,也即容器中的管理的bean。也就是说,laoSiJi方法返回的bean交给了Spring容器管理后,当其他地方需要实例对象的时候,是直接从容器中获取的第一次调用方法产生的实例对象,而不会重复的调用laoSiJi方法。我们可以如下测试——
package spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.BenzCar;
import spring.impl.Man;
@Configuration
public class CarConfig {
@Bean
public Car laoSiJi() {
System.out.println("方法调用");
return new BenzCar();
}
@Bean
public Man work() {
return new Man(laoSiJi());
}
@Bean
public Man work2() {
return new Man(laoSiJi());
}
}
如上测试你会发现,虽然我定义了两个方法来获取Man实例,但是控制台只输出了一次调用打印,即证明方法只在最初返回bean的时候被调用了一次,而后的实例获取都是直接从容器中获取的。这也就是默认情况下Spring返回的实例都是单例的原因:一旦容器中注册了实例对象,应用程序需要的时候,就直接给予,不用重复创建。当然,很多情况下我们不会如上面的方式去引入依赖的bean,而可能会通过参数注入的方式,这样你就可以很灵活的使用不同的装配机制来满足对象之间的依赖关系,比如下面这种自动装配的方式给Man的实例注入依赖的Car对象——
package spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.Man;
@Configuration
@ComponentScan("spring.impl")
public class CarConfig {
@Bean
public Man work(Car car) {
return new Man(car);
}
}
当然,如果你喜欢去简就繁,也可以通过XML配置文件配置依赖的bean。下面再来看看XML的方式如何装配bean。
2 XML配置文件装配bean
使用XML配置文件的方式装配bean,首要的就是要创建一个基于Spring配置规范的XML文件,该配置文件以<beans>为根元素(相当于Java配置的@Configuration注解),包含一个或多个<bean>元素(相当于配置类中@Bean注解)。针对上文的汽车示例,如果改成XML配置就是这样——
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--通过类的全限定名来声明要创建的bean-->
<bean class="spring.impl.BenzCar"></bean>
</beans>
然后,从基于XML的配置文件中加载上下文定义,我们就能根据ID获取到对应的bean了——
package spring.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import spring.facade.Car;
public class CarTest {
@Test
public void carTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("resource/applicationContext.xml");
//XML的方式如果没有明确给定ID,默认bean的ID会根据类的全限定名来命名,以#加计数序号的方式命名。
Car car = (Car)context.getBean("spring.impl.BenzCar#0");
car.drive();
}
}
当然,示例中使用自动化的命名ID看起来逼格满满,但其实并不实用,如果需要引用bean的实例就有点操蛋了,实际应用中当然还是要借助<bean>的id属性来自定义命名。
2.1 构造器注入
给<bean>元素设置id属性,在构建另外的对象实例的时候,就可以很方便的引用,譬如上面基于Java的配置中的构造器注入,XML中的同样这样实现——
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="car" class="spring.impl.BenzCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--通过Man的构造器注入Car的实例对象-->
<constructor-arg ref="car"></constructor-arg>
</bean>
</beans>
而有时候我们并不一定都是将对象的引用装配到依赖对象中,也可以简单的注入字面值——
package spring.impl;
import spring.facade.Car;
public class Man {
private Car car;
private String str;
public Man(String str ,Car car) {
this.car = car;
this.str = str;
}
public void work() {
System.out.println(str);
car.drive();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="car" class="spring.impl.BenzCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--分别注入字面值和对象的应用-->
<constructor-arg value="陈本布衣"></constructor-arg>
<constructor-arg ref="car"></constructor-arg>
</bean>
</beans>
接着,我们继续对已有代码做些改动,将注入的参数改为Car的List集合——
public Man(List<Car> cars) {
this.cars = cars;
}
那么配置文件就可以这样配置——
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="benzCar" class="spring.impl.BenzCar"></bean>
<bean id="qqCar" class="spring.impl.QQCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--通过<list>子元素实现List集合对象的装配-->
<constructor-arg>
<list>
<ref bean="benzCar"/>
<ref bean="qqCar"/>
</list>
</constructor-arg>
</bean>
</beans>
如果是需要注入集合中的字面值,写法如下——
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="benzCar" class="spring.impl.BenzCar"></bean>
<bean id="qqCar" class="spring.impl.QQCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--通过<list>子元素实现List集合字面值的装配-->
<constructor-arg>
<list>
<value>这里直接填写字面值</value>
<value>陈本布衣</value>
</list>
</constructor-arg>
</bean>
</beans>
我们可以采用同样的方式装配Set集合,只是Set集合会忽略掉重复的值,而且顺序也不保证。此处不做演示。
2.2 属性注入
构造器注入是一种强依赖注入,而很多时候我们并不倾向于写那种依赖性太强的代码,而属性的Setter方法注入作为一种可选性依赖,在实际的开发中是应用得非常多的。上面Man类如果要通过属性注入的方式注入Car的实例,就该是这样子——
package spring.impl;
import spring.facade.Car;
public class Man {
private Car car;
public void setCar(Car car) {
this.car = car;
}
public void work() {
car.drive();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="benzCar" class="spring.impl.BenzCar"></bean>
<bean id="qqCar" class="spring.impl.QQCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--通过属性注入的方式注入Car的实例-->
<property name="car" ref="benzCar"></property>
</bean>
</beans>
以上示例中,XML配置文件中属性注入的属性名必须要和Java类中Setter方法对应的属性名一致。而对于字面量的注入,和上面构造器的方式类似,只不过使用的元素名换成了<property>而已,下面仅做展示——
<bean id="man" class="spring.impl.Man">
<property name="str" value="字面量的注入"></property>
<property name="list">
<list>
<value>集合的字面量注入1</value>
<value>集合的字面量注入2</value>
</list>
</property>
</bean>
<bean id="benzCar" class="spring.impl.BenzCar"></bean>
<bean id="qqCar" class="spring.impl.QQCar"></bean>
<bean id="man" class="spring.impl.Man">
<!--属性注入的方式注入集合-->
<property name="cars">
<list>
<ref bean="qqCar"></ref>
<ref bean="benzCar"></ref>
</list>
</property>
</bean>
3 三种装配方式的混合使用
在同一个应用程序中,Spring常见的这三种装配方式我们可能都会用到,而对于不同的装配方式,他们之间如何实现相互引用从而整合到一起的呢?我们先看看Java配置类的引用问题。试想如果Java配置类中的bean数量过多,我们可能会考虑拆分。在本文的示例中,Man类实例的创建必须通过构造器注入Car的实例,如果把两个实例的产生分成两个配置类,那么在依赖注入的配置类中可以通过@Import注解引入被依赖的配置类——
package spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import spring.facade.Car;
import spring.impl.Man;
@Configuration
@Import(CarConfig.class) //通过@Import注解引入产生Car实例的配置类
public class ManConfig {
@Bean
public Man work(Car car) {
return new Man(car);
}
}
但是如果Car的实例不是通过Java类配置的,而是通过XML方式配置的方式配置,我们只需通过@ImportResource注解将配置bean的XML文件引入即可,只不过这个时候要保证XML中被依赖的bean的id要和Java配置类中的形参保持一致——
package spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import spring.facade.Car;
import spring.impl.Man;
@Configuration
@ImportResource("classpath:resource/applicationContext.xml")
public class ManConfig {
@Bean
public Man work(Car car) {
return new Man(car);
}
}
而如果bean是采用XML进行装配,如果需要装配的bean过多,我们当然还是会根据业务拆分成不同的配置文件,然后使用<improt>元素进行不同XML配置文件之间的引入,形如: <import resource="classpath:xxx.xml" /> ;而如果要在XML中引入Java配置,只需将Java配置类当成普通的bean在XML中进行声明即可,但是在测试的时候要注意开启组件扫描,因为加载XML配置的上下文对象只会加载XML配置文件中的bean定义,无法让基于Java配置类产生bean的装配机制自动生效——
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描,在测试的时候配置类才能向容器中注册类中声明的bean-->
<context:component-scan base-package="spring"/>
<!--XML中引入Java配置类:将配置类声明为bean-->
<bean class="spring.config.CarConfig"></bean>
<bean id="man" class="spring.impl.Man">
<constructor-arg ref="laoSiJi"></constructor-arg>
</bean>
</beans>
最后说一点,不管是Java配置还是XML配置,有个通常的做法就是创建一个比所有配置都更高层次的根配置类/文件,该配置不声明任何的bean,只用来将多个配置组合在一起,从而让配置更易于维护和扩展。好了,以上便是两种bean的装配方式的简单讲解,如有纰漏,欢迎指正,不胜感激。
文章部分转自于:https://www.cnblogs.com/chenbenbuyi/p/8457700.html
下面讲解一下高级装配
环境与profile
企业开发过程中常常会将一些应用从一个服务器迁移到另一个服务器。开发阶段,某些环境相关做法可能并不适合迁移到生产环境中,甚至即便迁移过去也无法正常工作。如:数据库配置、加密算法以及与外部系统的集成是跨环境部署时会发生变化。
比如我们想配置一个数据库,我们可能使用EmbeddedDatabaseBuilder;
@Bean(destroyMethod="shutdown")
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder(){
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.builder();
}
}
使用EmbarrassedDatabasedBuilder会搭建一个嵌入式的Hypersonic数据库,他的模式(schema)定义在schema.sql中,测试数据则是通过test-data.sql加载的。
这样的数据库用于测试还行,但对于生产环境,他就不太适用啦。在生产环境中你可能希望使用JNDI从容器中获取一个DataSource:
@Bean
public DataSource dataSource(){
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource)jndiObjectFactoryBean.getObject();
}
通过JNDI获取DataSource能够让容器决定该如何创建这个DataSource,甚至包含切换为容器管理的连接池。虽然JNDI管理的DataSource更适合生产环境,但对于简单集成和开发测试环境来说,这会带来不必要的复杂性。
同时,在QA环境中,你可以选择完全不同的DataSource配置,你可以配置为Commons DBCP连接池:
@Bean(destroyMethod="close")
public DataSource dataSource(){
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:h2:tcp://dbserver/~/test");
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUsername("sa");
dataSource.setPassword("password");
dataSource.setInitialSize(20);
dataSource.setMaxAction(30);
return dataSource;
}
上面表现了:在不同的环境中某个bean有所不同。必须有一种方法配置该bean,使其在每一种环境下都会选择最为合适的配置。
有一个方法就是在单独配置类中配置每一个bean,然后在构建阶段确定要将哪一个配置编译到可部署应用中。问题就是每一次切换运行环境都要进行一次重构(这是不可取的) 。一下提供解决方案。
-
配置Profile bean
在3.1版本中,Spring引入了bean profile的功能,要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。
(上面意思是所有bean都会加载到profile,只有当遇到合适的profile,bean类才会加载。只有在运行时候才确定环境相关的bean类)
在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile:
package com.cache.profile;
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;
/**
* <dl>
* <dd>Description:功能描述</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年8月16日 下午9:52:32</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@Configuration
@Profile("dev")
public class DevlopmentProfileConfig {
@Bean(destroyMethod="shutdown")
public DataSource dataSource(){
EmbeddedDatabaseBuilder embeddedDatabaseBuilder =new EmbeddedDatabaseBuilder();{
return embeddedDatabaseBuilder.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schem.sql")
.addScript("classpath:test-data.sql")
.build();
}
}
}
这时候@Profile注解应用在了类级别上。他会告诉Spring只有在dev profile激活时才会创建该Bean。如果没有激活dev profile配置,则Spring会忽略掉这个Bean。
其实在生产环境中,下面的配置更合适:
package com.cache.profile;
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;
/**
* <dl>
* <dd>Description:功能描述</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年8月16日 下午10:09:56</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@Configuration
@Profile("prod")
public class ProductionProfileConfig {
@Bean()
public DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
同样,本例中的Bean也是只有在profile文件被激活的时候才会被创建。
在Spring3.1时,只能在类级别上使用@Profile注解,不过从Spring3.2开始,@Profile注解就可以使用在方法级别上了。这样就能将两个bean的声明放到同一个配置类中:
package com.cache.profile;
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;
/**
* <dl>
* <dd>Description:功能描述</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年8月16日 下午10:16:48</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@Configuration
public class DataSourceConfig {
@Bean(destroyMethod="shutdown")
@Profile("dev")
public DataSource embeddedDataSource(){
EmbeddedDatabaseBuilder embeddedDatabaseBuilder =new EmbeddedDatabaseBuilder();{
return embeddedDatabaseBuilder.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schem.sql")
.addScript("classpath:test-data.sql")
.build();
}
}
@Bean()
@Profile("prod")
public DataSource jndiDataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}
上面这些Bean只有当对应的profile被激活才能被加载,但如果bean上没有@Profile注解,这样的bean始终是会被创建的。
XML中配置profile
我们也可以通过<beans>元素的profile属性,在XML中配置profile bean。例如为了在XML中定义使用于开发阶段的嵌入式数据库DataSource bean,我们可以创建如下所示的XML文件:
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd"
profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql"/>
<jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>
</beans>
与之类似,我们也可以将profile设置为prod,创建使用试用于生产环境的JNDI获取的DataSource bean。也可以创建基于连接池定义的DataSource bean,将其放在另一个XML文件中,并标记为qa profile。所有的配置文件都会放到部署单元之中(如WAR文件),但是只有profile属性与当前激活profile配置文件相匹配的才会才会被用到。
我们还可以在根<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个profile XML文件。这能够将所有的profile bean定义放到同一个XML文件:
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd">
<beans profile = "dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script localtion="classpath:schem.sql"/>
<jdbc:script localtion="classpath:test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="qa">
<beans id="dataSource" class="org.apache.dbcp.commons.dbcp.BasicDataSource"
destory-mothos="close"
p:url="jdbc:h2:tcp://dbserver/~/test"
p:driverClassName="org.h2.Driver"
p:username="sa"
p:password="password"
p:initialSize="20"
p:maxActive="30"/>
</beans>
<beans profile="prod"
<jee:jndi-lookup id="dataSource"
jndi-name="jdbc/myDatabase"
resource-ref="true"
proxy-interface="javax.sql.DataSource" />
</beans>
</beans>
除了所有的bean定义到同一XML文件之中,这种配置方式与定义在单独的XML文件中的实际效果是一样的。这里有三个bean,类型都是javax.sql.DataSource
,并且ID都是dataSource。但运行时,只会创建一个bean,这取决于处于激活状态的是哪个profile。
下面我们就介绍一下如何激活某个profile?
激活profile
Spring在确定激活哪个profile时,需要依赖两个独立的属性:Spring.profiles.active和spring.profiles.default。如果设置了spring.profiles.active属性的话,那么Spring就会根据它去选择激活那个profile,如果spring.profiles.active没有设置值,那么Spring会根据spring.profiles.default的值选择激活哪个profille,如果两个值都没有设置,那么Spring只会创建哪些没有没有定义profile中的bean。
设置这两个属性的方式有以下几种:
- 作为DispatcherServlet的初始化参数;
- 作为Web应用的上下文参数;
- 作为JNDI条目;
- 作为环境变量;
- 作为JVM的系统属性;
- 在集成测试类上,使用@ActiveProfiles注解设置。
通常我会通过设置DispatcherServlet的参数的方式来指定spring.profiles.active和spring.profiles.default,下面是在web.xml中指定他们的值:
<?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"
version="2.5">
<display-name>testCache</display-name>
<context-param>
<!-- 为上下文设置默认profile -->
<param-name>spring.profile.default</param-name>
<param-value>dev</param-value>
</context-param>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
<!-- 为Servlet设置默认的profile -->
<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>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
使用profile进行测试
当运行集成测试时,通常希望采用与生产环境相同的配置进行测试。但如果配置中的bean定义了profile中,那么在运行测试时,我们就需要有一种方式来启动合适的profile。
Spring就提供了@ActiveProfiles注解,来指定激活哪个profile:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfigration(classes={PersistenTestConfig})
@ActiveProfiles
public class PersistenTest(){
...
}
在条件化创建bean方面,Spring的profile机制是一种很好的方法,这里的条件要基于那个profile处于激活状态来判断。下节我们会介绍Spring 4.0提供的一种更为通用的机制,来实现条件化的bean定义。
bean作用于有四种:
- 单利:在整个应用中,只创建bean一个实例。
- 原型:每次注入或者是通过spring上下文获取时候,都会创建一个新的bean实例
- 会话:在web应用中,为每一个会话创建一个bean实例
- 请求:在web应用中,为每一次请求都创建一个bean实例对象