`

Spring Boot原理解析之Conditional条件装配

阅读更多

     Spring Boot可以使用条件装配来灵活地指定什么时候将哪些bean实例化并纳入容器,条件装配是spring boot自动配置机制(auto configure)的重要一环,也是理解spring boot原理的重要基础。本文以实例为引导,展示spring条件装配的常用使用场景,其间也会涉及一些spring的原理。阅读本文,要求有一些spring和spring boot的基本使用经验,最好对java config配置有一定了解。

 

    条件装配主要以@ConditionalOnXXX系列注解和@Conditional注解两大类注解结合java config配置来实现。其中@ConditionalOnXXX相当于@Conditional的多种预置特殊场景,提供装配bean的多种特殊条件。

 

    (一)实验环境介绍

     1. 搭建一个基本maven工程,包含spring容器的基本配置即可,无需web和jdbc相关依赖。为方便起见,你也可以使用spring boot向导快速搭建一个spring boot工程并导入依赖,但是不要使用spring boot自带的启动类运行。我们会自己创建容器启动类(也是我们的测试类),用比较原始的方式创建一个spring容器,便于我们观察bean是否被spring创建。

     2. 实验组件。

         我们的实验用组件就三大类,包含main方法的测试类,几个java bean以及一个spring的配置类。

     1)测试类。创建几个包含主函数的普通java类即可,我们在main函数中手工创建spring容器,并用getBean方法获取相应的bean并打印地址,观察该bean是否被创建。当然,如果你想使用junit单元测试类也是可以的。我们这里设置了三个测试类,结构和内容都相似,你也可以只创建一个,我们只是为了测试代码更清晰一些,因为要测试不同的条件装配。具体代码见下文详细说明。

 

 

实验组件说明

 

  2)几个普通的java类,用于充当要纳入spring容器管理的bean,无需继承任何类和实现任何接口(pojo)

这里取名为C1,C2。。。,你也可以根据自己习惯自行取名。出于良好的编程习惯,可以显式地增加一个无参构造器,可以什么内容都不写,下面不再赘述。

public class C1 {
    public C1(){
        
    }
}

 

3) 一个spring 配置类 

    这是我们实验的重点,具体内容在下节说明。

 

 

 

 

(二)编写配置类ConditionalConfig

/**
 * 条件装配测试
 * 使用conditional相关注解配置的一系列bean
 */
@Configuration
public class ConditionalConfig {

    /**
     * 无条件声明bean
     * @return
*/
@Bean
public C1 getC1(){
        C1 c1 = new C1();
        return c1;
}
    ...

 

       该类用于完成所有实验所用bean的配置,也是个pojo,不需要继承任何类或实现任何接口,但是需要以一个叫@Configuration的注解标注。这里使用了java config的配置方式,是spring boot用注解代替XML,实现几乎零配置的一个重要机制。如果没有接触过java config的配置,可以把该配置类想象成一个spring的xml配置文件。那么在这个配置文件里,最核心的标签是什么呢,当然就是<beans>,表示spring bean的容器,里面会放置一个个的<bean>标签,用于声明要实例化和交由spring 容器管理的bean。同样,我们这里的@Configuration注解就相当于<beans>标签,这个类里会有多个方法,以@Bean的注解标注,看成一个个的

<bean>标签,同样用于声明bean。也就是说两种方式含义相同形式不同    

        当然这里有个问题,无论我们从容器获取bean,还是进行依赖注入,通常都是使用bean的id。用XML配置文件的方式,可以通过bean标签的id属性设置bean的id,那么使用java config的方式如果设置?其实默认就是以每一个用于创建bean的方法名作为bean的id比如这里的getC1,getC2等等,随后我们会在测试中验证。如果你确实觉得这样的名字比较奇怪,也可以在@Bean注解中增加一个自定义bean id,比如myC1(实际上是为注解的value属性赋值  类似于spring mvc的requestmapping注解),就像这样

@Bean("myC1")
public C1 getC1(){
    C1 c1 = new C1();
    return c1;
}

      另外我们使用java config的方式在方法中实例化bean的方式通常就是直接new出来,要求有相应的构造器

也可以通过bean所在类自己提供的工厂方法或者反射等其他方式来实例化bean,有兴趣可以参阅其他资料。实例化以后,该对象就会存在于当前spring容器中。对于c1 bean,我们让它无条件创建

作为其他条件装配bean的一个辅助条件  下面介绍ConditionalOnXX系列注解

 

   1.  @ConditionalOnClass

   该注解表示根据某个指定的类是否存在于classpath中来决定是否实例化一个bean

Conditional} that only matches when the specified classes are on the classpath

注意该注解的target是type和method,也就是只能用于类型和方法上。这里我们注解在创建C2的java config方法上

@ConditionalOnClass(value = C1.class)// C1存在于classpath中才会加载C2
@Bean
public C2 getC2(){
    C2 c2 = new C2();
    return c2;
}

意思是要(调用该方法)创建C2,前提是C1的实例要存在于classpath中。存在则创建C2,不存在则不创建,这就以另一个bean构建了创建当前bean的条件。运行程序观察结果(注意测试代码和运行方法会在下文介绍 这里只是通过控制台输出先给大家展示每种条件的含义 现在只需要知道我们是通过spring容器的getBean(bean id)方法获取相应的bean的

 

com.example.springbootconditiondemo.C1@5db250b4
com.example.springbootconditiondemo.C2@223f3642

Process finished with exit code 0

 

这里可以看到由于C1是无条件创建并纳入spring容器的,所以肯定是存在于classpath中的,也就是说创建C2的前提条件是满足的,所以C1,C2两个实例均创建并纳入spring容器。这也是比较简单的一种条件装配注解

   这是正向的例子 我们再来看下反向的例子  也就是以一个不存在于当前classpath的类为条件

这里我们要修改@ConditionalOnClass注解的属性

不再使用value, 而是使用name属性指定一个字符串类型的全路径类名 比如

/**
 * 通过@ConditionalOnXX注解,指定是否加载该bean
 * @return
*/
@ConditionalOnClass(name = "C8")// C8存在于classpath中才会加载C2
@Bean
public C2 getC2(){
    C2 c2 = new C2();
    return c2;
}

 

C8是不存在的一个类  所以创建C2的条件不满足  我们再次进行测试

com.example.springbootconditiondemo.C1@184cf7cf
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.springbootconditiondemo.C2' available
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:346)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:337)
 at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)
 at com.example.springbootconditiondemo.App.main(App.java:18)

可以看到c1依然可以正常创建 但是C2确无法创建

这就进一步体现了@ConditionalOnclass注解对创建某个bean的控制作用

 

2. @ConditionalOnBean

   当指定的(另一个)bean存在于spring容器(上下文)中才执行该方法加载bean,比如我们把刚才加载C2的方法修改一下,将@ConditionalOnClass注解替换成@ConditionalOnBean注解。表示必须要C1 bean存在于当前spring 上下文中才会执行该方法实例化C2

@ConditionalOnBean(value = C1.class) //C1存在于spring容器中才会加载C2
@Bean
public C2 getC2(){
    C2 c2 = new C2();
    return c2;
}

运行测试观察结果:

17:26:26.230 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getC4'
com.example.springbootconditiondemo.C1@45b9a632
com.example.springbootconditiondemo.C2@25d250c6

 

跟之前类似,C1,C2两个bean均实例化并加载。大家可能会有疑问,这个注解同@ConditionalOnClass有什么区别呢。从目前来看,只要C1类存在于classpath中,无论是用@ConditionalOnClass注解还是@ConditionalOnBean注解,getC2()方法总会执行,似乎没什么区别。好,现在让我们来做一个实验。将getC1()方法上的@Bean注解注释掉,同时注释掉测试程序中获取C1的getBean方法,其他地方不改,运行测试程序

//@Bean("myC1")
public C1 getC1(){
    C1 c1 = new C1();
    return c1;
}

会发现出现异常,无法获取C2 bean。

No qualifying bean of type 'com.example.springbootconditiondemo.C2' available

原因是什么,是因为C1 bean不存在于spring 容器中,基于@ConditionalOnBean指定的条件,必须要C1 bean存在于spring 容器中。我们前面说过,@Bean注解的含义同xml配置文件中<bean id=""/>一样,用于声明一个bean,只有这样声明了 ,spring才会将其纳入容器中。那么此时C1这个类是否存在于classpath中呢,答案当然是肯定的,没有实例在spring 容器中并不代表该类没有被JVM加载。我们将getC2()方法上的条件注解再换回@ConditionalOnClass进行测试

 @ConditionalOnClass(C1.class)
 //@ConditionalOnClass(name = "C8")// C1存在于classpath中才会加载C2
// @ConditionalOnBean(value = C1.class) //C1存在于spring容器中才会加载C2
 //@ConditionalOnMissingBean(value = C1.class)//C1不存在于spring容器中才会加载C2
@Bean
public C2 getC2(){
     C2 c2 = new C2();
     return c2;
}

会发现C2能够正常从spring容器获取

com.example.springbootconditiondemo.C2@4fe767f3

也就是getC2方法执行的条件是满足的,这就是@ConditionalOnClass和@ConditionalOnBean这两个注解的区别

 

3. @ConditionalOnJava

    这个条件注解比较简单,顾名思义,判断依据是当前jdk版本是否与注解中指定版本一致,一致则执行,否则不执行。来看例子:

/**
 * 满足指定java版本时加载
 * @return
*
 * value属性指定jdk版本,range属性指定是高于等于该版本还是小于该版本
 */
@Bean
@ConditionalOnJava(value = JavaVersion.EIGHT,range = ConditionalOnJava.Range.EQUAL_OR_NEWER)
public C4 getC4(){
    C4 c4 = new C4();
    return c4;
}

@ConditionalOnJava注解主要有两个属性,一个value指定jdk版本号,另一个是range,指示需要高于等于等于指定版本还是低于指定版本,用一个叫Range的枚举表示,默认是前者。所以,如果是这种情况,可以不配置range,只配置value即可。这个注解比较好理解,就不演示测试结果了。

 

4. @Conditional

   如果出现@ConditionalOnXX不能满足要求,通常是需要根据一些更复杂的业务逻辑判断,那么可以使用@Conditional注解自定义判断规则,来看配置的例子

/**
 * 通过@Conditional注解,自己指定是否加载该bean的逻辑
 * @return
*/
@Bean
@Conditional(value = MyConditional1.class)
@Deprecated
public C3 getC3(){
    C3 c3 = new C3();
    return c3;
}

同样注解到标注@Bean的方法上,具体的判断逻辑由自定义的MyConditional1类来实现。该类必须要实现org.springframework.context.annotation.Condition接口,并且重写其matches()方法,该方法返回boolean,用于只是条件是否满足。

public class MyConditional1 implements Condition {

    /**
     * @param context  应用上下文环境,可以通过该参数获取用于帮助条件判断的辅助类,比如Environment,BeanFactory,ResouceLoader等
     * @param metadata 注解元数据,用于获取注解相关的信息
     * @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

 注意该方法的两个参数,这是我们获取被标注对象的上下文和相关注解元信息的重要组件,我们的判断逻辑通常围绕着这两个参数编写。

1)ConditionContext 是应用上下文环境,我们可以通过该参数获取用于帮助条件判断的辅助类,比如Environment,BeanFactory,ResouceLoader等

2)注解元数据,用于获取被注解类或方法的其他注解相关的信息

 

        来看例子,首先来看ConditionContext的使用,这里我们通过该参数获取spring的Environment接口,并通过该接口再获取环境相关信息,比如应用端口号,JAVA_HOME,MAVEN_HOME等信息都可以获取

//1.通过context参数获取Environment
Integer serverPort = context.getEnvironment().getProperty("server.port", Integer.TYPE);
System.out.println("当前应用端口号为:" + serverPort);
String mavenHome = context.getEnvironment().getProperty("MAVEN_HOME");
System.out.println("当前mavenhome:" + mavenHome);

测试结果:

 当前应用端口号为:8888
19:08:35.027 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'MAVEN_HOME' in PropertySource 'systemEnvironment' with value of type String
当前mavenhome:G:\devSoftware\apache-maven-3.6.1

 

 再来看另一个参数AnnotatedTypeMetadata,它可以获取被@Conditional标注的组件(一般是方法)上有哪些注解,以及这些注解的元数据(属性等)。比如我们这里测试该方法上是否有@Deprecated注解,并作为Conditional的判断条件。如果有,则返回true,否则返回false。

 

metadata.isAnnotated(Deprecated.class.getName())

 

由于在前面的配置类中,对于C3实例化方法getC3()做了@Deprecated标注,所以getBean时能获取C3,反之则不可以,请大家自行测试。

 

另外

ConditionContext 参数除了能获取环境信息外,还可以获取当前spring的BeanFactory(

context.getBeanFactory()

)以及classpath中的文件(

context.getResourceLoader();

)等,如果你需要使用这些信息进行Conditional判断,请自行参考源码。

 

 (三)编写测试程序

     最后,我们来展示一下如何编写测试程序。测试代码很简单,就是一个普通类加一个main函数,核心就是创建一个spring的应用上下文(也是BeanFactory),Spring Boot是帮我们自动创建了。注意,这里的应用上下文(ApplicationContext)的实现类选用AnnotationConfigApplicationContext,因为我们以注解的方式来配置,而不是使用XML文档形式。

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext acx = new AnnotationConfigApplicationContext();
//注册一个配置类,形成spring beans容器,相当于加载xml配置文件
acx.register(ConditionalConfig.class);
//初始化一个spring 容器
acx.refresh();
//测试c1,c2两个bean之间的依赖
       // System.out.println(acx.getBean(C1.class));
System.out.println(acx.getBean(C2.class));
}
}

      上面的第二句就是指定我们的java config配置类,就是我们前面用的用@Configuration标注的那个ConditionalConfig,相当于加载一个XML格式的bean配置文件。其他就是初始化容器,正常从容器中getBean出你要找的实例(spring bean),跟XML配置的形式没有太大区别。我们这里使用了3个测试类只是为了更清晰,逻辑和功能基本一样,完整源码会放在附件中。

     以上就是我们简单介绍的spring的Conditional条件装配机制。限于篇幅,只选择了几个常用的@ConditionalOnXX和@Conditional介绍,大家在实际工作或阅读spring boot源码时可能还会遇到其他条件装配标签,可参考本文介绍思路和测试方法自行研究。这些标签使用本身不复杂,但是对具体含义和使用场景比较抽象,建议大家多动手,从正反两方面测试,便于理解和加深印象。

 

  • 大小: 14.3 KB
分享到:
评论

相关推荐

    Spring Boot技术知识点:Bean装配1

    在Spring Boot框架中,Bean装配是核心概念之一,它涉及到如何管理和组织应用程序中的对象。Spring Boot简化了传统Spring应用的初始化过程,通过自动配置和组件扫描使得Bean的装配变得更加便捷。本文将深入探讨Spring...

    第三节-springboot源码解析-王炸篇.pdf

    自动装配的原理还与Spring的条件化装配有关,即@Conditional注解。这个注解能够根据特定条件决定是否创建某个Bean。Spring Boot的自动装配机制正是通过这种方式,基于类路径中的jar包和环境配置来选择合适的自动配置...

    深度剖析Spring Boot自动装配机制实现原理(csdn)————程序.pdf

    在Spring Boot中,自动装配是其核心特性之一,极大地简化了应用程序的配置。自动装配的目标是减少手动配置bean,让Spring框架能够根据项目依赖自动识别并管理bean。这个过程主要由`@EnableAutoConfiguration`注解...

    SpringBoot中使用@Conditional示例代码

    在Spring Boot框架中,`@Conditional`注解是条件装配的核心工具,它允许我们基于某些条件来决定是否加载或注册一个Bean。这个功能使得我们能够更精细化地控制应用的配置,仅在满足特定条件时才会激活某个Bean。下面...

    Spring Boot 常用注解.rar

    以上就是Spring Boot中一些常用的注解,掌握它们有助于提升开发效率,理解Spring Boot的工作原理。在实际开发中,根据具体需求选择合适的注解,可以使代码结构更加清晰,提高代码的可读性和可维护性。在Spring Boot...

    [课堂课件讲解]Java微服务实践-Spring Boot 自定义启动器.pptx

    自动装配Bean是由标准Spring @Configuration实现,结合Spring 4的新特性条件判断注解@Conditional及其Spring Boot派生注解,如:@ConditionalOnClass等。自动装配Bean可以根据需要自动装配相应的组件,以提高系统的...

    Spring Boot 核心知识,深入剖析!.docx

    自动配置的工作原理是基于`@Conditional`注解,这些注解确保只有在满足特定条件(如类存在、配置属性设置等)时,配置才会生效。 起步依赖是Spring Boot的另一个关键特性,它允许开发者通过声明依赖于特定的`spring...

    自动装配+条件注解+条件配置实现.zip

    在Spring Boot框架中,自动装配、条件注解和条件配置是三个重要的概念,它们共同构建了一个灵活、可扩展的应用程序架构。以下是对这些概念的详细解释: **自动装配(Auto-Wiring)** 自动装配是Spring框架的核心...

    fast-spring-boot-源码.rar

    这些启动器通过定义`@Configuration`和`@Conditional`注解,根据项目中的依赖条件自动装配相应的Bean。 6. **内嵌容器集成** Fast-Spring-Boot支持多种内嵌Servlet容器,如Tomcat、Jetty等。在源码中,`...

    springboot源码解析(二):自动装配原理1

    总的来说,Spring Boot的自动装配原理包括以下几个步骤: 1. 扫描并注册bean定义。 2. 根据条件判断启用哪些自动配置类。 3. 应用`@ConfigurationProperties`绑定配置属性。 4. 使用`@Autowired`和`@Qualifier`进行...

    编写自己的spring-boot-start.zip

    通过`@Configuration`和`@Conditional`注解,我们可以定义当特定条件满足时(例如,特定的Bean存在或者环境变量满足特定值),Spring容器会自动装配我们的配置。此外,我们还可以创建`@Component`、`@Service`、`@...

    自动装配模拟.zip

    2. **条件注解**:Spring Boot使用`@Conditional`系列注解来决定何时激活特定的自动配置。例如,`@ConditionalOnClass`表示只有当指定的类存在于类路径中时,该配置才会生效;`@ConditionalOnProperty`则依赖于特定...

    springBoot自动装配原理以及starter技术.pdf

    在Spring Boot中,自动装配是其核心特性之一,它极大地简化了配置,使得开发者能够快速构建应用程序。本文将深入解析Spring Boot自动装配的原理,并探讨starter技术。 首先,我们来看`@Import`注解,它是Spring框架...

    springboot自定义自动装配.rar

    2. **条件注解**:Spring Boot使用`@Conditional`系列注解(如`@ConditionalOnClass`、`@ConditionalOnMissingBean`等)来决定何时启用某个配置。这些注解帮助系统根据特定条件加载或忽略配置。 3. **组件扫描**:...

    springboot项目.pdf

    Spring Boot 提供了自动装配机制,可以根据不同的条件来自动装配不同的 Bean。自动装配机制可以通过使用 Conditional 注解来实现。 Conditional 注解可以用在方法上或类上,用于指定自动装配的条件。当 ...

    手动实现SpringBoot自动装配类

    在Spring Boot应用中,自动装配(Auto Configuration)是框架的核心特性之一,它使得开发者无需编写大量繁琐的配置代码,即可让应用中的各种组件自动协同工作。本教程将深入讲解如何手动实现Spring Boot的自动装配类...

    详解Spring Boot最核心的27个注解,你了解多少?

    @Conditional 注解是 Spring 4 新提供的注解,通过 @Conditional 注解可以根据代码中设置的条件装载不同的 bean。 ... 在接下来的内容中,我们将继续探究 Spring Boot 中的其他注解,包括 @Profile、@Value、@...

    spring源码研究

    深入研究Spring Boot的Application、AutoConfiguration、Conditional注解等,能更好地掌握启动过程和条件装配。 6. **Spring AOP代理**:Spring提供了JDK动态代理和CGLIB两种方式创建代理对象,用于实现AOP功能。...

    SpringBoot自动装配原理(简单易懂)

    本文将深入浅出地解析SpringBoot自动装配的原理。 1. **什么是自动装配** 自动装配是SpringBoot的一项功能,它通过`@Autowired`注解来自动注入依赖对象,无需显式地在配置文件中声明。SpringBoot会根据类型或名称...

    SpringBoot-参考指南文档.pdf

    Spring Boot的自动配置功能是基于`@Conditional`注解实现的,当满足特定条件(如类存在、属性配置等)时,才会生效。如果需要禁用某个自动配置,可以在配置类上使用`@EnableAutoConfiguration(exclude = {...

Global site tag (gtag.js) - Google Analytics