上一篇文章我们讲了模块装配,接下来我们讲述一下条件装配
什么是条件装配?
Bean装配的前置判断
有哪些例子?
@Profile(配置化条件装配) @Conditional(编程条件装配)
有哪些实现方式?
注解方式,接口方式
@Profile在Spring3.0时代出现的,使用方式就是在类上面加上@Profile(XXX),如果我们在SpringBoot启动的时候使用了.profiles("XXX"),那么这个类就会被装配进来。在4.0时代,发生了变化,Profile有了@Conditional这个注解。
上面讲的很抽象,我们还是用具体的例子来帮助大家理解一下:
首先看看@Profile的使用:
我们假设有这么一个场景,声明一个接口CalculateService,这个接口中只有一个方法sum(),如下:
public interface CalculateService {
/**
* 从多个整数 sum 求和
* @param values 多个整数
* @return sum 累加值
*/
Integer sum(Integer... values);
}
如果profile为java8时,我们采用lambda表达式来实现这个接口,并调用这个实现类的sum方法求和,如果profile为java7是,我们采用传统的方式来实现这个接口,并调用这个实现类的sum方法求和。
我们先看看Java7CalculateService的实现
@Profile("Java7")
@Service
public class Java7CalculateService implements CalculateService {
@Override
public Integer sum(Integer... values) {
System.out.println("Java 7 for 循环实现 ");
int sum = 0;
for (int i = 0; i < values.length; i++) {
sum += values[i];
}
return sum;
}
}
这个实现类有个注解@Profile("java7"),表示当我们的spring应用启动的时候指明了profile是java7才会生效。
我们再看看Java8CalculateService的实现
@Profile("Java8")
@Service
public class Java8CalculateService implements CalculateService {
@Override
public Integer sum(Integer... values) {
System.out.println("Java 8 Lambda 实现");
int sum = Stream.of(values).reduce(0, Integer::sum);
return sum;
}
}
达到的效果和上面很像。对lambda不熟悉的同学可能比较疑惑Integer::sum还有reduce是什么意思,我还是解答一下:
Stream.of是把传入的数组转换成流,reduce是对这个流进行相关计算,这个函数接受两个参数,一个初始值identity和一个accumulator函数,啥意思呢,就是以下这个意思:
* <pre>{@code
* T result = identity;
* for (T element : this stream)
* result = accumulator.apply(result, element)
* return result;
* }</pre>
这里的accumulator函数式Integer类在java8中提供的一个新的函数,而且是一个二元函数哦,具体实现看下面:
/**
* Adds two integers together as per the + operator.
*
* @param a the first operand
* @param b the second operand
* @return the sum of {@code a} and {@code b}
* @see java.util.function.BinaryOperator
* @since 1.8
*/
public static int sum(int a, int b) {
return a + b;
}
回归正题,我们定义好这两个实现类后,那应该怎么去使用呢?
@SpringBootApplication(scanBasePackages = "com.imooc.diveinspringboot.service")
public class CalculateServiceBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(CalculateServiceBootstrap.class)
.web(WebApplicationType.NONE)
.profiles("Java8")
.run(args);
// CalculateService Bean 是否存在
CalculateService calculateService = context.getBean(CalculateService.class);
System.out.println("calculateService.sum(1...10) : " +
calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// 关闭上下文
context.close();
}
}
重点看下里面.profiles这个方法,还有注解的scanBasePackages,这个就保证了应用获得的究竟是哪个实现类的Bean。好了Profile的原理和使用方式我们讲到这里,接下来我们讲一讲Condition:
在SprintBoot的spring-boot-autoconfigure.data.elasticsearch这个jar包里面,可以看到很多的装配类,比如ElasticsearchAutoConfiguration 这个类,这个类上面使用了@ConditionalOnProperty(prefix = "spring.data.elasticsearch", name = "cluster-nodes", matchIfMissing = false),这个ConditionalOnProperty包含了@Conditional(OnPropertyCondition.class),这个OnPropertyCondition里面就是对@ConditionalOnProperty里面的内容进行解析,看是否能匹配,如果匹配成功了,才进行下一步装配操作。那么我们可以用自己的方式来模拟实现一下这个ConditionalOnProperty,然后真正的理解它。
我们这里实现这样一个场景,如果我的系统环境变量的用户名字和注解上用户提供的名字一样,那么我们就装配一个String类型的Bean,否则不装配。
首先定义一个引导类:
public class ConditionalOnSystemPropertyBootstrap {
@Bean
@ConditionalOnSystemProperty(name = "user.name", value = "天帝")
public String helloWorld() {
return "Hello,World 光贤";
}
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(ConditionalOnSystemPropertyBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// 通过名称和类型获取 helloWorld Bean
String helloWorld = context.getBean("helloWorld", String.class);
System.out.println("helloWorld Bean : " + helloWorld);
// 关闭上下文
context.close();
}
}
我们看到helloWorld方法上面有一个 @ConditionalOnSystemProperty(name = "user.name", value = "天帝"),这个注解的作用是为了说如果系统环境变量中的user.name是天帝,那么久装配一个String类型的Bean。
我们看一下我们的ConditionalOnSystemProperty的实现:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
/**
* Java 系统属性名称
* @return
*/
String name();
/**
* Java 系统属性值
* @return
*/
String value();
}
这里最关键的还是它上面的@Conditional(OnSystemPropertyCondition.class),好了,我们直接进入到OnSystemPropertyCondition看看它到底做了什么:
public class OnSystemPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
String propertyName = String.valueOf(attributes.get("name"));
String propertyValue = String.valueOf(attributes.get("value"));
String javaPropertyValue = System.getProperty(propertyName);
System.out.println("-------"+javaPropertyValue+"-----------------");
return propertyValue.equals(javaPropertyValue);
}
}
这里的OnSystemPropertyCondition必须要实现Condition这个接口的matches方法,这个方法可以获取到我们注解中设置的值,然后与系统环境变量中的值比对,如果比对一样,那么返回true,表示我们的Conditional的条件成立,这个String类型的bean就可以装配到我们的容器中去了。这就是为什么我们在SpringBoot中的application.propertis里面写了一些配置项后,会发现应用给我们自动装配一些Bean,其中的原理就是这样的。