《SpringBoot系列十五》源码+案例分析条件装配时多个Condition执行的顺序是什么样的?可以配置优先级吗?

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 《SpringBoot系列十五》源码+案例分析条件装配时多个Condition执行的顺序是什么样的?可以配置优先级吗?

@[TOC]

一、前言

针对条件装配我们讨论了如下内容:

  1. 《SpringBoot系列十一》:精讲如何使用@Conditional系列注解做条件装配
  2. 《SpringBoot系列十二》:如何自定义条件装配(由@ConditionalOnClass推导)
  3. 《SpringBoot启动流程六》:SpringBoot自动装配时做条件装配的原理(万字图文源码分析)(含@ConditionalOnClass原理)
  4. 《SpringBoot系列十三》:图文精讲@Conditional条件装配实现原理
  5. 《SpringBoot系列十四》:@ConditionalOnBean、@ConditionalOnMissingBean注解居然失效了!

本文我们接着讨论多个Condition的执行顺序。

二、多个Condition的排序

在博文<《SpringBoot系列十三》:图文精讲@Conditional条件装配实现原理>中,我们讨论了Condition条件装配的原理,其中介绍了条件装配如何执行?入口为:ConditionEvaluator#shouldSkip(AnnotatedTypeMetadata,ConfigurationPhase)方法,其返回值为boolean类型,方法返回true表示当前类应该被过滤掉(即不符合条件装配的规则)、否则表示当前类应该被留下(即符合条件装配的规则)。

/**
 * Determine if an item should be skipped based on {@code @Conditional} annotations.
 * @param metadata the meta data
 * @param phase the phase of the call
 * @return if the item should be skipped
 */
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    // 如果类没被@Conditional衍生注解标注,则直接返回FALSE,表示当前类不应该被过滤掉
    if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
        return false;
    }

    // 如果没设置条件装配的阶段,当类是配置类时,为 PARSE_CONFIGURATION 阶段,否则默认为 REGISTER_BEAN 阶段
    if (phase == null) {
        if (metadata instanceof AnnotationMetadata &&
                ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
            return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
        }
        return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
    }

    // 获取类上所有的@Conditional 子注解,返回@Conditional注解中的value值
    List<Condition> conditions = new ArrayList<>();
    for (String[] conditionClasses : getConditionClasses(metadata)) {
        for (String conditionClass : conditionClasses) {
            Condition condition = getCondition(conditionClass, this.context.getClassLoader());
            conditions.add(condition);
        }
    }

    // 对获取到的所有Condition接口的实现类进行排序
    AnnotationAwareOrderComparator.sort(conditions);

    // 遍历所有的Condition,进行match
    for (Condition condition : conditions) {
        ConfigurationPhase requiredPhase = null;
        if (condition instanceof ConfigurationCondition) {
            // 获取当前Condition的执行阶段
            requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
        }
        // 如果入参传入的阶段和Condition的阶段不同,直接返回FALSE。
        // 如果阶段相同 或 Condition的阶段为null,再使用Condition#matches(this.context, metadata)做真正的条件装配逻辑,不符合则返回TRUE。
        if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
            return true;
        }
    }

    return false;
}

shouldSkip()方法执行的逻辑概括如下:

  1. 首先,如果入参类 为 null 或者 没有被@Conditional衍生注解标注,则直接返回FALSE,表示当前类不应该被过滤掉;
  2. 如果入参没有传条件装配的阶段;当类是配置类时,为 PARSE_CONFIGURATION 阶段,否则默认为 REGISTER_BEAN 阶段;
  3. 接着,获取类上所有的@Conditional 子注解,返回@Conditional注解中的value值。并对获取到的所有Condition接口的实现类进行排序。
  4. 遍历所有的Condition,进行匹配;如果入参传入的阶段和Condition的阶段不同,直接返回FALSE。如果阶段相同 或 Condition的阶段为null,再使用Condition#matches(this.context, metadata)做真正的条件装配逻辑,不符合则返回TRUE。

本文要讨论的多个Condition执行的顺序,就体现在AnnotationAwareOrderComparator.sort(conditions);方法。

