1. 简介

@Conditional注解在Spring4.0中引入,其主要作用就是判断条件是否满足,从而决定是否初始化并向容器注册Bean。

2. 定义

2.1 @Conditional

@Conditional注解定义如下:其内部只有一个参数为Class对象数组,且必须继承自Condition接口,通过重写Condition接口的matches方法来判断是否需要加载Bean

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}

2.2 Condition

Condition接口定义如下:该接口为一个函数式接口,只有一个matches接口,形参为ConditionContext context, AnnotatedTypeMetadata metadataConditionContext定义如2.2.1,AnnotatedTypeMetadata见名知意,就是用来获取注解的元信息的

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

2.2.1 ConditionContext

ConditionContext接口定义如下:通过查看源码可以知道,从这个类中可以获取很多有用的信息

public interface ConditionContext {
/**
* 返回Bean定义信息
* Return the {@link BeanDefinitionRegistry} that will hold the bean definition
* should the condition match.
* @throws IllegalStateException if no registry is available (which is unusual:
* only the case with a plain {@link ClassPathScanningCandidateComponentProvider})
*/
BeanDefinitionRegistry getRegistry(); /**
* 返回Bean工厂
* Return the {@link ConfigurableListableBeanFactory} that will hold the bean
* definition should the condition match, or {@code null} if the bean factory is
* not available (or not downcastable to {@code ConfigurableListableBeanFactory}).
*/
@Nullable
ConfigurableListableBeanFactory getBeanFactory(); /**
* 返回环境变量 比如在application.yaml中定义的信息
* Return the {@link Environment} for which the current application is running.
*/
Environment getEnvironment(); /**
* 返回资源加载器
* Return the {@link ResourceLoader} currently being used.
*/
ResourceLoader getResourceLoader(); /**
* 返回类加载器
* Return the {@link ClassLoader} that should be used to load additional classes
* (only {@code null} if even the system ClassLoader isn't accessible).
* @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
*/
@Nullable
ClassLoader getClassLoader();
}

3. 使用说明

通过一个简单的小例子测试一下@Conditional是不是真的能实现Bean的条件化注入。

3.1 创建项目

首先我们创建一个SpringBoot项目

3.1.1 导入依赖

这里我们除了springboot依赖,再添加个lombok依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ldx</groupId>
<artifactId>condition</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>condition</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

3.1.2 添加配置信息

在application.yaml 中加入配置信息

user:
enable: false

3.1.3 创建User类

package com.ldx.condition;

import lombok.AllArgsConstructor;
import lombok.Data; /**
* 用户信息
* @author ludangxin
* @date 2021/8/1
*/
@Data
@AllArgsConstructor
public class User {
private String name;
private Integer age;
}

3.1.4 创建条件实现类

package com.ldx.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; /**
* 用户bean条件判断
* @author ludangxin
* @date 2021/8/1
*/
public class UserCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
// 获取property user.enable
String property = environment.getProperty("user.enable");
// 如果user.enable的值等于true 那么返回值为true,反之为false
return "true".equals(property);
}
}

3.1.5 修改启动类

package com.ldx.condition;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional; @Slf4j
@SpringBootApplication
public class ConditionApplication { public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(ConditionApplication.class, args);
// 获取类型为User类的Bean
User user = applicationContext.getBean(User.class);
log.info("user bean === {}", user);
} /**
* 注入User类型的Bean
*/
@Bean
@Conditional(UserCondition.class)
public User getUser(){
return new User("张三",18);
} }

3.2 测试

3.2.1 当user.enable=false

报错找不到可用的User类型的Bean

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.3) 2021-08-01 17:07:51.994 INFO 47036 --- [ main] com.ldx.condition.ConditionApplication : Starting ConditionApplication using Java 1.8.0_261 on ludangxindeMacBook-Pro.local with PID 47036 (/Users/ludangxin/workspace/idea/condition/target/classes started by ludangxin in /Users/ludangxin/workspace/idea/condition)
2021-08-01 17:07:51.997 INFO 47036 --- [ main] com.ldx.condition.ConditionApplication : No active profile set, falling back to default profiles: default
2021-08-01 17:07:52.461 INFO 47036 --- [ main] com.ldx.condition.ConditionApplication : Started ConditionApplication in 0.791 seconds (JVM running for 1.371)
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.ldx.condition.User' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
at com.ldx.condition.ConditionApplication.main(ConditionApplication.java:16) Process finished with exit code 1

