走向自动装配|第三章-Spring Boot 条件装配

走向自动装配|第三章-Spring Boot 条件装配

文章目录

  • 走向自动装配|第三章-Spring Boot 条件装配
    • 文章说明
    • 1.条件注解
      • 1.1 基于配置方式实现 - @Profile
        • 1.1.1 代码演示
        • 1.1.2 源码分析
      • 1.2 编程方式 @Conditional
        • 1.2.1 源码分析
        • 1.2.2 自定义实现
    • 2.参考

文章说明

Spring Framework 到 Spring Boot 怎么一步一步走向自动装配

  • 第一章 - Spring Framework 手动装配
  • 第二章 - @Enable 模块驱动
  • 第三章 - Spring Boot 条件装配
  • 第四章 - Spring Boot 自动装配

项目环境

  • jdk 1.8
  • Spring Boot 2.0.2.RELEASE
  • github 地址:https://github.com/huajiexiewenfeng/deep-in-spring-boot
    • 本章模块:autoconfigure

1.条件注解

Spring 注解 场景说明 起始版本
@Profile 配置化条件装配 3.1
@Conditional 编程条件装配 4.0

1.1 基于配置方式实现 - @Profile

1.1.1 代码演示

假设需求如下:

计算数组中元素的和。

新建接口 CalculateService

/**
 * 计算服务
 */
public interface CalculateService {
    /**
     * 求和
     * @param numbers
     * @return
     */
    Integer sum(Integer... numbers);
}

Java7 实现

/**
 * Java 7 for 循环实现
 */
@Profile("Java7")
@Service
public class Java7CalulateImpl implements CalculateService {

    @Override
    public Integer sum(Integer... numbers) {
        System.out.println("Java7 实现");
        int sum = 0;
        for (int i = 0; i < numbers.length; i++) {
            sum += numbers[i];
        }
        return sum;
    }
}

Java8实现

/**
 * Java 8 Stream 实现
 */
@Profile("Java8")
@Service
public class Java8CalulateImpl implements CalculateService {

    @Override
    public Integer sum(Integer... numbers) {
        System.out.println("Java8 实现");
        return Stream.of(numbers).collect(summingInt(Integer::intValue));
    }

}

测试引导类

  • 使用 profiles(“Java8”) 可以设置 profiles 属性
@SpringBootApplication(scanBasePackages = "com.huajie.deepinspringboot.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);
        System.out.println(bean);
        System.out.println("sum(1...10) result:"+bean.sum(1,2,3,4,5,6,7,8,9,10));
        context.close();
    }

}

执行结果:

com.huajie.deepinspringboot.service.Java8CalulateImpl@7882c44a
Java8 实现
sum(1...10) result:55

1.1.2 源码分析

Spring 4 之后,@Profile 注解的实现方式发生了变化,使用 Condition 进行实现

@Profile 注解源码:

  • 通过 @Conditional(ProfileCondition.class)
    • org.springframework.context.annotation.ProfileCondition
@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();

}

org.springframework.context.annotation.ProfileCondition

  • 实现的方式也很简单
    • 从 metadata 获取注解上的信息
    • 遍历获取属性为 value 的信息
    • context.getEnvironment() 获取当前的属性源信息是否和注解配置的 value 信息一致
    • 返回 true 表示 Spring IoC 容器会加载这个 Bean 的相关配置元信息,false 则不会加载
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;
	}

}

1.2 编程方式 @Conditional

1.2.1 源码分析

@ConditionalOnProperty

  • 看名称的意思是判断当前属性条件是否符合
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
    ...

OnPropertyCondition 继承了 SpringBootCondition,而 SpringBootCondition 实现了 Condition 接口,Spring Boot 对 Condition 做了一定的封装,子类只需要实现 getMatchOutcome 的抽象方法即可,完成条件的判断逻辑。

OnPropertyCondition#getMatchOutcome 源码,逻辑大致如下

  • 获取注解中配置的信息
  • 与 context.getEnvironment() 当前上下文中的配置属性源信息进行对比
  • 如果匹配失败,则返回失败的 ConditionOutcome 对象
  • 如果匹配成功,则返回成功的 ConditionOutcome 对象
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
				metadata.getAllAnnotationAttributes(
						ConditionalOnProperty.class.getName()));
		List<ConditionMessage> noMatch = new ArrayList<>();
		List<ConditionMessage> match = new ArrayList<>();
		for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
			ConditionOutcome outcome = determineOutcome(annotationAttributes,
					context.getEnvironment());
			(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
		}
		if (!noMatch.isEmpty()) {
			return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
		}
		return ConditionOutcome.match(ConditionMessage.of(match));
	}

1.2.2 自定义实现

我们仿照 @ConditionalOnProperty 的方式自己来实现一个

基于编程方式实现 - @ConditionalOnSystemProperty

新建 OnSystemPropertyCondition 实现 Condition 接口

  • 获取注解元信息与系统属性进行对比,如果想则返回 true
public class OnSystemPropertyCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
        //取到配置的值
        String propertyName = String.valueOf(annotationAttributes.get("name"));
        String propertyValue = String.valueOf(annotationAttributes.get("value"));
        //系统值
        String systemPropertyValue = System.getProperty(propertyName);
        //是否相同
        if (ObjectUtils.nullSafeEquals(propertyValue, systemPropertyValue)) {
            return true;
        }
        return false;

    }

}

自定义注解 @ConditionalOnSystemProperty

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
    /**
     * Java 系统属性名称
     *
     * @return
     */
    String name();

    /**
     * Java 系统属性值
     *
     * @return
     */
    String value();
}

测试引导类

@SpringBootApplication
public class ConditionalOnSystemPropertyBootstrap {

    @Bean
    @ConditionalOnSystemProperty(name = "usr.name", value = "小仙")
    public String helloWorld() {
        return "hello,world";
    }

    static {
        System.setProperty("usr.name", "小仙");
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(ConditionalOnSystemPropertyBootstrap.class)
                .web(WebApplicationType.NONE)
                .run(args);
        String bean = context.getBean("helloWorld", String.class);
        System.out.println("helloWorld bean : " + bean);

        context.close();
    }
}

执行结果:

helloWorld bean : hello world

如果系统属性设置为 System.setProperty("usr.name", "小x"); 或者其他信息

执行结果:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'helloWorld' available
...

当前上下文中没有这个 Bean 的定义。

2.参考

  • 慕课网-小马哥《Spring Boot2.0深度实践之核心技术篇》

你可能感兴趣的:(Spring,Boot,编程思想)