详解SpringBoot中@ConditionalOnClass注解的使用

目录
  • 一、@ConditionalOnClass注解初始
  • 二、@ConditionalOnClass注解用法
    • 1、使用value属性
    • 2、使用name属性
  • 三、@ConditionalOnClass是怎么实现的
  • 四、总结

今天给大家带来的是springboot中的@ConditionalOnClass注解的用法。上次的@ConditionalOnBean注解还记得吗?

一、@ConditionalOnClass注解初始

看下@CodidtionalOnClass注解的定义,

需要注意的有两点,

  • 该注解可以用在类及方法上;类指的是标有@Configuration的类,方法是标有@Bean的方法;
  • 该注解使用了@Conditional注解标记;这是重点

看到这里,有小伙伴会疑惑,讲了那么多@Conditional注解的作用是什么,不急,作用马上来。

@ConditionalOnClass注解的作用是当项目中存在某个类时才会使标有该注解的类或方法生效;

这句话有点拗口,通俗的讲,@ConditionalOnClass标识在@Configuration类上,只有存在@ConditionalOnClass中value/name配置的类该Configuration类才会生效;@ConditionalOnClass标识在@Bean方法上,只有只有存在@ConditionalOnClass中value/name配置的类方法才会生效。看具体的实例更容易理解些

二、@ConditionalOnClass注解用法

从上面@ConditionalOnClass注解的定义中我们知道该注解可以配置两个属性,分别是value和name,其中value和name都是数组,只不过内容不一样,

value是Class的数组,name是全限类名的字符串。

1、使用value属性

开始,我一直使用value属性进行配置,但是总是报错,比如我配置

@Configuration
@ConditionalOnClass(value = {Client.class})
public class MyAutoConfig {
    public MyAutoConfig(){
        System.out.println("constructor MyAutoConfig");
    }
}

该Client是下面的类,

org.springframework.boot.autoconfigure.data.elasticsearch.Client

它是ES中的一个类,我本身配置的含义是只有在Client存在的时候MyAutoConfig才会生效,但是总是不成功。你知道为什么不成功吗?

这是因为我没有引ES的依赖,导致在我的classpath中没有这个类,按照@ConditionalOnClass的理解,应该是不存在则不会生效,但是由于没有这个类,导致的问题是:无法编译,提示下面的错误

java: 找不到符号
  符号: 类 Client

这是可以理解的,因为没有这个类,而我要引用这个类肯定是引用不到的,所以编译是失败的,也就程序跑不起来。那么存在一个问题,@ConditionalOnClass注解的value属性要在什么情况下使用?

这里有一个mybatisplus的配置类,

其配置类上标识了@ConditionalOnClass注解,该注解中配置了value属性,且配置了SqlSessionFactory和SqlSessionFactoryBean两个类,

MyBatisPlusAutoConfiguration是在mybatis-plus-boot-starter的jar包下

SqlSessionFactory是在mybatis的jar包下

SqlSessionFactoryBean是在mybaits-spring的jar包下

这三个类分属于不同的jar包,如果我在一个项目中引入了mybatis-plus-boot-starter的jar包,没有引入mybatis的jar包那么MybatisPlusAutoConfiguration不会生效,也就是只有mybatis和mybatis-spring的jar包都引入了,MybatisPlusAutoConfiguration才会生效,才会被纳入spring容器的管理。

需要注意一点:为了防止少引包,在mybatis-plus-boot-starter中会依赖mybatis和mybatis-spring,这也是starter的好处,不会少引包,需要哪些依赖它都引好了。

那么再回到问题的开始,为什么,我配置了一个不存在的类就没成功,那是因为java的源文件需要编译,在编译时会检查类是否存在,不存在肯定是编译不通过的;而如果引用的是jar包中的文件引用另外一个jar的,则是因为jar包经过了编译,已经打包成功了,故不存在问题。

通过value属性需要结合jar包的方式,这里就不演示了,感兴趣的小伙伴可以自己尝试。通过name属性来指定。

2、使用name属性

@ConditionalOnClass注解还有name属性,name属性指定的是全限类名,也就是包含包名+类名。看下我的配置,

@Configuration
@ConditionalOnClass(name = {"com.my.template.config.ClassA"})
public class MyAutoConfig {
    public MyAutoConfig(){
        System.out.println("constructor MyAutoConfig");
    }
}

这里配置了“com.my.template.config.ClassA”,ClassA是我的一个存在的类,

下面启动,看下在启动日志中是否有“constructor MyConfig”打印,

