通俗解释什么是条件装配
从 Spring Framework 3.1 开始,允许在 Bean 装配时增加前置条件判断。这种在Bean装配时增加判断条件的装配叫做条件装配。
实现方式
spring条件装配实现主要有两种实现方式,一种是配置化条件装配,使用@profile注解来实现。另一种比较灵活,底层实现Condition接口来进行实现。
Coding
- @Profile注解方式
需求:分别使用Java7和Java8方式来实现整数求和。
定义一个接口CalculateService:
package com.wangming.service;
public interface CalculateService {
int calculate(Integer ...arcgs);
}
分别使用java7和Java8两种方式实现该功能,注意两个实现类中的@Profile注解:
Java7CalculateServiceImpl
package com.wangming.service.impl;
import com.wangming.service.CalculateService;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
@Service
@Profile("Java7")
public class Java7CalculateServiceImpl implements CalculateService {
@Override
public int calculate(Integer... arcgs) {
System.out.println("java7实现");
int sum=0;
for (int i=0;i<arcgs.length;i++)
{
sum+=arcgs[i];
}
System.out.println("sum:"+sum);
return sum;
}
}
Java8CalculateServiceImpl:
package com.wangming.service.impl;
import com.wangming.service.CalculateService;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import java.util.stream.Stream;
@Service
@Profile("Java8")
public class Java8CalculateServiceImpl implements CalculateService {
@Override
public int calculate(Integer... arcgs) {
System.out.println("java8实现");
Integer sum = Stream.of(arcgs).reduce(0, Integer::sum);
System.out.println("sum:"+sum);
return sum;
}
}
引导类,CalculateBootstrap:
package com.wangming.bootstrap;
import com.wangming.service.CalculateService;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication(scanBasePackages = "com.wangming.service")
public class CalculateBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context =
new SpringApplicationBuilder(CalculateBootstrap.class)
.web(WebApplicationType.NONE)
.profiles("Java8")
.run(args);
CalculateService bean = context.getBean(CalculateService.class);
int calculate = bean.calculate(1, 2, 3);
// 关闭上下文
context.close();
}
}
我们来执行一下看下结果,结果如下:可以看到程序结果是符合预期的。
- Condition接口实现
在看这个接口之前我们来看下Profile注解方式是如何实现的呢?我们看下源码
Profile注解源码:
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
我们发现了这么一个注解@Conditional,见名知意,我们可以大胆推断配置注解的实现必然是在ProfileCondition这个类中进行了筛选,我们看下源码ProfileCondition:
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;
/**
* {@link Condition} that matches based on the value of a {@link Profile @Profile}
* annotation.
*
* @author Chris Beams
* @author Phillip Webb
* @author Juergen Hoeller
* @since 4.0
*/
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles((String[]) value)) {
return true;
}
}
return false;
}
return true;
}
}
经过debug,我们发现确实是在此处经过了筛选通过。
好了,我们回到正题,没错,condition接口方式的实现正是如此的原理。我们看一个springframework中一个包org.springframework.boot.autoconfigure.condition这是该包下的部分注解及其实现类。我们以@ConditionalOnProperty为突破口,来稍微了解一下springboot是如何做到条件装配的。
我们来模拟实现一个自己的@MyConditionalOnProperty:
package com.wangming.condition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({MyOnConditionProperty.class})
public @interface MyConditionOnProperty {
String prefix() default "";
}
MyOnConditionProperty实现类代码:
package com.wangming.condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
public class MyOnConditionProperty implements org.springframework.context.annotation.Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> annotationAttributes = metadata
.getAnnotationAttributes(MyConditionOnProperty.class.getName());
String prefix = String.valueOf(annotationAttributes.get("prefix"));
return prefix.equals("pass");
}
}
上述代码我们实现了一个怎样的功能呢?还是用示例来阐述,建一个引导类MyOnConditionPropertyBootstrap:
package com.wangming.bootstrap;
import com.wangming.condition.MyConditionOnProperty;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class MyOnConditionPropertyBootstrap {
@Bean(name = "hi")
@MyConditionOnProperty(prefix = "pass")
public String hello() {
return "hello";
}
public static void main(String[] args) {
ConfigurableApplicationContext context =
new SpringApplicationBuilder(MyOnConditionPropertyBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
String bean = context.getBean("hi", String.class);
// 关闭上下文
context.close();
}
}
我们在hello方法上打上我们自己的注解MyConditionOnProperty,并赋值prefix为"pass",这样我们在运行代码的时候,当从容器中获取该Bean的时候,会首先根据条件判断是否要装配这个Bean,此处的条件就是prefix="pass",因为我们在MyOnConditionProperty 实现类中做了筛选。当prefix的值不为"pass"的时候便不会装配该Bean。这种编程的方式实现条件装配,可以根据我们自己的业务需求来进行定制,因此比较灵活。