1、对多个Condition排序

在博文《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息我们有讨论过对所有自动装配类的排序,其实和这里是一样的,本文对其进行更细粒度的讨论;

进入到AnnotationAwareOrderComparator.sort(conditions)方法中;

AnnotationAwareOrderComparator.sort(conditions);

sort()方法中直接使用List集合的sort()方法,但需要自定义Comparator为当前类实例AnnotationAwareOrderComparator
在这里插入图片描述
再看AnnotationAwareOrderComparator的类结构:
在这里插入图片描述
AnnotationAwareOrderComparator继承自OrderComparator,自定义Comparator需要实现Comparator的抽象方法compare(T o1, T o2)。由于AnnotationAwareOrderComparator自身没有compare()方法,所以看其父类OrderComparator中的compare(Object o1, Object o2)方法;

@Override
public int compare(@Nullable Object o1, @Nullable Object o2) {
    return doCompare(o1, o2, null);
}

private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
    // 判断o1是否实现PriorityOrdered接口
    boolean p1 = (o1 instanceof PriorityOrdered);
    // 判断o2是否实现PriorityOrdered接口
    boolean p2 = (o2 instanceof PriorityOrdered);
    // 如果o1实现了PriorityOrdered接口 而 o2没实现,则按 o1 在 o2之前的顺序排序(从集合中的排序来看就是交换o1 和 o2的位置)
    if (p1 && !p2) {
        return -1;
    }
    // 如果o2实现了PriorityOrdered接口 而 o1没实现,则按 o2在o1之前的顺序排序(从集合中的排序来看o2和o1的位置不变)
    else if (p2 && !p1) {
        return 1;
    }

    // 如果o2和o1都实现了 或 都没实现PriorityOrdered接口,则比对两者的顺序值。
    int i1 = getOrder(o1, sourceProvider);
    int i2 = getOrder(o2, sourceProvider);
    return Integer.compare(i1, i2);
}

方法逻辑如下:

  1. 本来的集合中的顺序是o2,o1;而入参参数的先后是顺序o1,o2,如果保持(o2 < o1)的顺序就返回1 或 0,交换o2和o1顺序就返回-1,0的含义是在两个元素相同时,不交换顺序(为了排序算法的稳定性,可以使用1来代替0,不要用-1来代替0)。即:想要升序,返回-1;降序返回1。详情见Arrays.sort()方法源码
  2. 实现PriorityOrdered接口的放前面,如果都实现了PriorityOrdered接口 或 都没实现,则根据获取到的顺序值对比排序。

因为OrderComparator#doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider)方法中的入参sourceProvider为null,所以进入到getOrder()方法时,后续直接调用子类的findOrder(Object obj)方法去查找相应类的顺序值。
在这里插入图片描述

1)AnnotationAwareOrderComparator#findOrder()方法:

@Override
@Nullable
protected Integer findOrder(Object obj) {
    Integer order = super.findOrder(obj);
    if (order != null) {
        return order;
    }
    return findOrderFromAnnotation(obj);
}

先调用父类OrderComparator#findOrder()方法,如果找到顺序值直接返回,否者从类的@Order注解中取到顺序值。下面我们分别看OrderComparator#findOrder()方法和AnnotationAwareOrderComparator#findOrderFromAnnotation()方法。

1> OrderComparator#findOrder()方法:

@Nullable
protected Integer findOrder(Object obj) {
    return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null);
}

该方法判断obj有没有实现Ordered接口,实现Ordered接口之后,有没有重写其getOrder()方法,如果重写了,则直接从getOrder()中获取到序列值;否则返回null;

往上返,回到OrderComparator#findOrder()方法中;如果通过OrderComparator#findOrder()获取不到顺序值,即返回null,AnnotationAwareOrderComparator会再通过findOrderFromAnnotation()方法从@Order注解中获取顺序值。

2> AnnotationAwareOrderComparator#findOrderFromAnnotation()方法:

