springboot 1.5.3 源码分析(三):spring @Conditional注解

欢迎关注本人公众号

在这里插入图片描述

springcloud系列学习笔记目录参见博主专栏 spring boot 2.X/spring cloud Greenwich。

书接上文。
前面讲了springboot的实现基础是spring的@Conditional注解。介绍原理前我们来看看怎么用。后面介绍其原理。

springboot 1.5.3 源码分析(三):spring @Conditional注解_第1张图片

我们实现这么一个小功能:根据不同的环境,实例化不同的bean
springboot通常都是通过-Dspring.profiles.active=dev来区分环境的,如果我们想实现线上的代码逻辑与开发或者测试环境不同,那么这是一个解决方案。

使用java的多态,先定义一个接口:

public interface EnvironmentService {

    void printEnvironment();
}

然后定义两个不同环境的实现类,内容比较简单,只是输出一句log。

@Slf4j
public class ProdService  implements EnvironmentService {

    @Override
    public void printEnvironment() {
        log.info("我是生产环境。");
    }
}
@Slf4j
public class DevService implements EnvironmentService{

    @Override
    public void printEnvironment() {
        log.info("我是开发环境。");
    }
}

好了,,准备工作已经做完,接下来编写配置类,使用spring的@Conditional注解来根据不同的环境配置加载不同的类:


import com.example.demo.environment.impl.DevService;
import com.example.demo.environment.impl.ProdService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * 根据不同的环境,初始化不同的bean
 */
@Configuration
@Slf4j
public class MySpringBootConfig {

    @ConditionalOnExpression("#{'${spring.profiles.active}'.equals('dev')}")//使用了spring的SPEL表达式
    @ConditionalOnClass(DevService.class)
    @Bean
    public DevService initDevService() {
        log.info("DevService已加载。");
        return new DevService();
    }

    @ConditionalOnExpression("#{'${spring.profiles.active}'.equals('prod')}")
    @ConditionalOnClass(ProdService.class)
    @Bean
    public ProdService initProdService() {
        log.info("ProdService已加载。");
        return new ProdService();
    }
}

condition有很多:
springboot 1.5.3 源码分析(三):spring @Conditional注解_第2张图片
这里我们使用的是@ConditionalOnExpression。它根据SPEL表达式返回的结果作为条件判断。
这里判断条件为:spring.profiles.active=dev时,创建DevService。为prod时,创建ProdService。

接下来看如何使用:


import com.example.demo.environment.EnvironmentService;
import com.example.myservice.HelloService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@SpringBootApplication
public class DemoApplication {

	@Autowired
	HelloService helloService;
	@Autowired
    EnvironmentService environmentService;

	@Value("#{'${spring.profiles.active}'.equals('dev')}")
	String springProfilesActive;

	@RequestMapping("/")
	public String word(String name) {
        environmentService.printEnvironment();
        log.info("springProfilesActive={}", springProfilesActive);
        return helloService.hello(name);
	}

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

很简单,只需要注入EnvironmentService即可。注意IDEA可能会提示错误,因为这个接口有两个实现类。不过不用去管他,因为我们通过condition只实例化了一个bean。
springboot 1.5.3 源码分析(三):spring @Conditional注解_第3张图片
运行结果:

我是开发环境。
springProfilesActive=true
initVal=hahaha, name=hhh

将配置修改为prod后:

我是生产环境。
springProfilesActive=true
initVal=hahaha, name=hhh

使用非常简单,那么@Conditional是怎么实现的呢?

官方文档的说明是**“只有当所有指定的条件都满足是,组件才可以注册”**。主要的用处是在创建bean时增加一系列限制条件。

类图:

核心类:

public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

所有的condition都是Condition接口的实现类,条件判断是通过matches方法返回的布尔值来判断的。

@ConditionalOnExpression注解为例:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {

	/**
	 * The SpEL expression to evaluate. Expression should return {@code true} if the
	 * condition passes or {@code false} if it fails.
	 * @return the SpEL expression
	 */
	String value() default "true";
}

ConditionalOnExpression注解上添加了另一个注解Conditional,指明是哪个condition类处理改注解。
我们打开OnExpressionCondition这个类:

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
			//获取到我们配置的EL表达式的值
		String expression = (String) metadata
				.getAnnotationAttributes(ConditionalOnExpression.class.getName())
				.get("value");
		expression = wrapIfNecessary(expression);
		String rawExpression = expression;
		expression = context.getEnvironment().resolvePlaceholders(expression);
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		BeanExpressionResolver resolver = (beanFactory != null)
				? beanFactory.getBeanExpressionResolver() : null;
		BeanExpressionContext expressionContext = (beanFactory != null)
				? new BeanExpressionContext(beanFactory, null) : null;
		if (resolver == null) {
			resolver = new StandardBeanExpressionResolver();
		}
		boolean result = (Boolean) resolver.evaluate(expression, expressionContext);
		return new ConditionOutcome(result, ConditionMessage
				.forCondition(ConditionalOnExpression.class, "(" + rawExpression + ")")
				.resultedIn(result));
	}

有这么一个方法,获取到我们设置的EL表达式的值后,进行一些处理,比如渠道environment中的值,然后计算整体的结果。
其中关键的是boolean result = (Boolean) resolver.evaluate(expression, expressionContext);这么一行代码,通过springEL计算最终结果。

至此,spring的condition算是介绍完了,我们可以通过实现org.springframework.context.annotation.Condition#matches自定义condition。

上述demo的另一种写法

实现不同环境加载不同的类,既可以使用ConditionalOnExpression也可以使用@Profile注解

两种都可以实现不同环境加载不同的类,写法不同,但是他们的实现原理都是一样的,都是获取环境变量spring.profiles.active的值,与value进行比较。读者可以去看一下ProfileCondition源码

    @Bean
    @Profile("dev")
    public DevService initDevService1() {
        log.info("DevService已加载---Profile");
        return new DevService();
    }

    @Bean
    @Profile("prod")
    public ProdService initProdService1() {
        log.info("ProdService已加载---Profile");
        return new ProdService();
    }

备注:核心注解

spring.factories文件里每一个xxxAutoConfiguration文件一般都会有下面的条件注解:

@ConditionalOnBean:当容器里有指定Bean的条件下

@ConditionalOnClass:当类路径下有指定类的条件下

@ConditionalOnExpression:基于SpEL表达式作为判断条件

@ConditionalOnJava:基于JV版本作为判断条件

@ConditionalOnJndi:在JNDI存在的条件下差在指定的位置

@ConditionalOnMissingBean:当容器里没有指定Bean的情况下

@ConditionalOnMissingClass:当类路径下没有指定类的条件下

@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下

@ConditionalOnProperty:指定的属性是否有指定的值

@ConditionalOnResource:类路径是否有指定的值

@ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean

@ConditionalOnWebApplication:当前项目是Web项目的条件下。

上面@ConditionalOnXXX都是组合@Conditional元注解,使用了不同的条件Condition

springcloud系列学习笔记目录参见博主专栏 spring boot 2.X/spring cloud Greenwich。

你可能感兴趣的:(spring,boot,2.X/spring,cloud,Greenwich)