3.2.2 当user.enable=true

正常输出UserBean实例信息

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.3) 2021-08-01 17:13:38.022 INFO 47129 --- [ main] com.ldx.condition.ConditionApplication : Starting ConditionApplication using Java 1.8.0_261 on ludangxindeMacBook-Pro.local with PID 47129 (/Users/ludangxin/workspace/idea/condition/target/classes started by ludangxin in /Users/ludangxin/workspace/idea/condition)
2021-08-01 17:13:38.024 INFO 47129 --- [ main] com.ldx.condition.ConditionApplication : No active profile set, falling back to default profiles: default
2021-08-01 17:13:38.434 INFO 47129 --- [ main] com.ldx.condition.ConditionApplication : Started ConditionApplication in 0.711 seconds (JVM running for 1.166)
2021-08-01 17:13:38.438 INFO 47129 --- [ main] com.ldx.condition.ConditionApplication : user bean === User(name=张三, age=18)

3.3 小结

上面的例子通过使用@ConditionalCondition接口,实现了spring bean的条件化注入。

好处:

  1. 可以实现某些配置的开关功能,如上面的例子,我们可以将UserBean换成开启缓存的配置,当property的值为true时,我们才开启缓存的配置。
  2. 当有多个同名的bean时,如何抉择的问题。
  3. 实现自动化的装载。如判断当前classpath中有mysql的驱动类时(说明我们当前的系统需要使用mysql),我们就自动的读取application.yaml中的mysql配置,实现自动装载;当没有驱动时,就不加载。

4. 改进

从上面的使用说明中我们了解到了条件注解的大概使用方法,但是代码中还是有很多硬编码的问题。比如:UserCondition中的property的key包括value都是硬编码,其实我们可以通过再扩展一个注解来实现动态的判断和绑定。

4.1 创建注解

import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*; /**
* 自定义条件属性注解
* <p>
* 当配置的property name对应的值 与设置的 value值相等时,则注入bean
* @author ludangxin
* @date 2021/8/1
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 指定condition的实现类
@Conditional({UserCondition.class})
public @interface MyConditionOnProperty {
// 配置信息的key
String name();
// 配置信息key对应的值
String value();
}

4.2 修改UserCondition

package com.ldx.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; import java.util.Map; /**
* 用户bean条件判断
* @author ludangxin
* @date 2021/8/1
*/
public class UserCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
// 获取自定义的注解
Map<String, Object> annotationAttributes = annotatedTypeMetadata.getAnnotationAttributes("com.ldx.condition.MyConditionOnProperty");
// 获取在注解中指定的name的property的值 如:user.enable的值
String property = environment.getProperty(annotationAttributes.get("name").toString());
// 获取预期的值
String value = annotationAttributes.get("value").toString();
return value.equals(property);
}
}

测试后,结果符合预期。

其实在spring中已经内置了许多常用的条件注解,其中我们刚实现的就在内置的注解中已经实现了,如下。

5. Spring内置条件注解

注解 说明
@ConditionalOnSingleCandidate 当给定类型的bean存在并且指定为Primary的给定类型存在时,返回true
@ConditionalOnMissingBean 当给定的类型、类名、注解、昵称在beanFactory中不存在时返回true.各类型间是or的关系
@ConditionalOnBean 与上面相反,要求bean存在
@ConditionalOnMissingClass 当给定的类名在类路径上不存在时返回true,各类型间是and的关系
@ConditionalOnClass 与上面相反,要求类存在
@ConditionalOnCloudPlatform 当所配置的CloudPlatform为激活时返回true
@ConditionalOnExpression spel表达式执行为true
@ConditionalOnJava 运行时的java版本号是否包含给定的版本号.如果包含,返回匹配,否则,返回不匹配
@ConditionalOnProperty 要求配置属性匹配条件
@ConditionalOnJndi 给定的jndi的Location 必须存在一个.否则,返回不匹配
@ConditionalOnNotWebApplication web环境不存在时
@ConditionalOnWebApplication web环境存在时
@ConditionalOnResource 要求制定的资源存在