@Nullable
private Integer findOrderFromAnnotation(Object obj) {
    AnnotatedElement element = (obj instanceof AnnotatedElement ? (AnnotatedElement) obj : obj.getClass());
    MergedAnnotations annotations = MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY);
    Integer order = OrderUtils.getOrderFromAnnotations(element, annotations);
    if (order == null && obj instanceof DecoratingProxy) {
        return findOrderFromAnnotation(((DecoratingProxy) obj).getDecoratedClass());
    }
    return order;
}

findOrderFromAnnotation()方法中通过OrderUtils.getOrderFromAnnotations(element, annotations);获取@Order注解中的值;
在这里插入图片描述

3> 进入OrderUtils#getOrderFromAnnotations()方法:

@Nullable
static Integer getOrderFromAnnotations(AnnotatedElement element, MergedAnnotations annotations) {
    // 这里默认不会走
    if (!(element instanceof Class)) {
        return findOrder(annotations);
    }
    // 从缓存中获取,默认为null
    Object cached = orderCache.get(element);
    if (cached != null) {
        return (cached instanceof Integer ? (Integer) cached : null);
    }
    // 查找类上是否有@Order注解,如果有,返回@Order注解中的Value值,否者返回null;
    Integer result = findOrder(annotations);
    orderCache.put(element, result != null ? result : NOT_ANNOTATED);
    return result;
}

方法逻辑:

  1. 首先从缓冲中获取顺序值,默认为null,即获取不到。
  2. 接着通过findOrder(MergedAnnotations)方法查找类上是否有@Order注解,如果没有返回null,否则返回@Order注解中的Value值;
  3. findOrder(MergedAnnotations)方法执行完之后,将查询到的@Order中的value值写入到缓存orderCache中。

4> 进入OrderUtils#findOrder()方法:

@Nullable
private static Integer findOrder(MergedAnnotations annotations) {
    // 获取到类上的@Order注解
    MergedAnnotation<Order> orderAnnotation = annotations.get(Order.class);
    // 如果@Order注解存在,则返回@Order注解的Value值
    if (orderAnnotation.isPresent()) {
        return orderAnnotation.getInt(MergedAnnotation.VALUE);
    }
    // 如果@Order注解不存在,继续判断类上是否有`javax.annotation.Priority`注解
    MergedAnnotation<?> priorityAnnotation = annotations.get(JAVAX_PRIORITY_ANNOTATION);
    if (priorityAnnotation.isPresent()) {
        return priorityAnnotation.getInt(MergedAnnotation.VALUE);
    }
    // 如果两个注解都没有,则返回null。
    return null;
}

最后返回到OrderComparator#getOrder()方法,如果通过子类AnnotationAwareOrderComparator的findOrder(Object)方法获取到的order为null,则此处将Order设置为Integer.MAX_VALUE
在这里插入图片描述
<br/>
注意:如果两个对象的Order顺序值一样,则按原本在集合中的顺序先后排列
<br/>

2)List集合中的顺序是怎样的?

private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
    // 获取类上所有@Conditional注解中的全部Condition
    MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
    Object values = (attributes != null ? attributes.get("value") : null);
    return (List<String[]>) (values != null ? values : Collections.emptyList());
}

通过ConditionEvaluator#getConditionClasses(AnnotatedTypeMetadata)方法获取类上所有@Conditional注解中的全部Condition,获取次序为:类上的注解从上至下、@Conditional中的Condition集合从左到右。

在这里插入图片描述
下面我们用案例来例证这个结论。

3)排序总述

首先通过通过ConditionEvaluator#getConditionClasses(AnnotatedTypeMetadata)方法获取类上所有@Conditional注解中的全部Condition,获取次序为:类上的注解从上至下、@Conditional中的Condition集合从左到右。,返回一个List<Condition>集合。

然后利用List集合自身的排序,通过传入自定义的Comparator 实现排序规则。具体规则如下:

