1. 基于环境配置 Profile
1.1 概念
地址:@Profile
Indicates that a component is eligible for registration when one or more specified profiles are active.
A profile is a named logical grouping that may be activated programmatically via ConfigurableEnvironment.setActiveProfiles(java.lang.String…) or declaratively by setting the spring.profiles.active property as a JVM system property, as an environment variable, or as a Servlet context parameter in web.xml for web applications. Profiles may also be activated declaratively in integration tests via the @ActiveProfiles annotation.
指示当一个或多个指定的概要文件处于活动状态时,组件有资格注册。
概要文件命名逻辑分组,可能被激活以编程方式通过ConfigurableEnvironment.setActiveProfiles声明方式(以…)或通过设置spring.profiles.active财产作为一个JVM系统属性,作为一个环境变量,或者作为web应用程序的web . xml中的Servlet上下文参数。概要文件也可以通过@ActiveProfiles注释在集成测试中声明性地激活。
其实听起来是有点蒙的,上文的意思就是说,当你用 @Profile 注解标注一个组件的时候,直到某个你配置好的属性把它激活了,这个属性可以通过(启动参数、环境、web.xml 等)
其实就可以理解为,根据运行时环境不同,动态的选择注册当前环境应该注入的组件。
1.2 使用
实体
public class PartA {
private String name;
public PartA(String name) {
this.name = name;
}
}
配置类
@Configuration
@Profile("dev")
public class MyModuleConfiguration {
@Bean
public PartA zhangsan() {
return new PartA("张三");
}
@Bean
public PartA lisi() {
return new PartA("李四");
}
}
测试类
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); Stream.of(context.getBeanDefinitionNames()).forEach(System.out::println);
}
}
输出
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
可以看到 @Profile(“dev”) 标注在配置类上,但是输出结果却没有对应的配置类和张三李四这两个 bean。
如果想在其中使用它,就必须在 ApplicationContext 中进行配置,即你想获取哪些环境下的类
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("dev");
context.register(MyModuleConfiguration.class);
context.refresh();
Stream.of(context.getBeanDefinitionNames()).forEach(System.out::println);
}
}
输出
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myModuleConfiguration
zhangsan
lisi
还有一种办法就是在 VM Options 中配置 -Dspring.profiles.active=dev
这样的字样,也能达到效果。
所以通过这个注解,我们就能实现不同环境下的独有配置效果了,例如配置常见的 dev、pre、prod 等等。
2. 条件装配 conditional
2.1 概念
上面你的 profile 更适合对整个项目的运行环境进行管理,但是不能根据具体的一个 Bean 的一些条件因素决定是否装配,这也就是条件装配的来由。
地址: @Conditional
Indicates that a component is only eligible for registration when all specified conditions match. A condition is any state that can be determined programmatically before the bean definition is due to be registered (see Condition for details). The @Conditional annotation may be used in any of the following ways:
- as a type-level annotation on any class directly or indirectly annotated with @Component, including @Configuration classes
- as a meta-annotation, for the purpose of composing custom stereotype annotations
- as a method-level annotation on any @Bean method
If a @Configuration class is marked with @Conditional, all of the @Bean methods, @Import annotations, and @ComponentScan annotations associated with that class will be subject to the conditions.
表示组件只有在所有指定条件都匹配的情况下才有资格注册。 条件是可以在注册bean定义之前以编程方式确定的任何状态(有关详细信息,请参阅condition)。 @Conditional注释可以通过以下任何一种方式使用: 作为任何直接或间接用@Component注释的类的类型级注释,包括@Configuration类 作为元注释,用于编写定制的原型注释 作为任何@Bean方法的方法级注释 如果@Configuration类被标记为@Conditional,那么与该类关联的所有@Bean方法、@Import注释和@ComponentScan注释都将受到这些条件的约束。
2.2 基本使用
先创建一些基本的铺垫
public class PartA {
private String name;
public PartA(String name) {
this.name = name;
}
}
public class PartB {
private String name;
public PartB(String name) {
this.name = name;
}
}
配置类的 Bean 实例化上增加 @Conditional
@Configuration
public class MyModuleConfiguration {
@Bean
public PartB lisi() {
return new PartB("李四");
}
@Bean
@Conditional(MyCondition.class)
public PartA zhangsan() {
return new PartA("张三");
}
}
可以看到 PartA 上面增加了条件配置的注解,而且指定了规则类,我们来实现一下这个规则类
- 其实前面几句都是我的测试代码,真正有用的只有 return 中依据,即比较当前的上下文工厂中是否已经存在了某个指定的内容。
大家可以注意到,我在上面的 MyModuleConfiguration 配置类中,特别把 lisi 这个 Bean 放在了前面,大家可以自己调整位置,看看是否有效。
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String[] beanDefinitionNames = context.getBeanFactory().getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
System.out.println(PartB.class.getName());
// 重点就这一句,上面都是测试
return Objects.requireNonNull(context.getBeanFactory()).containsBeanDefinition("lisi");
}
}
测试
public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyModuleConfiguration.class); Stream.of(context.getBeanDefinitionNames()).forEach(System.out::println); } }
输出
org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory myModuleConfiguration lisi zhangsan
2.3 通用匹配方式
如果一个组件依赖了很多个 Bean ,那么我们为每个条件都去写一个条件规则的类,这也太麻烦了,我们应该想想批量的方式
我们先自定义一个注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
Class<?>[] value() default {};
String[] beanNames() default {};
}
规则
public class OnBeanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnBean.class.getName());
// 匹配类型
assert attributes != null;
Class<?>[] classes = (Class<?>[]) attributes.get("value");
for (Class<?> clazz : classes) {
if (!Objects.requireNonNull(context.getBeanFactory()).containsBeanDefinition(clazz.getName())) {
return false;
}
}
// 匹配beanName
String[] beanNames = (String[]) attributes.get("beanNames");
for (String beanName : beanNames) {
if (!Objects.requireNonNull(context.getBeanFactory()).containsBeanDefinition(beanName)) {
return false;
}
}
return true;
}
}
配置类中就可以根据 beanNames 或者类的类型决定条件了。
@Configuration
public class MyModuleConfiguration {
@Bean
public PartB lisi() {
return new PartB("李四");
}
@Bean
@ConditionalOnBean(beanNames = "lisi")
public PartA zhangsan() {
return new PartA("张三");
}
}