SpringBoot自动装配-Condition的更多相关文章

  1. 一步步从Spring Framework装配掌握SpringBoot自动装配

    目录 Spring Framework模式注解 Spring Framework@Enable模块装配 Spring Framework条件装配 SpringBoot 自动装配 本章总结 Spring ...

  2. Spring Boot之从Spring Framework装配掌握SpringBoot自动装配

    Spring Framework模式注解 模式注解是一种用于声明在应用中扮演“组件”角色的注解.如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的 ...

  3. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  4. springboot自动装配

    Spring Boot自动配置原理 springboot自动装配 springboot配置文件 Spring Boot的出现,得益于“习惯优于配置”的理念,没有繁琐的配置.难以集成的内容(大多数流行第 ...

  5. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  6. springboot自动装配(3)---条件注解@Conditional

    之前有说到springboot自动装配的时候,都是去寻找一个XXXAutoConfiguration的配置类,然而我们的springboot的spring.factories文件中有各种组件的自动装配 ...

  7. SpringBoot自动装配原理解析

    本文包含:SpringBoot的自动配置原理及如何自定义SpringBootStar等 我们知道,在使用SpringBoot的时候,我们只需要如下方式即可直接启动一个Web程序: @SpringBoo ...

  8. springboot自动装配原理

    最近开始学习spring源码,看各种文章的时候看到了springboot自动装配实现原理.用自己的话简单概括下. 首先打开一个基本的springboot项目,点进去@SpringBootApplica ...

  9. springboot自动装配原理,写一个自己的start

    springboot自动装配原理 第一次使用springboot的时候,都感觉很神奇.只要加入一个maven的依赖,写几行配置,就能注入redisTemple,rabbitmqTemple等对象. 这 ...

随机推荐

  1. Linux命令大全之基本命令

    命令提示符中:    ~:表示家目录   #:表示超级用户   $:表示普通用户 命令 [选项] [参数] ls(list):查询目录中的内容 ls  [选项]  [文件或目录] -a:显示所有文件, ...

  2. 解决java socket在传输汉字时出现截断导致乱码的问题

    解决java socket在传输汉字时出现截断导致乱码的问题 当使用socket进行TCP数据传输时,传输的字符串会编码成字节数组,当采用utf8编码时,数字与字母长度为1个字节,而汉字一般为3个字节 ...

  3. Jenkins 构建自动化 .NET Core 发布镜像

    Jenkins 构建自动化 .NET Core 发布镜像 导读 在本章中,将介绍如何在 Linux 下使用 Docker 部署.启动 Jenkins,编写脚本,自动化构建 .NET Core 应用,最 ...

  4. 温故知新Docker概念及Docker Desktop For Windows v3.1.0安装

    Docker 简介 什么是Docker? Docker是一个开放源代码软件项目,项目主要代码在2013年开源于GitHub.它是云服务技术上的一次创新,让应用程序布署在软件容器下的工作可以自动化进行, ...

  5. 22.17、heartbeat和drbd整合

    1.要确保master-db和slave-db的drbd服务和heartbeat服务都已经停止了: 2.heartbeate设置: 修改master-db和slave-db的'/etc/ha.d/ha ...

  6. 5、SpringBoot整合之SpringBoot整合MybatisPlus

    SpringBoot整合MybatisPlus 目录(可点击直接跳转,但还是建议按照顺序观看,四部分具有一定的关联性): 实现基础的增删改查 实现自动填充功能 实现逻辑删除 实现分页 首先给出四部分完 ...

  7. 学会这些CSS技巧让你写样式更加丝滑

    目录 1,前言 1,calc() 2,min() 3,max() 4,clamp() 5,gap 6,writing-mode 1,前言 记录一些很好用的css属性 1,calc() calc()函数 ...

  8. 其他:IntelliJ IDEA设置运行内存

    1. 打开idea的安装路径,进去bin目录  2. 修改idea.exe.vmoptions  将idea内存设置为-Xms512m -Xmx2048m -XX:ReservedCodeCacheS ...

  9. org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [java.util.List]: Specified class

    错误:org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [java.util ...

  10. buu 刮开有奖

    一.查壳, 二.拖入ida,分析 直接搜字符串完全没头绪,在看了大佬的wp才找到了,关键函数. 明显那个String就是我们要求的flag,要开始分析程序. 字符串长度为8,同时这个函数对字符串进行了 ...