自定义Comparator需要实现compare(Object o1, Object o2)方法,AnnotationAwareOrderComparator类继承自OrderComparator,由于AnnotationAwareOrderComparator没有重写compare(Object o1, Object o2)方法,所以排序规则体现在OrderComparator的compare(Object o1, Object o2)方法中,进而进入到其doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider)方法中;

  1. 比如:有两个需要被排序的对象o2, o1,它在原List集合中的位置顺序为o2, o1。
  2. 首先进行PriorityOrdered判断:

    • 如果o1对象实现了PriorityOrdered接口,o2对象没实现,返回-1,即交换o2 和 o1的位置,将o1 放在o2前面;
    • 如果o2对象实现了PriorityOrdered接口,o1对象没实现,返回1,即o2 和 o1的位置保持不变,o2 仍在o1前面;
    • 否则,如果o1 和 o2对象同时实现了PriorityOrdered接口 或 都没实现,则通过OrderComparator#getOrder()方法分别获取到 o2 和 o1对象的顺序值;
  3. OrderComparator#getOrder(Object, OrderSourceProvider)方法中:

    1. 首先通过OrderComparator#findOrder()方法查找顺序值,而AnnotationAwareOrderComparator重写了findOrder()方法,所以直接进入到AnnotationAwareOrderComparator#findOrder()方法。
    2. 在AnnotationAwareOrderComparator#findOrder()方法中,会先调用其父类OrderComparator#findOrder()方法判断对象是否实现Ordered接口 并 重写了getOrder()方法,有则返回getOrder()方法的返回值。
    3. 否则,通过findOrderFromAnnotation()方法从注解中获取顺序值(优先走orderCache缓存获取,获取不到才会进行如下逻辑,走如下逻辑获取到之后,再放去orderCache缓存中):
       1> 首先从对象的@Order注解中获取其value值,有则会返回;
       2> 否者从对象的`javax.annotation.Priority`注解中获取其Value值,有则返回,
       3> 否者,返回null。
  4. 回到OrderComparator#getOrder()层面,如果findOrder()返回了具体的Integer值,则返回,否者返回Integer.MAX_VALUE。
  5. 最后,回到OrderComparator#doCompare()方法,返回Integer.compare(i1, i2)的结果,按顺序值的升序排序(即:顺序值小的在前面、大的在后面)

2、Condition排序案例

从上面的讨论我们可以得知排序规则:如果Condition实现了PriorityOrdered接口,则放在最前面一部分;接着依次通过 <实现Ordered接口的getOrder()方法返回顺序值>、<从@Order注解中的Value值获取到顺序值返回> 、<从javax.annotation.Priority注解中的Value值获取到顺序值返回>,如果通过上面三种方式都没有获取到顺序值,则返回Integer.MAX_VALUE

1)代码目录

自定义Condition参考博文:《SpringBoot系列十二》:如何自定义条件装配

整体代码目录结构如下:
在这里插入图片描述

1> @ConditionalOnSystemProperty

package com.saint.autoconfigure.condition;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

/**
 * 系统属性名称与属性值匹配条件注解
 *
 * @author Saint
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnSystemPropertyCondition.class, MyCondition.class})
public @interface ConditionalOnSystemProperty {

    /**
     * 属性名
     *
     * @return
     */
    String name();

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

2> MyCondition

package com.saint.autoconfigure.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 *
 * @author Saint
 */
public class MyCondition implements Condition, PriorityOrdered {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return true;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

3> OnSystemPropertyCondition

package com.saint.autoconfigure.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;

import java.util.Objects;

/**
 * 系统属性值与值匹配条件
 *
 * @author Saint
 */
public class OnSystemPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attributes =
                metadata.getAllAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
        String name = (String) attributes.getFirst("name");
        String value = (String) attributes.getFirst("value");
        String systemPropertyValue = System.getProperty(name);
        // 比较系统属性值和方法的值
        if (Objects.equals(systemPropertyValue, value)) {
            System.out.printf("系统属性【名称: %s】找到匹配值:%s \n", name, value);
            return true;
        }
        return false;
    }
}

4> ClassX

package com.saint;

import com.saint.autoconfigure.condition.ConditionalOnSystemProperty;
import com.saint.service.TestService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;

/**
 * @author Saint
 */
