首个里程碑版本 - Spring 3.1 已经发布,我们有一系列文章来讲解 Spring 的一些新特性:
- Bean definition profiles
- ...
- ...
今天我们来讲解第一项 - 被称为 Bean definition profiles 的新特性。我们收到最多的请求是提供一个基于核心容器的机制,以允许在不同环境中注册不同的beans。环境一词针对不同的用户有不同的解释,不过最典型的场景是仅在性能测试环境下注册监控组件、或针对客户A和客户B的两个部署分别注册各自定制的beans。可能最常用的案例是在开发阶段使用单独的 datasource,而在 QA环境或生产环境中从 JNDI 中查找一个相同的 datasource。Bean definition profiles 是一种能够满足以上各种需求的通用解决方法,下面用一个示例来详细讲解。
Understanding the application
首先来看一个银行系统中用于示范怎样在两个帐户之间转帐的 JUnit test case。
public class IntegrationTests { @Test public void transferTenDollars() throws InsufficientFundsException { ApplicationContext ctx = // instantiate the spring container TransferService transferService = ctx.getBean(TransferService.class); AccountRepository accountRepository = ctx.getBean(AccountRepository.class); assertThat(accountRepository.findById("A123").getBalance(), equalTo(100.00)); assertThat(accountRepository.findById("C456").getBalance(), equalTo(0.00)); transferService.transfer(10.00, "A123", "C456"); assertThat(accountRepository.findById("A123").getBalance(), equalTo(90.00)); assertThat(accountRepository.findById("C456").getBalance(), equalTo(10.00)); } } |
我们的目标很简单,从 帐户A123向帐户 C456转 10 美元。
典型的 XML 配置
bean definition profiles 也支持 @Configuration 方式的配置,在这里我们使用大家最熟悉的 XML 配置方式。
先不要管 bean definition profiles,考虑平时我们的 XML 配置会是怎样的。假设我们在开发阶段,一般我们会选择使用一个独立的 datasource,为了方便在这里我们使用 HSQLDB( -Spring 内存数据库( 嵌入式数据库 ) )
<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" xsi:schemaLocation="..."> <bean id="transferService" class="com.bank.service.internal.DefaultTransferService"> <constructor-arg ref="accountRepository"/> <constructor-arg ref="feePolicy"/> </bean> <bean id="accountRepository" class="com.bank.repository.internal.JdbcAccountRepository"> <constructor-arg ref="dataSource"/> </bean> <bean id="feePolicy" class="com.bank.service.internal.ZeroFeePolicy"/> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans> |
基于上面的 XML 配置,之前 JUnit test case 缺少的部分如下:
public class IntegrationTests { @Test public void transferTenDollars() throws InsufficientFundsException { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:/com/bank/config/xml/transfer-service-config.xml"); ctx.refresh(); TransferService transferService = ctx.getBean(TransferService.class); AccountRepository accountRepository = ctx.getBean(AccountRepository.class); // perform transfer and issue assertions as above ... } } |
当运行测试程序时,测试条会显示绿色,我们这个简单的应用和容器连接在一起,我们从容器中获取 beans 并且使用它们,这里跟平时相比没有什么特别的。当我们考虑怎样将该应用部署到 QA 环境或生产环境时问题变得有趣了。例如,一个常用的场景是在开发阶段使用 Tomcat 作为服务器( 更易用 ),但在生产环境中会将应用部署到 WebSphere 中。而在生产环境中 datasource 通常被注册到服务器的 JNDI 目录中。这意思着为了获取 datasource 我们必须要执行 JNDI 查找( JNDI lookup )。当然,对此 Spring 提供了非常好的支持,非常流行的方法是使用 Spring 的 <jee:jndi-lookup/> 元素。产生环境中的配置如下:
<beans ...> <bean id="transferService" ... /> <bean id="accountRepository" class="com.bank.repository.internal.JdbcAccountRepository"> <constructor-arg ref="dataSource"/> </bean> <bean id="feePolicy" ... /> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> |
上面的配置会正常工作。但问题是如何基于当前环境来切换 datasource 的配置方式呢?过去的一段时间里,Spring 的用户已经想出很多种方法来达到目的,通常都会依赖于一个系统环境变量( system environment variables )和 一个包含 ${placeholder}的<import/> 元素,通过环境变量的值来解析出正确的配置文件路径。不过这种方法不能称为一流的解决方法。
Enter bean definition profiles
概括一下上面所描述的基于环境的bean定义 - 在某些上下文中注册某些 bean。也可以说 在情况A时注册一批( a certain profile of )bean,而在情况B时注册另一批不同的 bean。
在 Spring 3.1 中,<beans/> XML 文档现在已经包含了这个新概念,针对上面的示例,我们可以把配置文件分为三个文件,注意 *-datasource.xml 文件中的 profile="..." 属性。
transfer-service.xml:
<beans ...> <bean id="transferService" ... /> <bean id="accountRepository" class="com.bank.repository.internal.JdbcAccountRepository"> <constructor-arg ref="dataSource"/> </bean> <bean id="feePolicy" ... /> </beans> |
standalone-datasource-config.xml:
<beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans> |
jndi-datasource-config.xml:
<beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> |
更新 test case,同时载入 3 个配置文件:
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:/com/bank/config/xml/*-config.xml"); ctx.refresh(); |
这样还不行,当运行单元测试时我们会得到一个 NoSuchBeanDefinitionException,因为容器无法找到名为 dataSource 的 bean。原因是虽然我们明确定义了两个 profiles - dev 和 production,但我们并没有激活其中的一个 profile。
Enter the Environment
在 Spring 3.1 中出现了一个新概念 - Environment。这个抽象概念贯穿于整个容器,以后的文章中会经常的看到这个概念。在这里重要的是要知道,Environment 包括了哪个 profile 正处于激活状态的信息。当 Spring ApplicationContext 加载上述三个配置文件时,会非常注意 <beans> 元素的 profile 属性,如果 beans 元素有 profile 属性,且其属性值所代表的 profile 并不是当前激活的 profile,则整个配置文件会被跳过,没有任何 bean 会被解析或被注册。
激活一个 profile 有多种方式,最直接的办法是使用 ApplicationContext API 以编程式的方式来实现:
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.getEnvironment().setActiveProfiles("dev"); ctx.load("classpath:/com/bank/config/xml/*-config.xml"); ctx.refresh(); |
这样当我们执行 test case 时,测试会通过。让我们看一下容器是怎样加载这三个配置文件的( *-config.xml )
- transfer-service-config.xml - beans 元素没有 profile 属性,因此总会被容器解析
- standalone-datasource-config.xml - 指定了 profile="div",并且 div profile 是当前激活的 profile,因此会被解析
- jndi-datasource-config.xml - 指定了 profile="production",但 production profile 并不是激活状态,因此被跳过
那在真正的生产环境中如何切换为 JNDI looup 呢?当然必须要激活 production profile。像上面那样为了执行单元测试而使用编程式激活profile 的方式是非常合适的,但当部署 WAR 文件时这种方法并不适用。因此,profiles 也可以通过 spring.profiles.active 属性使用声明式激活方式,spring.profiles.active 属性值可以通过很多种方式指定:
- system environment variables
- JVM system properties
- servlet context parameters in web.xml
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>spring.profiles.active</param-name>
<param-value>production</param-value>
</init-param>
</servlet> - servlet config parameter( 即上面划线部分, 猜测 init-param 可能要添加到 Root Application Context 上才可以 )
- entry in JNDI
注意,profiles 并不是非此即彼的关系( 互斥 ),完全可以一次性激活多个 profile,使用编程式激活方法时,可以直接给 setActiveProfiles(String ...) 方法提供多个 profile name:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2"); |
若使用声明式激活方法的话,spring.profiles.active 可以接收多个以逗号分隔的 profile name:
-Dspring.profiles.active="profile1,profile2" |
beans 元素的 profile 属性也可以设置多个候选 profile name :
<beans profile="profile1,profile2"> ... </beans> |
这提供了分解应用的一种灵活的方法,以便交叉分析在哪种情况下哪些 bean 会被注册。
Making it simpler : introducing nested <beans/> elements
目前为止,bean definition profile 给我们提供了一种方便的机制基于部署上下文/环境来决定哪些 beans 被注册,但这样引来一个问题:本来是一个配置文件,现在不得不使用3个配置文件。为了区分 profile="dev" 和 profile="production" 切割配置文件是必须的,因为 profile 属性是设置在 beans 元素上的。
在 Spring 3.1 中,在一个配置文件中存在嵌套的 <beans/> 元素是允许的,这意味着,我们可以仅使用一个配置文件,来实现 profile 的定义:
<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" xsi:schemaLocation="..."> <bean id="transferService" class="com.bank.service.internal.DefaultTransferService"> <constructor-arg ref="accountRepository"/> <constructor-arg ref="feePolicy"/> </bean> <bean id="accountRepository" class="com.bank.repository.internal.JdbcAccountRepository"> <constructor-arg ref="dataSource"/> </bean> <bean id="feePolicy" class="com.bank.service.internal.ZeroFeePolicy"/> <beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> </beans> |
Spring-beans-3.1.xsd 已经更新,允许这种嵌套,但有一个限制条件,这些嵌套的 <beans/> 元素必须位于整个配置文件的最下面
( 作为根结点 <beans/> 元素的最后面的子元素 )。虽然这个特性是为了给 bean definition profiles 提供支持,但嵌套的 <beans/> 元素在其他情况下也非常有用,想象一下有一批 beans 需要设置为 lazy-init="true",你可以为每个 bean 都设置 lazy-init="true",但更方便的做法是直接给 <beans/> 元素设置 lazy-init="true",这样其所有的子元素bean 都会继承这个设置。而嵌套的 <beans/> 元素这一支持,使得你不需要专门为这一批 bean 创建一个单独的配置文件,直接嵌套 <beans/> 元素即可。
Caveats
使用 bean definition profiles 时有一些注意事项
-
如果有更简洁的方法来实现目的,不要使用 profiles
如果在各个 profiles 之间唯一的变化是属性值,那么使用 Spring 已经提供的 PropertyPlaceholderConfigurer 或 <context:property-placeholder /> 可能就够了。
- ...
...
2014.5.6 更新
经测试,应该通过全局的 <context-param> 来激活 profile, 通过上文中的 <init-param> 方式并不能激活相应的 profile
<context-param> <param-name>spring.profiles.active</param-name> <param-value>dev</param-value> </context-param> |
使用 web.xml 的 <context-param> 来激活 profile 有个缺点, 当环境变化时( test/dev/product )需要频繁的更改 web.xml 文件.
尤其当使用西部数码 Java 虚拟主机的 Tomcat 时, 只能对 server.xml 进行配置, 其他配置文件( 包括共享的 web.xml 文件 )是没权限碰的, 下面是使用 server.xml 中 Context#Parameter 配置来取代 <context-param> 方法, 实际上 server.xml#Context#Parameter 与 web.xml#context-param 的效果是完全一样的, 具体请参考下面的官方资料:
( server.xml )Context Parameter
配置也很简单, ( server.xml )如下:
<Context docBase="..." path="" .../> <Parameter name="spring.profiles.active" value="product"/> </Context> |
针对不同环境的 Tomcat, 分别给 Context ( 容器 )设置相应的 Context Parameter, 这样, 同一个应用就可以在代码不变的情况下部署到各个环境中, 并且能够自动激活当前环境对应的 profile 了
另外, 测试时使用 Jetty 服务器我感觉比 Tomcat 要方便很多, 一般使用 Eclipse + RunJettyRun, 此时, server.xml#Context#Parameter 这种方式明显不适合 Jetty, 来看一下如何配置:
还有个小技巧, 如果测试 / 开发环境都使用 Jetty, 可以在上图中, 右键项目 -> Duplicate 复制一份, 然后配置各自的 Environment 来激活不同的 profile 即可.
2014.5.22 更新
激活 Profile 的关键元素有哪些?看源码
org.springframework.core.env.StandardEnvironment
/** System environment property source name: {@value} */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value} */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
getSystemEnvironment()));
}
|
StandardEnvironment 的子类 StandardServletEnvironment
/** Servlet config init parameters property source name: {@value} */
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
/** Servlet context init parameters property source name: {@value} */
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
/** JNDI property source name: {@value} */
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
|
相关推荐
浅谈xml配置spring profiles的几个注意点 Spring Profiles是Spring框架中的一种功能,它允许开发者根据不同的环境配置不同的Bean。在xml配置文件中,我们可以使用`<beans>`元素的`profile`属性来指定不同的配置...
Spring-Profiles-介绍来源属于博客: 拉动并运行: ! 确保 Maven 在您的路径上$ md spring-profiles $ cd spring-profiles $ git 初始化$ git remote add origin $ git pull origin master $ mvn clean compile ...
反馈 通过示例学习 - 使用 Spring Profiles、Spring MVC、REST、JPA/Hibernate、AngularJS、Graphene、Gradle、OpenShift 部署构建的简单反馈应用程序
8. **Spring profiles**:在多环境部署中,Spring profiles允许我们为不同的环境(如开发、测试、生产)配置不同的属性。这有助于保持代码的可复用性和可维护性。 9. **Spring Batch**:Spring Batch提供了一套完整...
Spring Boot配置特定属性spring.profiles的方法 Spring Boot框架提供了多种方式来配置特定属性,今天我们将介绍如何使用spring.profiles来配置特定属性。 什么是spring.profiles? spring.profiles是Spring Boot...
10. **Spring Profiles**:Spring Profiles允许根据环境条件选择不同的配置,有助于在开发、测试和生产环境中灵活切换。 总的来说,《Spring in Action》第二版是一本全面且深入的Spring指南,适合有一定Java基础,...
7. **Spring Profiles**:允许根据不同的环境(如开发、测试、生产)配置不同的bean。 8. **Spring Expression Language (SpEL)**:用于在运行时查询和操纵对象图的强大的表达式语言。 综上所述,"springDemo"可能...
- Spring Profiles允许在不同环境中使用不同的配置,比如开发环境、测试环境和生产环境。 11. **Spring WebSocket**: - Spring支持WebSocket协议,提供了Stomp协议的集成,用于实现实时通信。 12. **Spring ...
- **Spring Profiles**:根据环境配置不同bean。 - **WebFlux实战**:利用Reactor或RxJava实现非阻塞I/O。 - **Spring Cloud**:集成Spring Boot和Spring Cloud,构建微服务架构。 - **Spring Batch**:处理批量...
在多环境部署中,Spring Profiles可以帮助我们根据不同环境加载不同的配置。这部分可能涉及如何定义和激活profiles。 10. **Spring Batch** Spring Batch是用于批处理操作的模块,适合大量数据处理场景。如果提及...
8. **Spring Profiles**:Spring 3引入了环境配置的概念,允许你在不同环境中使用不同的配置。源代码中可能包含对不同profile的配置和切换。 9. **Spring AOP事务管理**:Spring通过AOP实现声明式事务管理,让...
8. **Spring profiles**:用于不同环境下的配置切换,例如开发、测试和生产环境。 9. **Spring测试**:包括单元测试和集成测试,使用@Test注解和Mockito等库来验证代码行为。 10. **构建工具**:如Maven或Gradle,...
Spring Profiles是Spring框架中的一个特性,用于在不同的环境中使用不同的配置。通过定义不同的profile,可以在生产环境和开发环境使用不同的数据库连接、日志级别等配置。 如何在自定义端口上运行Spring Boot应用...
6. **Spring Profiles Live**: Spring配置可以基于不同的环境(如开发、测试、生产)通过profiles来切换。"Live"可能意味着根据当前环境动态应用配置。 7. **Spring WebSocket Live**: Spring支持WebSocket协议,...
9. **Spring Profiles**:通过定义不同环境的profile,可以在不同环境下使用不同的配置,例如开发环境、测试环境和生产环境。 10. **Spring Boot DevTools**:开发者工具集,包括热部署、重启应用等功能,提高了...
对于有经验的开发者而言,Spring Boot 提供了许多高级特性和扩展点,例如自定义自动配置、Spring Profiles 的使用、安全性和性能优化等。 ### 开始使用 #### 引入 Spring Boot Spring Boot 介绍部分详细阐述了 ...
13. **Spring profiles**:在不同环境下切换配置,如dev、test、prod。 14. **Spring Boot与Spring Cloud的区别**:Spring Boot专注于快速、简洁地开发单个应用程序,而Spring Cloud为微服务架构提供了一整套解决...
12. **Spring Profiles**:允许在不同环境中使用不同的配置,使得开发、测试和生产环境的配置可以独立管理。 本书深入剖析了Spring架构的设计原理,对于想要深入了解Spring并提升自己技能的开发者来说,是一份宝贵...
Spring Boot Documentation 1. About the Documentation 2. Getting Help 3. First Steps 4. Working with Spring Boot 5. Learning about Spring Boot Features 6. Moving to Production 7. Advanced Topics II. ...
深入理解Spring Environment和Profiles,可能会出现在高级Spring开发者或架构师的面试题目中,包括如何管理不同环境的配置、如何使用@Profile以及如何扩展配置属性源等。 总结来说,Spring Environment抽象是...