使用Spring profile
软件开发过程一般涉及“开发 -> 测试 -> 部署上线”多个阶段,每个阶段的环境的配置参数会有不同,如数据源,文件路径等。为避免每次切换环境时都要进行参数配置等繁琐的操作,可以通过spring的profile功能来进行配置参数的切换。
profile可以理解为我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中。举个更具体的例子,我们以前所定义的Bean,当Spring容器一启动的时候,就会一股脑的全部加载这些信息完成对Bean的创建;而使用了Profile之后,它会将Bean的定义进行更细粒度的划分,将这些定义的Bean划分为几个不同的组,当Spring容器加载配置信息的时候,首先查找激活的Profile,然后只会去加载被激活的组中所定义的Bean信息,而不被激活的Profile中所定义的Bean定义信息是不会加载用于创建Bean的。
配置profile bean
现在假设有三个课程course的实现类,分别是数学math、英语English、语文Chinese.分别对应开发、测试、生成阶段使用的对象。
public interface Course {
void teach();
}
public class Math implements Course {
@Override
public void teach() {
System.out.println("教数学");
}
}
public class English implements Course {
@Override
public void teach() {
System.out.println("教英语");
}
}
public class Chinese implements Course {
@Override
public void teach() {
System.out.println("教语文");
}
}
1.在xml中配置profile
方式一:不同的profile配置在不同的配置文件中,开发,测试,生产各对应一个配置文件,在<beans>标签中使用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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd" profile="dev">
<bean name="math" class="com.demo.spring.Math"></bean>
</beans>
方式二:都配置在同一个配置文件中,在根<beans>标签中再嵌套一层<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">
<beans profile="dev">
<bean name="math" class="com.demo.spring.Math" ></bean>
</beans>
<beans profile="test">
<bean name="english" class="com.demo.spring.English"></bean>
</beans>
<beans profile="pro">
<bean name="chinese" class="com.demo.spring.Chinese"></bean>
</beans>
</beans>
2.使用注解配置profile
方式一,创建三个配置类,在类上使用@Profile注解
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public Math getMath(){
Math math = new Math();
return math;
}
}
方法二,使用同一个配置类,在不同的方法上使用@Profile注解
@Configuration
public class Config2 {
@Bean
@Profile("dev")
public Math getMath(){
Math math = new Math();
return math;
}
@Bean
@Profile("test")
public English getEnglish(){
English english = new English();
return english;
}
@Bean
@Profile("pro")
public Chinese getChinese(){
Chinese chinese = new Chinese();
return chinese;
}
}
当某个bean被定义了但是没有指定profile时,是一定会被spring容器创建的。
激活Profile
Spring依靠spring.profiles.active和spring.profiles.default两个属性来判断哪些profile需要被激活,如果当spring.profiles.active属性被设置时,那么Spring会优先使用该属性对应值来激活Profile。当spring.profiles.active没有被设置时,那么Spring会根据spring.profiles.default属性的对应值来进行Profile进行激活。如果上面的两个属性都没有被设置,那么就不会有任务Profile被激活,只有定义在Profile之外的Bean才会被创建。要设置这两个属性方式有:
- 作为SpringMVC中的DispatcherServlet的初始化参数
- 作为Web 应用上下文中的初始化参数
- 作为JNDI的入口
- 作为环境变量
- 作为虚拟机的系统参数
- 使用@AtivceProfile来进行激活
这样在切换应用环境时,只需要修改spring.profiles.active的值就可以了。
作为Web 应用上下文中的初始化参数和Servlet的配置参考如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 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_3_0.xsd">
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext*.xml
</param-value>
</context-param>
<!-- 在上下文context-param中设置profile.default的默认值 -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>development</param-value>
</context-param>
<!-- 在上下文context-param中设置profile.active的默认值 -->
<!-- 设置active后default失效,web启动时会加载对应的环境信息 -->
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>development</param-value>
</context-param>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 在DispatcherServlet参数中设置profile的默认值,active同理 -->
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>development</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>
测试代码如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config.class)
@ActiveProfiles("dev")
public class TeacherTest {
@Autowired
private Course course;
@Test
public void test(){
course.teach();
}
}
测试结果:
教数学
条件化的bean
使用@Conditional注解,主要作用于@Bean上,判断给定的条件,条件成立时,才会去创建这个Bean.常用使用场景是根据不同的系统创建不同的bean.通过@Target可以知道它可作用于类,方法中。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
如存在一个服务接口
public interface ListService {
public String showListCmd();
在不同系统下有不同的实现:
public class WindowsListService implements ListService {
@Override
public String showListCmd() {
return "windows";
}
public class LinuxListService implements ListService {
@Override
public String showListCmd() {
return "linux";
}
}
我们希望Windows系统中创建WindowsListService服务。则使用@Conditional注解如下;
public class ConditionConfig {
@Bean
@Conditional(WindowsCondition.class)// 使用@Conditional注解,符合Windows条件就实例化WindowsListService
public ListService windowsListService() {
return new WindowsListService();
}
}
@Conditional注解需要传入一个Conditon接口的实现类。
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
Conditon接口只有一个方法matches,只需要根据需要自定义mathces方法即可。了解下这两个参数
ConditionContext是个接口:
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
ClassLoader getClassLoader();
}
- 借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
- 借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
- 借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;常用
- 读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
- 借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata接口:
public interface AnnotatedTypeMetadata {
boolean isAnnotated(String var1);
@Nullable
Map<String, Object> getAnnotationAttributes(String var1);
@Nullable
Map<String, Object> getAnnotationAttributes(String var1, boolean var2);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2);
}
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解
所以要实现一个WindowsConditon来判断环境是不是Windows:
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Windows");
}
现在WindowsListService就只会在Windows环境中创建了。
Spring运行时值注入
1.使用Environment类获取外部值
存在一个Teacher类
public class Teacher {
private String name;
private String age;
public Teacher(String name, String age) {
this.name = name;
this.age = age;
}
void work(){
System.out.println(name + ":" +age);
}
}
存在配置文件config.properties内容如下:
theacher.age = 11
theacher.name = xxxx
如下配置便可获取配置文件中的值,以后只需修改配置文件即可
@Configuration
@PropertySource("classpath:config.properties")
public class DevConfig {
@Autowired
Environment env;
@Bean
public Teacher getTeacher(){
String name = env.getProperty("teacher.name");
String age = env.getProperty("teacher.age");
return new Teacher(name, age);
}
}
2.使用属性占位符
Spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到Spring bean中。在Spring装配中,占位符的形式为使用“${... }”包装的属性名称。
1.使用@Value注解,修改Teacher类如下;
@PropertySource("classpath:config.properties")
public class Teacher {
@Value("${teacher.name}")
private String name;
@Value("${teacher.age}")
private String age;
public void work(){
System.out.println(name+age);
}
}
引入PropertySourcePlaceholderConfigurer,会解析占位符。
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
2.在配置文件中引入properties文件:
<context:property-placeholder location="classpath:config.properties"/>
3.使用SpEL表达式
使用#{...}进行占位。