constructor MyAutoConfig
constructor MyAutoConfig2
constructor classA
2022-07-30 17:18:54.113 

看到了,日志说明name配置是生效的,也就是存在ClassA则MyAutoConfig会注册到spring的容器中。作为对比,下面配置一个不存在的类ClassD,

@Configuration
@ConditionalOnClass(name = {"com.my.template.config.ClassD"})
public class MyAutoConfig {
    public MyAutoConfig(){
        System.out.println("constructor MyAutoConfig");
    }
}

看下启动日志

constructor MyAutoConfig2
constructor classA
2022-07-30 21:43:30.550  INFO 13116 --- [  

从上面的日志可以看到,没有打印出来想要的日志,说明MyAutoConfig没有注册到spring的容器中。

我们知道name属性是一个数组,上面仅仅配置了一个类,如果配置多个会是什么样子,感兴趣的可以自己尝试,这里这直接给出答案,只有name属性中配置的全部满足相应的配置类才会生效。

不知道,你是否对@ConditionalOnClass是怎么实现的感兴趣吗,继续往下看,大揭秘了。

三、@ConditionalOnClass是怎么实现的

要理解@ConditionalOnClass是怎么实现的还是要回到该注解的定义上,前边提到该注解被

@Conditional(OnClassCondition.class)

注解标识,@Conditional注解的含有是要满足条件才会生效,该注解后边再看。今天的主角是OnClassCondition类,看下其继承关系

重点关注XXCondition即可,可以看到最终实现了Condition接口,@Conditional注解的本质就是考查是否满足Codition接口的matches()方法,所以这看SpringBootCondition中matches方法的实现,

@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获得该注解标准的类或方法
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
            //模板方法,该实现在OnClassCodition类中
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
            //返回是否符合条件
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
					+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "
					+ "that class. This can also happen if you are "
					+ "@ComponentScanning a springframework package (e.g. if you "
					+ "put a @ComponentScan in the default package by mistake)", ex);
		}
		catch (RuntimeException ex) {
			throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
		}
	}

getMatchOutCome()方法使用了模板方法,实现在OnClassCondition类中,这是最要的方法,

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ClassLoader classLoader = context.getClassLoader();
		ConditionMessage matchMessage = ConditionMessage.empty();
         //获得@ConditionalOnClass注解中配置的value和name属性的值
		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
		if (onClasses != null) {
            //判断@ConditionOnClass注解配置的类是否都可以加载到,如有加载不到的则放到missing中
			List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
			if (!missing.isEmpty()) {
                //有加载不到的,则返回ConditionOutcome对下,其中属性match为false
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
						.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
					.found("required class", "required classes")
					.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
		}
        //@ConditionalOnMissingClass的处理逻辑
		List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
		if (onMissingClasses != null) {
			List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
			if (!present.isEmpty()) {
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
						.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
					.didNotFind("unwanted class", "unwanted classes")
					.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
		}
        //返回ConditionOutCome对下,其match属性为true
		return ConditionOutcome.match(matchMessage);
	}

上面的代码已经给出了注释,对应@ConditionalOnClass注解的处理就是解析器配置的value和name属性,判断配置的类是否加载到,如有未加载到的则直接返回属性match=false的ConditionOutcome对象,那么是如何判断是否加载到的,是通过FilteringSpringBootCondition中的filter方法,

protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
			ClassLoader classLoader) {
		if (CollectionUtils.isEmpty(classNames)) {
			return Collections.emptyList();
		}
		List<String> matches = new ArrayList<>(classNames.size());
		for (String candidate : classNames) {
            //循环调用matches方法
			if (classNameFilter.matches(candidate, classLoader)) {
				matches.add(candidate);
			}
		}
		return matches;
	}

对于@CoditionalOnClass的处理该方法传入的参数为

List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);

那么也就是调用ClassNameFilter.MISSING的matches方法,其方法如下

可以看到调用的是!isPresent方法,看下该方法的实现,

static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }

            try {
                //具体实现逻辑
                FilteringSpringBootCondition.resolve(className, classLoader);
                return true;
            } catch (Throwable var3) {
                return false;
            }
        }

具体的实现在resolve方法中,且该方法被try catch包住了,如果加载不到,直接返回false。

protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
        return classLoader != null ? Class.forName(className, false, classLoader) : Class.forName(className);
    }

看到这里,大家明白了,@ConditionalOnClass注解中判断配置的类是否存在使用的方法是Class.forName,类加载。

四、总结