@Configuration
@ConditionalOnClass(ClassY.class)
@ConditionalOnBean(TestService.class)
@ConditionalOnSystemProperty(name = "language", value = "Chinese")
public class ClassX {

    public ClassX() {
        System.out.println("初始化了ClassX!");
    }
}

5> ClassY

package com.saint;

/**
 * @author Saint
 */
public class ClassY {

    public ClassY() {
        System.out.println("初始化了ClassY!");
    }
}

6> 启动类

package com.saint;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
//@Import(TestImportClassA.class)
public class SaintSpringbootApplication {

    public static void main(String[] args) {
        // 设置language属性值
        System.setProperty("language", "Chinese");

        ConfigurableApplicationContext context = SpringApplication.run(SaintSpringbootApplication.class, args);

        // 从Spring上下文中获取beanName为message的类
        String message = context.getBean("message", String.class);
        System.out.println("当前message类型为:" + message);
    }

}

2)Condition的顺序

以上面自定义的ClassX为例:
在这里插入图片描述
其获取到的Condition的顺序为:OnClassCondition、OnBeanCondition、OnSystemPropertyCondition、MyCondition,这个排序的规则,正如我们上面所说:获取次序为:类上的注解从上至下、@Conditional中的Condition集合从左到右。

再看排序后的结果:
在这里插入图片描述
由于只有MyCondition实现了PriorityOrdered接口,所以它在最前面,另外三个的顺序值都是一次按照顺序值大小排序。

三、总结

从自动装配类的条件装配来看,一般选择将Class Conditions类注解(@ConditionalOnClass、@ConditionalOnMissingClass) 放在最前面、Property Conditions类注解(@ConditionalOnProperty)其次、Bean Conditions类注解(@ConditionalOnBean、@ConditionalOnMissingBean)放最后。当然也还有其他种类的条件注解,一般常用的是这三种。
在这里插入图片描述
再结合配置类加载的两个阶段(配置类解析、注册Bean<见博文:《SpringBoot系列十三》:图文精讲@Conditional条件装配实现原理>)来看:

1> 自动装配类:

  • 自动装配类在配置类解析阶段会对Bean Conditions之外的条件注解做判断;而Bean Condtions类注解需要到注册bean阶段才会生效。

2> @Component标注的类:

  • 同样的@Component衍生注解标注的类 在配置类解析阶段会对Bean Conditions之外的条件注解做判断,与此同时也会注入到Spring的临时容器beanDefinitionNames,所以针对@Component标注的类而言,Bean Conditions可能会在配置类解析阶段生效、也可能不会生效;所以最终还是要在注册Bean阶段做一个全面的条件装配。不过一般而言,正常业务中不会有人在@Component衍生注解里搞条件装配(@Configuration除外),通常都在在自动装配类中做那么个操作。
相关文章
|
1月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
55 0
|
21小时前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
21小时前
|
Java Maven Spring
SpringBoot配置跨模块扫描问题解决方案
在分布式项目中,使用Maven进行多模块开发时,某些模块(如xxx-common)没有启动类。如何将这些模块中的类注册为Spring管理的Bean对象?本文通过案例分析,介绍了两种解决方案:常规方案是通过`@SpringBootApplication(scanBasePackages)`指定扫描路径;推荐方案是保持各模块包结构一致(如com.xxx),利用SpringBoot默认扫描规则自动识别其他模块中的组件,简化配置。
SpringBoot配置跨模块扫描问题解决方案
|
7天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
51 14
|
11天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
66 13
|
1月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
49 1
SpringBoot入门(7)- 配置热部署devtools工具
|
19天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
46 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
29天前
|
消息中间件 Java 数据库
解密Spring Boot:深入理解条件装配与条件注解
Spring Boot中的条件装配与条件注解提供了强大的工具,使得应用程序可以根据不同的条件动态装配Bean,从而实现灵活的配置和管理。通过合理使用这些条件注解,开发者可以根据实际需求动态调整应用的行为,提升代码的可维护性和可扩展性。希望本文能够帮助你深入理解Spring Boot中的条件装配与条件注解,在实际开发中更好地应用这些功能。
36 2