学习笔记是学习了 慕课网小马哥的 《Spring Boot 2.0深度实践之核心技术篇》根据自己的需要和理解做的笔记。
Spring 条件装配
从 Spring Framework 3.1 开始,允许在 Bean 装配时增加前置条件判断。 条件注解有 @Profile
@Conditional
两种。
@Profile
配置化条件装配, 在3.2以后的版本支持方法级别和类级别,3.1版本只支持类级别。
应用场景:一般用于生产环境和开发环境之间的切换。简单的来说 就是存在类或者方法上添加@Profile
注释并且添加环境配置标识比如 “java7”,"java8"。我们会以一个简单的多整数求和示例来演示如何使用。
1.首先我们先来定义一个计算接口 CalculateService
/**
* 计算服务接口
*/
public interface CalculateService {
/**
* 求和
* @param values 多个整数
* @return sum 累加值
*/
Integer sum(Integer... values);
}
2.然后分别实现一个JDK7的求和方法 和一个JDK8的求和方法。
JDK7求和类
/**
* Java7 for 循环实现
*
*/
@Profile("java7")
@Service
public class Java7CalculateService implements CalculateService {
@Override
public Integer sum(Integer... values) {
System.out.println("使用 Java7CalculateService 开始计算");
int sum = 0;
for (int i = 0; i < values.length; i++) {
sum+= values[i];
}
return sum;
}
}
JDK8 求和类
/**
* java8 lambda
*/
@Profile("java8")
@Service
public class Java8CalculateService implements CalculateService {
@Override
public Integer sum(Integer... values) {
System.out.println("使用 Java7CalculateService 开始计算");
int sum = Stream.of(values).reduce(0,Integer::sum);
return sum;
}
}
我们看到 在类上都添加了@Profile
注释,并且里面的签名是不同的,那么我们现在就来看一下,如何使用来将 这两种计算服务分开来。
3.创建引导类
/**
* 引导类
*/
@SpringBootApplication(scanBasePackages = "neal.springframework.annotation.profile.service")
public class CalculateBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(CalculateBootstrap.class)
.web(WebApplicationType.NONE)
.profiles("java8")
.run(args);
// helloWorld Bean 是否存在
CalculateService calculateService =
context.getBean(CalculateService.class);
System.out.println("计算1~10相加: " + calculateService.sum(1,2,3,4,5,6,7,8,9,10));
// 关闭上下文
context.close();
}
}
我们使用启动容器,在启动容器的时候 使用 .profiles
方法 来设置使用何种计算方式来计算。
Demo代码地址
@Conditional
编程条件装配 ,官方文档的说明是“只有当所有指定的条件都满足是,组件才可以注册”。主要的用处是在创建bean时增加一系列限制条件。 spring framework 4.0 之后才有的方法。
可以使用的范围
- 类型级别,可以在@Component 或是 @Configuration类上使用
- 原型级别,可以用在其他自定义的注解上
- 方法级别,可以用在@Bean的方法上
在使用@Conditional
时,进行条件判断的类必须实现 org.springframework.context.annotation.Condition
接口。
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
现在就让我们自定义一个 条件装配,来理解@Conditional
的用法。
1.创建一个 判断java系统属性的 注解 ConditionalOnSystemProperty
/**
* java系统属性 条件判断
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnSystemCondition.class)
public @interface ConditionalOnSystemProperty {
//系统属性名
String name();
//系统属性值
String value();
}
在这个注解里,我们定义了两个签名 name
和 value
。name
用来 表示系统属性名, value
用来表示希望的系统属性值。 在@Conditional
中 引入条件判断类 OnSystemCondition
。现在不理解这个注解没关系,我们慢慢往下看.
2.创建 条件判断类 OnSystemCondition
/**
* 系统属性条件判断
*/
public class OnSystemCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
/**
* 获取 {@link ConditionalOnSystemProperty} 的属性集合
*/
Map<String,Object> attributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
//系统属性名称
String property = String.valueOf(attributes.get("name"));
//期望的对应的系统属性值
String propertyValue = String.valueOf(attributes.get("value"));
//获取系统属性值
String javaPropertyValue = System.getProperty(property);
System.out.println("当前java系统属性" + property + "的值为 :" + javaPropertyValue);
boolean flag = javaPropertyValue.equals(propertyValue);
if(flag) {
System.out.println("当前java系统属性与@ConditionalOnSystemProperty 中value的值相等,加载bean");
}else{
System.out.println("当前java系统属性与@ConditionalOnSystemProperty 中value的值不等,拒绝加载bean");
}
//判断期望值和对应的系统属性值 是否一致
return flag;
}
}
我们使用 Condition
接口方法中的 AnnotatedTypeMetadata
类型参数来获取 @ConditionalOnSystemProperty
相关的属性集合,从而进一步获取具体属性值。
3.创建引导类
/**
* 引导类
*/
public class ConditionBootstrap {
/**
* @Conditional 可以用于方法级别上
* 如果 java系统属性 user.name 和Neal 相等,则加载helloWorld
* @return helloWolrd bean
*/
@Bean
@ConditionalOnSystemProperty(name="user.name",value = "Neal")
public String helloWorld() {
return "Hello World ";
}
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(ConditionBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// helloWorld Bean 是否存在
String helloWorld =
context.getBean("helloWorld", String.class);
System.out.println("helloWorld Bean : " + helloWorld);
// 关闭上下文
context.close();
}
}
在引导类中,我们声明了一个 名为 helloWorld
的bean ,并在这个方法上添加了条件装配注释。如果满足条件,那么我们就可以在Spring容器中获取到 helloWorld
。
我们来跑一下程序查看一下结果
我们可以看到 Java 系统的属性名和条件装配中 value 的值相等,所以加载成功。
那么我们把ConditionBootstrap
中的 @ConditionalOnSystemProperty(name="user.name",value = "Neal")
改为 @ConditionalOnSystemProperty(name="user.name",value = "hello")
我们再跑一下程序。查看工作台输出结果.
我们可以看到 Java 系统的属性名和条件装配中 value 的值不等,所以无法加载helloWorld
因此抛出了异常。
这两个条件装配注释,在开发中可以给我们更灵活的spring配置。我本人也希望在今后的开发中会使用到这些灵活又方便的注释。