本文主要认识了@ConditionalOnClass注解,分析了其注解的原理,如何判断配置的类是否存在。

  • @ConditionalOnClass注解有两个属性,分别是value和name,注意其配置方式;
  • @ConditionalOnClass注解判断配置的类是否存在的方式是通过Class.forName的方式;

以上就是详解SpringBoot中@ConditionalOnClass注解的使用的详细内容,更多关于SpringBoot @ConditionalOnClass注解的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringBoot2底层注解@Import用法详解

    目录 SpringBoot2注解@Import @Import 导入组件 用法 验证 SpringBoot2注解@Import 上一篇中了解到了@Configuration,可以注册组件.除此之外,还有许多注解也可以,用法跟之前学习 spring 的时候一样.比如,@Bean.@Component.@Controller.@Service.@Repository等. 这篇介绍另外一种给容器添加组件的方法:@Import注解,给容器中导入组件. @Import 导入组件 用法 @Import的用法

  • springboot中@component注解的使用实例

    目录 @component注解的使用 方式一 方式二 @component注解有什么作用 用一句话概括 @component注解的使用 配置响应头的内容. 方式一 直接在拦截器里配置响应头内容. /**  * 拦截器--用户身份确认.  */ public class RestInterceptor implements HandlerInterceptor {         private static final Logger log = LoggerFactory.getLogger(R

  • SpringBoot中@Import注解如何正确使用

    目录 简介 一.功能简介 二.示例 1.引入普通类 2.引入ImportSelector的实现类 (1)静态import场景(注入已知的类) (2)动态import场景(注入指定条件的类) 3.引入ImportBeanDefinitionRegister的实现类 简介 由于最近的项目需求,需要在把配置类导入到容器中,通过查询,使用@Import注解就能实现这个功能,@Import注解能够帮我们吧普通配置类(定义为Bean的类)导入到IOC容器中.该注解我们也能过在源码中经常看到,是框架层实现的重

  • SpringBoot超详细讲解@Value注解

    目录 一.非配置文件注入 1.注入普通字符串 2.注入JAVA系统变量 3.注入表达式 4.注入其他Bean属性 5.注入文件资源 6.注入URL资源 二.通过配置文件注入 1.注入普通字符串 2.注入基本类型 3.注入数组类型 4.注入List类型 5.注入Map类型 一.非配置文件注入 1.注入普通字符串 直接附在属性名上,在 Bean 初始化时,会赋初始值. @Value("admin") private String name; 2.注入JAVA系统变量 @Value(&quo

  • SpringBoot2底层注解@Configuration配置类详解

    目录 SpringBoot2底层注解@Configuration配置类 一.配置类 二.配置类本身也是组件 三.proxyBeanMethods 属性 有组件依赖的场景 SpringBoot2底层注解@Configuration配置类 一.配置类 @Configuration这个注解作用就是告诉 springboot 这是一个配置类. 这个配置已经不陌生了,在之前 spring 相关的使用全注解方式时,就使用到了配置类. 在配置类里,可以使用@Bean标记在方法上,给容器注册组件,默认也是单实例

  • SpringBoot超详细讲解@Enable*注解和@Import

    目录 @Enable* 解决办法 解放方案一 解决方案二 解决方案三 @Import 1.导入Bean 2.导入配置类 3.导入ImportSelector实现类 4.导入ImportBeanDefinitionRegistrar实现类 @Enable* 创建一个主启动类 package com.you.boot; import com.you.config.EnableUser; import com.you.config.UserConfig; import org.springframew

  • SpringBoot中@Import注解的使用方式

    目录 一. @Import引入普通类 二. @Import引入配置类(@Configuration修饰的类) 三 .@Import引入ImportSelector的实现类 3.1 静态import场景(注入已知的类) 3.2 动态import场景(注入指定条件的类) 四. @Import引入ImportBeanDefinitionRegistrar的实现类 前言: @Import注解用来帮助我们把一些需要定义为Bean的类导入到IOC容器里面.下面我们就对@Import注解的使用做一个简单的总结

  • 详解SpringBoot中@ConditionalOnClass注解的使用

    目录 一.@ConditionalOnClass注解初始 二.@ConditionalOnClass注解用法 1.使用value属性 2.使用name属性 三.@ConditionalOnClass是怎么实现的 四.总结 今天给大家带来的是springboot中的@ConditionalOnClass注解的用法.上次的@ConditionalOnBean注解还记得吗? 一.@ConditionalOnClass注解初始 看下@CodidtionalOnClass注解的定义, 需要注意的有两点,

  • 详解springboot中mybatis注解形式

    springboot整合mybatis对数据库进行访问,本实例采用注解的方式,如下: pom.xml文件 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> </parent> <pr

  • 详解SpringBoot中添加@ResponseBody注解会发生什么

    SpringBoot版本2.2.4.RELEASE. [1]SpringBoot接收到请求 ① springboot接收到一个请求返回json格式的列表,方法参数为JSONObject 格式,使用了注解@RequestBody 为什么这里要说明返回格式.方法参数.参数注解?因为方法参数与参数注解会影响你使用不同的参数解析器与后置处理器!通常使用WebDataBinder进行参数数据绑定结果也不同. 将要调用的目标方法如下: @ApiOperation(value="分页查询") @Re

  • 详解SpringBoot中@NotNull,@NotBlank注解使用

    目录 一.添加依赖 二.在类中使用验证注解 1.创建验证实体类(嵌套使用) 2.创建全局异常处理器,对message信息进行处理,并返回给前端 3.在controller中的使用 三.在方法参数中使用验证注解,与@RequsetParam注解同时使用,注意类上使用@Validated 四.自定义验证注解 一.添加依赖 <!-- spring-boot 2.3及以上的版本只需要引入下面的依赖 --> <dependency> <groupId>org.springfram

  • 详解SpringBoot中的参数校验(项目实战)

    Java后端发工作中经常会对前端传递过来的参数做一些校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,充满了if-else这种校验代码,在代码中相当冗长.例如说,用户注册时,会校验手机格式的正确性,用户名的长度等等.虽说前端也可以做参数校验,但是为了保证我们API接口的可靠性,以保证最终数据入库的正确性,后端进行参数校验不可忽视. Hibernate Validator 提供了一种统一方便的方式,让我们快速的实现参数校验. Hibernate Validator 使用注解,实现声明式校验

  • 详解SpringBoot定制@ResponseBody注解返回的Json格式

     1.引言 在SpringMVC的使用中,后端与前端的交互一般是使用Json格式进行数据传输,SpringMVC的@ResponseBody注解可以很好的帮助我们进行转换,但是后端返回数据给前端往往都有约定固定的格式,这时候我们在后端返回的时候都要组拼成固定的格式,每次重复的操作非常麻烦. 2.SpringMVC对@ResponseBody的处理 SpringMVC处理@ResponseBody注解声明的Controller是使用默认的.RequestResponseBodyMethodProc

  • 详解springboot使用异步注解@Async获取执行结果的坑

    目录 一.引言 二.获取异步执行结果 1.环境介绍 2.错误的方式 3.正确方式 三.异步执行@Async注解 四.总结 一.引言 在java后端开发中经常会碰到处理多个任务的情况,比如一个方法中要调用多个请求,然后把多个请求的结果合并后统一返回,一般情况下调用其他的请求一般都是同步的,也就是每个请求都是阻塞的,那么这个处理时间必定是很长的,有没有一种方法可以让多个请求异步处理那,答案是有的. springboot中提供了很便利的方式可以解决上面的问题,那就是异步注解@Async.正确的使用该注

  • 详解SpringBoot中自定义和配置拦截器的方法

    目录 1.SpringBoot版本 2.什么是拦截器 3.工作原理 4.拦截器的工作流程 4.1正常流程 4.2中断流程 5.应用场景 6.如何自定义一个拦截器 7.如何使其在Spring Boot中生效 8.实际使用 8.1场景模拟 8.2思路 8.3实现过程 8.4效果体验 9.总结 1.SpringBoot版本 本文基于的Spring Boot的版本是2.6.7 . 2.什么是拦截器 Spring MVC中的拦截器(Interceptor)类似于ServLet中的过滤器(Filter),它

  • 详解SpringBoot中@SessionAttributes的使用

    目录 简介 概述 代码 后端代码 前端代码 测试 简介 说明 本文介绍SpringBoot中@SessionAttributes的用法. 概述 在默认情况下,ModelMap中的属性作用域是request级别,也就是说,当本次请求结束后,ModelMap 中的属性将销毁.如果希望在多个请求中共享ModelMap中的属性,必须将其属性转存到session 中,这样 ModelMap 的属性才可以被跨请求访问. Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 sessi

  • 详解SpringBoot中Controller接收对象列表实现

    如果Spring Boot中对应的Controller要接收一个对象,该对象中又存放了一个List列表,那么页面该如何传递相关应的参数信息呢. 本篇文章给大家一个简单的示例,提供一种实现方式. 实体类 首先看实体类的结构(注意使用了Lombok): @Data public class Rules { private List<Rule> rules; } 对应Rule实体类代码如下: @Data public class Rule { /** * 类名 */ private String c

随机推荐