Spring 4 学习笔记6:依赖注入(配置)
2016-09-28 07:04
567 查看
《Spring in Action》4th Edition 学习笔记
Spring 注入 bean 可以有如下三种方式:
自动注入
显式注入(java 配置)
显式注入(xml 配置)
Component scanning:Spring 自动发现需要在应用上下文中生成的 bean
Autowiring:自动注入 bean 依赖
配置 Bean 的名称:
Note:当使用
Java:更强大、类型安全、重构友好
XML:更灵活
Note:使用 Java 配置时,应该将 JavaConfig 和业务代码分开,最好使用不同的包管理。因为 JavaConfig 毕竟是配置文件,所以不应该和业务逻辑代码混一块。
使用
默认注册的 Bean 的名称为方法名,也可以通过
如何在 JavaConfig 中声明依赖于其他 Bean 的 Bean?只需要在声明方法的参数中指定需要的依赖类型,Spring 会自动帮你注入。(无论该方法依赖的 Bean 是在 JavaConfig 中声明的,还是 Xml 配置中声明的)
Note:可使用 Spring Tool Suite 自动生成 XML 配置文件
正因为
很多时候,声明一个 bean,还需要初始化很多属性,那么如何使用 Spring 注入这些属性呢?
构造器注入
c-namespace
属性注入
p-namespace
其中
Spring 3.0 中引入的 c-namespace:简洁,不能注入集合
使用 c-namespace。首先声明 c-namespace 的 xml schema,然后注入:
也可以使用参数列表的方式注入:
其中
如果需要注入的集合中的元素为对象,那么可以使用
使用 p-namespace 注入:
使用
假设你需要注入一个
现在有如下三个实现类:
因为三个实现类都继承自
那么有哪些方法能够帮助 Spring 消除这种不明确的情况呢?
设置一个 primary bean
Qualifying autowired beans
现在,如果 Spring 碰到有多个选择的时候,它会选择哪个被设置为 Primary Bean 的 Bean 来注入。但是,如果你需要根据不同情况使用(注入)不同的实现类呢?比如,在这个类中你可能需要注入
在使用
在本例中,Spring 注入时就会去寻找限定符为字符串 iceCream 的 bean,而默认情况下,每个 bean 的 ID 为首字母小写的类名,而 bean ID 又是默认的限定符,所以
但是,使用 bean ID 作为限定符有一个缺点:和实现类高度耦合。如果你把类
而注入时也和使用 bean ID 时一样,使用
这样,你就可以在重构
定义自定义限定符注解(使用
声明 bean 时:
注入 bean 时:
因为 Spring 有 Java 和 XML 两种配置方式,所以导入也就有如下四种:
JavaConfig 导入 JavaConfig
JavaConfig 导入 XML 配置
XML 配置导入 XML 配置
XML 配置导入 JavaConfig
NOTE:如果你的 Spring 应用真的存在多个配置文件,建议创建一个高层级的配置文件。这个文件中不配置任务的 Bean,只是用来导入各个不同配置文件的
或者在高层级配置文件中导入所有的 JavaConfig:
Spring @PropertySource example
PropertySource API
比如,我们需要为开发和生产环境配置不同的数据库源:
以上,为不同的环境声明了不同的 bean,这些 bean 都是
一种方案:将这些 bean 分别配置在不同的配置 class (或 XML 文件)中,然后在构建时确定包含哪个(可能使用 Maven profiles),这种方式的弊端在于每次修改配置都需要重新构建。
另一种方案:使用 Spring profile bean。
在 Spring 3.1 中,Spring 引入了 bean profiles。你需要将不同环境使用的配置分别声明在不同的配置文件中,然后告诉 Spring 这个配置是哪个环境下使用的,那么 Spring 就会在不同的环境下做出正确的选择。
这样,Spring 就会根据激活的 profile选择不同的配置。在 Spring 3.1 中,你只能在 class level 使用
有如下方式可以设置这两个属性:
Web 应用的 context 参数
JNDI entries
环境变量
JVM 系统变量
在集成测试类中使用
那么,
在 web.xml 中设置
Spring 注入 bean 可以有如下三种方式:
自动注入
显式注入(java 配置)
显式注入(xml 配置)
自动注入
自动注入主要使用如下的技术:Component scanning:Spring 自动发现需要在应用上下文中生成的 bean
Autowiring:自动注入 bean 依赖
配置可被发现的 beans
使用@Component注解配置可被发现的 Bean,默认名称为类名(首字母小写)
package soundsystem; import org.springframework.stereotype.Component; @Component public class SgtPeppers implements CompactDisc { private String title = "Sgt. Pepper's Lonely Hearts Club Band"; private String artist = "The Beatles"; public void play() { System.out.println("Playing " + title + " by " + artist); } }
配置 Bean 的名称:
@Component("lonelyHeartsClub") public class SgtPeppers implements CompactDisc { ... }
定义 Spring 注入配置类
使用@Configuration注解标明这是 spring 的配置类。使用
@ComponentScan注解开启 spring component scanning。
package soundsystem; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class CDPlayerConfig { }
@Component注解默认会扫描配置类所在的包,也可以通过
basePackages属性配置需要扫描的包。
@Configuration @ComponentScan("soundsystem")//扫描 soundsystem 包 public class CDPlayerConfig {} // 或者使用 basePackages 属性 @Configuration @ComponentScan(basePackages="soundsystem") public class CDPlayerConfig {} // 配置多个包 @Configuration @ComponentScan(basePackages={"soundsystem", "video"}) public class CDPlayerConfig {} // 使用 class 或 interface 指定需要扫描的包 @Configuration @ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class}) public class CDPlayerConfig {}
basePackages:使用字符串配置,不那么类型安全
basePackageClasses:使用 java 配置,类型安全(对重构友好)
Note:当使用
basePackageClasses的时候,可以在需要扫描的包中定义一个空的标签接口(empty marker interface),这样可实现与业务代码的分离。
自动注入依赖
使用@Autowired注解自动注入依赖。无论你将
@Autowired注解放在
构造函数,
setter方法还是
任意方法上,Spring 都会尝试去自动注入定义在方法参数中的依赖。当且仅有一个匹配的 Bean 的时候,Spring 就会自动注入。当匹配多个 Bean 时,Spring 会抛出异常。你也可以通过设置
@Autowired注解的
required属性设置这个依赖是否为必须。
package soundsystem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CDPlayer implements MediaPlayer { private CompactDisc cd; // 构造函数注入 @Autowired public CDPlayer(CompactDisc cd) { this.cd = cd; } public void play() { cd.play(); } } // setter方法注入 @Autowired public void setCompactDisc(CompactDisc cd) { this.cd = cd; } // 任意方法注入 @Autowired public void insertDisc(CompactDisc cd) { this.cd = cd; } // 设置依赖可为null // 当spring在容器中不能找到对应的依赖时,不会抛出异常 @Autowired(required=false) public CDPlayer(CompactDisc cd) { this.cd = cd; }
显式注入(Java 配置)
Java 配置 VS XML 配置Java:更强大、类型安全、重构友好
XML:更灵活
Note:使用 Java 配置时,应该将 JavaConfig 和业务代码分开,最好使用不同的包管理。因为 JavaConfig 毕竟是配置文件,所以不应该和业务逻辑代码混一块。
@Bean
注解声明 Bean
使用 @Bean注解生成对象的方法,Spring 就会注册这个方法返回的对象到 Spring 应用上下文中。
默认注册的 Bean 的名称为方法名,也可以通过
@Bean注解的
name属性定义 Bean 名称。
package soundsystem; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CDPlayerConfig { @Bean public CompactDisc compactDisc() { return new SgtPeppers(); } }
// 指定注册 bean 的名称 @Bean(name="lonelyHeartsClubBand") public CompactDisc compactDisc() { int choice = (int) Math.floor(Math.random() * 4); if (choice == 0) { return new SgtPeppers(); } else if (choice == 1) { return new WhiteAlbum(); } else if (choice == 2) { return new HardDaysNight(); } else { return new Revolver(); } }
如何在 JavaConfig 中声明依赖于其他 Bean 的 Bean?只需要在声明方法的参数中指定需要的依赖类型,Spring 会自动帮你注入。(无论该方法依赖的 Bean 是在 JavaConfig 中声明的,还是 Xml 配置中声明的)
// 构造器注入 @Bean public CDPlayer cdPlayer(CompactDisc compactDisc) { return new CDPlayer(compactDisc); } // setter方法注入 @Bean public CDPlayer cdPlayer(CompactDisc compactDisc) { CDPlayer cdPlayer = new CDPlayer(compactDisc); cdPlayer.setCompactDisc(compactDisc); return cdPlayer; }
显式声明(XML 配置)
XML 作为 Spring 以前的配置方法被大量的使用,所以有必要学习如何通过 XML 配置 Spring。但是,当开始一个新的项目时,推荐使用 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 http://www.springframework.org/schema/context"> <!-- configuration details go here --> </beans>
Note:可使用 Spring Tool Suite 自动生成 XML 配置文件
声明一个简单的 Bean
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
<bean>元素和 JavaConfig 中的
@Bean注解含义相同。
id指定了 bean 的名称,
class指定了 bean 的类型。当 Spring 遇到
<bean>元素时,它会调用
SgtPepers的默认构造函数来创建 bean。
正因为
class属性使用字面量,所以不能进行编译时检查,不过可以使用 IDE 来进行类型的检查。
很多时候,声明一个 bean,还需要初始化很多属性,那么如何使用 Spring 注入这些属性呢?
构造器注入
<constructor-arg>元素
c-namespace
属性注入
<property>元素
p-namespace
其中
c-namespace和
p-namespace是 Spring 3.0 引入的新的配置方法,更简洁。但是相比于
<constructor-arg>和
<property>元素,少了集合注入的功能。
使用构造器注入初始化 bean
在 Xml 中配置构造器依赖注入有两种方式:<constructor-arg>元素:结构复杂
Spring 3.0 中引入的 c-namespace:简洁,不能注入集合
Bean 引用的构造器注入
使用<constructor-arg>:
<bean id=" class="soundsystem.CDPlayer"> <constructor-arg ref="compactDisc" /> </bean>
使用 c-namespace。首先声明 c-namespace 的 xml schema,然后注入:
<?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:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="compactDisc" class="soundsystem.SgtPeppers" /> <bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" /> </beans>
也可以使用参数列表的方式注入:
<!-- 注入第一个参数 --> <bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc" /> <!-- 如果只有一个参数,可省略参数index --> <bean id="cdPlayer" class="soundsystem.CDPlayer" c:_-ref="compactDisc" />
其中
_0表示第 0 个参数,如果有多个参数依次为
_1,
_2。如果,仅有一个构造器参数,那么可以省略参数索引,直接使用
_就行了。
-ref后缀表示这是一个引用,而不仅仅是个字面量。
构造器注入字面量
有时候我们不需要引用其他的 bean,只需要自己指定字面量参数,构造器的字面量注入如下:<bean id="compactDisc" class="soundsystem.BlankDisc"> <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /> <constructor-arg value="The Beatles" /> </bean> <!-- c-namespace --> <bean id="compactDisc" class="soundsystem.BlankDisc" c:_title="Sgt. Pepper's Lonely Hearts Club Band" c:_artist="The Beatles" /> <!-- 使用参数索引 --> <bean id="compactDisc" class="soundsystem.BlankDisc" c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles" /> <!-- 只有一个参数,可忽略索引 --> <bean id="compactDisc" class="soundsystem.BlankDisc" c:_="Sgt. Pepper's Lonely Hearts Club Band" />
构造器集合注入
因为 c-namespace 不支持集合的注入,所以要注入集合只能使用<constructor-arg>元素:
<!-- 注入null --> <bean id="compactDisc" class="soundsystem.BlankDisc"> <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /> <constructor-arg value="The Beatles" /> <constructor-arg><null/></constructor-arg> </bean> <!-- 集合注入字面量 --> <bean id="compactDisc" class="soundsystem.collections.BlankDisc"> <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" /> <constructor-arg value="The Beatles" /> <constructor-arg> <list> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> <value>Lucy in the Sky with Diamonds</value> </list> </constructor-arg> </bean>
如果需要注入的集合中的元素为对象,那么可以使用
<ref>元素:
// 需注入的构造器 public Discography(String artist, List<CompactDisc> cds) { ... }
<bean id="beatlesDiscography" class="soundsystem.Discography"> <constructor-arg value="The Beatles" /> <constructor-arg> <list> <ref bean="sgtPeppers" /> <ref bean="whiteAlbum" /> <ref bean="hardDaysNight" /> <ref bean="revolver" /> </list> </constructor-arg> </bean>
属性注入初始化 bean
构造器注入 VS 属性注入:当属性为必须时使用构造器注入,属性非必填时使用构造器注入。属性注入引用对象
属性注入使用<property>元素:
<bean id="cdPlayer" class="soundsystem.CDPlayer"> <property name="compactDisc" ref="compactDisc" /> </bean>
使用 p-namespace 注入:
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="cdPlayer" class="soundsystem.properties.CDPlayer" p:compactDisc-ref="compactDisc" /> </beans>
属性注入字面量
<bean id="compactDisc" class="soundsystem.properties.BlankDisc"> <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" /> <property name="artist" value="The Beatles" /> <property name="tracks"> <list> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> <value>Lucy in the Sky with Diamonds</value> </list> </property> </bean>
<bean id="compactDisc" class="soundsystem.properties.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles"> <property name="tracks"> <list> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> <value>Lucy in the Sky with Diamonds</value> </list> </property> </bean>
使用
util-namespace简化注入集合,
util-namespace包含如下工具:
元素 | 描述 |
---|---|
<util:constant> | 引用某类型的 public static字段,并把它暴露为一个 bean |
<util:list> | 创建一个 java.util.List类型的 bean |
<util:map> | 创建一个 java.util.Map类型的 bean |
<util:properties> | 创建一个 java.util.Properties类型的 bean |
<util:property-path> | 引用一个 bean 的属性(或嵌套属性),并暴露为 bean |
<util:set> | 创建一个 java.util.Set类型的 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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <bean id="compactDisc" class="soundsystem.properties.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles" p:tracks-ref="trackList" /> <util:list id="trackList"> <value>Sgt. Pepper's Lonely Hearts Club Band</value> <value>With a Little Help from My Friends</value> <value>Lucy in the Sky with Diamonds</value> </util:list> </beans>
Autowiring 中包冲突解决
Autowiring 能够很方便的为我们自动注入需要的依赖,但是如果需要注入的接口不止一个实现类,那 Spring 如何帮我们定位我们需要的依赖呢?假设你需要注入一个
Dessert接口的实现类:
@Autowired public void setDessert(Dessert dessert) { this.dessert = dessert; }
现在有如下三个实现类:
@Component public class Cake implements Dessert { ... } @Component public class Cookies implements Dessert { ... } @Component public class IceCream implements Dessert { ... }
因为三个实现类都继承自
Dessert接口,所以自动注入时 Spring 就不能确定注入哪一个,它就会抛出
NoUniqueBeanDefinitionException异常。
那么有哪些方法能够帮助 Spring 消除这种不明确的情况呢?
设置一个 primary bean
Qualifying autowired beans
设置 primary bean
当有多个选择时,我们可以通过设置一个 Primary Bean 来告诉 Spring 使用这个 Bean。可以有如下三种方式指定 Primary bean:// 自动注入时声明一个primary bean @Component @Primary public class IceCream implements Dessert { ... } // 在JavaConfig中显式注入时 @Bean @Primary public Dessert iceCream() { return new IceCream(); } // 或者在XML中显式注入时指定primary bean <bean id="iceCream" class="com.desserteater.IceCream" primary="true" />
现在,如果 Spring 碰到有多个选择的时候,它会选择哪个被设置为 Primary Bean 的 Bean 来注入。但是,如果你需要根据不同情况使用(注入)不同的实现类呢?比如,在这个类中你可能需要注入
IceCream,而在另一种情况下你需要注入
Cake呢。这种情况下就需要为这些类添加某种限定符。
Qualifying autowired beans
默认情况下每个未显式声明限定符的 bean,都有一个默认的限定符——bean ID。所以,我们在注入时可以使用这个 bean ID 限定符来限定注入的 bean。在使用
@Autowired或
@Inject注解时,
@Qualifier注解是使用限定符的主要方式:
@Autowired @Qualifier("iceCream") public void setDessert(Dessert dessert) { this.dessert = dessert; }
在本例中,Spring 注入时就会去寻找限定符为字符串 iceCream 的 bean,而默认情况下,每个 bean 的 ID 为首字母小写的类名,而 bean ID 又是默认的限定符,所以
IceCream类就被选中,被注入了。
但是,使用 bean ID 作为限定符有一个缺点:和实现类高度耦合。如果你把类
IceCream重构为
Gelato,那么你需要修改注入时
@Qualifier注解的值,以便使用重构后的类。所以,有什么办法能够做到松耦合呢?答案就是创建自定义限定符。
自定义限定符
除了使用默认的 bean ID 限定符,在声明 bean 的时候还可以创建自定义的限定符:@Component @Qualifier("cold") public class IceCream implements Dessert { ... }
而注入时也和使用 bean ID 时一样,使用
@Qualifier注解来指定使用的限定符:
@Autowired @Qualifier("cold") public void setDessert(Dessert dessert) { this.dessert = dessert; }
这样,你就可以在重构
IceCream类的同时,不用修改
setDessert方法,保证了松耦合。
定义自定义限定符注解
如果,一个限定符还是无法满足你的要求,你就需要自己定义限定符的注解,因为 java 不允许一个类使用多个相同的注解。定义自定义限定符注解(使用
@Qualifier注解该注解就行):
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Cold { } @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Creamy { }
声明 bean 时:
@Component @Cold @Creamy public class IceCream implements Dessert { ... } @Component @Cold @Fruity public class Popsicle implements Dessert { ... }
注入 bean 时:
@Autowired @Cold @Creamy public void setDessert(Dessert dessert) { this.dessert = dessert; }
配置的导入和混合
在一个典型的 Spring 应用中,你可能需要同时用到 JavaConfig 和 XML 配置,那么我们如何用两种不同的配置方式来配置一个容器呢?这就需要用到 Spring 配置的导入功能了。因为 Spring 有 Java 和 XML 两种配置方式,所以导入也就有如下四种:
JavaConfig 导入 JavaConfig
JavaConfig 导入 XML 配置
XML 配置导入 XML 配置
XML 配置导入 JavaConfig
NOTE:如果你的 Spring 应用真的存在多个配置文件,建议创建一个高层级的配置文件。这个文件中不配置任务的 Bean,只是用来导入各个不同配置文件的
root configuration。这样,显得配置就很清晰,可以一下定位应用的入口。而且,建议在
root configuration中来开启 component scanning(
<context:component-scan>或则
@ComponentScan)功能。
JavaConfig 导入 JavaConfig
使用@Import注解导入:
package soundsystem; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @Import(CDPlayerConfig.class) public class SoundSystemConfig { }
或者在高层级配置文件中导入所有的 JavaConfig:
package soundsystem; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @Import({CDPlayerConfig.class, CDConfig.class}) public class SoundSystemConfig { }
JavaConfig 导入 XML 配置
使用@ImportResource注解在 JavaConfig 中导入 XML 配置:
package soundsystem; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportResource; @Configuration @Import(CDPlayerConfig.class) @ImportResource("classpath:cd-config.xml") public class SoundSystemConfig { }
XML 配置导入 XML 配置
使用<import>元素导入 XML 配置:
<import resource="cd-config.xml" />
XML 配置导入 JavaConfig
要在 XML 中导入 Java,只需要在 XML 配置中将 JavaConfig 声明为一个 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:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 导入JavaConfig --> <bean class="soundsystem.CDConfig" /> <bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" /> </beans>
JavaConfig 使用 properties 配置文件
为了不把数据库配置等信息硬编码到代码中,很多时候我们都倾向于使用properties文件。那么,如何使用这些文件呢,参考文章:
Spring @PropertySource example
PropertySource API
环境和配置
每个应用几乎都会遇到从一个环境迁移到另一个环境的问题,大多数时候我们需要为不同的环境设置不同的值,比如数据库配置、加密算法以及和其他系统的集成。比如,我们需要为开发和生产环境配置不同的数据库源:
// 开发环境 @Bean(destroyMethod = "shutdown") public DataSource embeddedDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("classpath:schema.sql") .addScript("classpath:test-data.sql") .build(); } // 生产环境 @Bean 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,这些 bean 都是
DataSource实例。那么,如何实现不同的环境使用不同的数据库配置呢?
一种方案:将这些 bean 分别配置在不同的配置 class (或 XML 文件)中,然后在构建时确定包含哪个(可能使用 Maven profiles),这种方式的弊端在于每次修改配置都需要重新构建。
另一种方案:使用 Spring profile bean。
配置 Profile beans
Spring profile bean 是在运行时确定使用哪种配置,而且是通过根据不同的环境创建不同的 bean 的方式来实现的。这样的话一个相同构建单元(比如 WAR 包)就能应付所有的环境,不需要针对不同的环境去进行不同的构建。在 Spring 3.1 中,Spring 引入了 bean profiles。你需要将不同环境使用的配置分别声明在不同的配置文件中,然后告诉 Spring 这个配置是哪个环境下使用的,那么 Spring 就会在不同的环境下做出正确的选择。
JavaConfig
在 JavaConfig 中,使用@Profile注解标明这个 bean 属于哪个 profile:
package com.myapp; import javax.activation.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; // 开发环境 @Configuration @Profile("dev") public class DevelopmentProfileConfig { @Bean(destroyMethod="shutdown") public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("classpath:schema.sql") .addScript("classpath:test-data.sql") .build(); } }
package com.myapp; import javax.activation.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.jndi.JndiObjectFactoryBean; // 生产环境 @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(); } }
这样,Spring 就会根据激活的 profile选择不同的配置。在 Spring 3.1 中,你只能在 class level 使用
@Profile注解。从 Spring 3.2 开始,你可以在 method level 使用
@Profile注解了,和
@Bean注解一起。这样,就能将配置都写在一个 JavaConfig 文件中了。
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 DataSourceConfig { @Bean(destroyMethod = "shutdown") @Profile("dev") public DataSource embeddedDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("classpath:schema.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(); } }
XML 配置 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:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <beans profile="dev"> <jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:schema.sql" /> <jdbc:script location="classpath:test-data.sql" /> </jdbc:embedded-database> </beans> <beans profile="prod"> <jee:jndi-lookup id="dataSource" lazy-init="true" jndi-name="jdbc/myDatabase" resource-ref="true" proxy-interface="javax.sql.DataSource" /> </beans> </beans>
激活 profiles
Spring 使用两个属性来确定激活的 profile:spring.profiles.active和
spring.profiles.default。Spring 首先使用
spring.profiles.active设置的值,如果
spring.profiles.active没有设值,那么 Spring 就使用
spring.profiles.default的值。如果
spring.profiles.active和
spring.profiles.default都没有设值,那么没有任何 profile 被激活。
有如下方式可以设置这两个属性:
DispatcherServlet的初始化参数
Web 应用的 context 参数
JNDI entries
环境变量
JVM 系统变量
在集成测试类中使用
@ActiveProfiles注解
那么,
spring.profiles.active和
spring.profiles.default两个属性该如何选择呢?一种方式是在
DispatcherServlet的初始化参数和 web 应用的 context 参数(用于
ContextLoaderListener)中设置
spring.profiles.default为开发环境,当切换到其他环境时再通过设置环境变量等其他方式设置
spring.profiles.active来激活不同的 profile。
在 web.xml 中设置
spring.profiles.default:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> <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>appServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <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>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
在测试中使用 profile
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={PersistenceTestConfig.class}) @ActiveProfiles("dev") public class PersistenceTest { ... }
相关文章推荐
- Spring 学习笔记(4)—— Bean 的基本配置、依赖注入(DI)、注入参数引用其他Bean
- Spring学习笔记--Spring配置文件和依赖注入
- Spring 学习笔记(四)—— XML配置依赖注入
- spring学习笔记3--依赖注入对象
- Spring学习笔记(5)----依赖注入的简单实现
- Spring 学习笔记 - IOC/依赖注入
- spring 控制反转与依赖注入原理-学习笔记
- spring学习笔记(1)-spring依赖注入的几种方式
- spring学习笔记3——注入依赖对象
- Spring学习笔记(5)----依赖注入的简单实现
- ASP.NET_MVC使用Spring.Net.MVC依赖注入学习笔记
- Spring通过容器获取配置对象及事件注入(学习笔记二)
- Spring学习笔记--依赖注入
- 传智播客 Spring学习 xml配置依赖注入
- Spring 4 学习笔记2:控制反转(IoC)和依赖注入(DI)
- Spring 学习笔记之依赖注入
- ITCAST视频-Spring学习笔记(编码剖析Spring依赖注入的原理)
- Spring4学习笔记-泛型依赖注入
- Spring 学习笔记 ----依赖注入
- Spring 4 学习笔记3:依赖注入(DI)