认识SpringBoot中的条件注解
1.前言☕
大家好,我是Leo哥🫣🫣🫣,今天给大家带来关于精品SpringBoot专栏,暂且就给他起名为循序渐进学SpringBoot,这里我参考了我上一个专栏:循序渐进学SpringSecurity6。有需要的朋友可以抓紧学习来哈,带你从SpringSecurity从零到实战项目。好了,我们进入正题,为什么会有SpringBoot这个专栏呢,是这样的,今年Leo哥也是正在重塑知识体系,从基础到框架,而SpringBoot又是我们框架中的核心,我觉得很有必要通过以博客的形式将我的知识系列进行输出,同时也锻炼一下自己的写作能力,如果能帮到大家那就更好啦!!!本地系列教程会从SpringBoot基础讲起,会以知识点+实例+项目的学习模式由浅入深对Spring Boot框架进行学习&使用。好了,话不多说让我们开始吧😎😎😎。
2.概述
SpringBoot 大量的自动化配置都是基于条件注解来实现的, 如果用户有配置就用用户的配置,如果没有就用系统默认的配置。条件注解是整个 Spring Boot 的核心,条件注解并非一个新事物,这是一个存在于 Spring 中的东西,我们在 Spring 中常用的 profile 实际上就是条件注解的一个特殊化。
@Conditional 是 Spring4 新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean,让我们可以在满足不同条件时创建不同的 Bean,这种配置方式在 Spring Boot 中得到了广泛的使用,大量的自动化配置都是通过条件注解来实现的
有的朋友可能没用过条件注解,但是开发环境、生产环境切换的 Profile 多多少少都有用过吧?我们上篇文字也有提到过--starter,实际上这就是条件注解的一个特例。
3.@Conditional的定义
Spring4 中提供了更加通用的条件注解,让我们可以在满足不同条件时创建不同的 Bean,这种配置方式在 Spring Boot 中得到了广泛的使用,大量的自动化配置都是通过条件注解来实现的。
java
//此注解可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
从代码中可以看到,需要传入一个Class数组,并且需要继承Condition接口:
java
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
Condition是个接口,需要实现matches方法,返回true则注入bean,false则不注入。
- 标注在类方法上: 一个方法只能注入一个bean实例,所以@Conditional标注在方法上只能控制一个bean实例是否注入。
- 标注在类上: 一个类中可以注入很多实例,@Conditional标注在类上就决定了一批bean是否注入
4.常见的条件注解
- @ConditionalOnBean: 当容器中存在指定的Bean时,满足条件。
- @ConditionalOnMissingBean: 当容器中不存在指定的Bean时,满足条件。
- @ConditionalOnClass: 当类路径下存在指定的类时,满足条件。
- @ConditionalOnMissingClass: 当类路径下不存在指定的类时,满足条件。
- @ConditionalOnProperty: 当配置文件中存在指定的属性,并且具有特定的值时,满足条件。
- @ConditionalOnResource: 当类路径下存在指定的资源时,满足条件。
- @ConditionalOnExpression: 当SpEL(Spring Expression Language)表达式的结果为true时,满足条件。
- @ConditionalOnJava: 当Java版本匹配指定的条件时,满足条件。
- @ConditionalOnWebApplication: 当应用是一个Web应用时,满足条件。
- @ConditionalOnNotWebApplication: 当应用不是一个Web应用时,满足条件。
5.应用实践
我们定义一个Person接口。
java
package org.javatop.service;
/**
* @author : Leo
* @version 1.0
* @date 2024-01-10 21:09
* @description :
*/
public interface Person {
String getName();
}
Person接口中有一个getName()方法。然后继续定义两个实现类
java
package org.javatop.proifle.service.impl;
import org.javatop.proifle.service.Person;
/**
* @author : Leo
* @version 1.0
* @date 2024-01-10 21:11
* @description :
*/
public class Man implements Person {
@Override
public String getName() {
return "man";
}
}
java
package org.javatop.proifle.service.impl;
import org.javatop.proifle.service.Person;
/**
* @author : Leo
* @version 1.0
* @date 2024-01-10 21:11
* @description :
*/
public class Woman implements Person {
@Override
public String getName() {
return "woman";
}
}
分别是 Man和 Woman两个类,两个类实现了getName()
方法,然后分别返回不同值
接下来再分别创建 ManPersonCondition 和 WomanPersonCondition的条件类,如下:
java
package org.javatop.proifle.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author : Leo
* @version 1.0
* @date 2024-01-10 21:12
* @description :
*/
public class ManPersonCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("person").equals("男人");
}
}
java
package org.javatop.proifle.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author : Leo
* @version 1.0
* @date 2024-01-10 21:13
* @description :
*/
public class WomanPersonCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("person").equals("女人");
}
}
在 matches 方法中做条件属性判断,当系统属性中的 person 属性值为 男人
的时候,ManPersonCondition的条件得到满足,当系统中 person 属性值为 女人
的时候,WomanPersonCondition的条件得到满足,换句话说,哪个条件得到满足,一会就会创建哪个Bean。
接下来我们来配置 配置类
java
package org.javatop.proifle.config;
import org.javatop.proifle.condition.ManPersonCondition;
import org.javatop.proifle.condition.WomanPersonCondition;
import org.javatop.proifle.service.Person;
import org.javatop.proifle.service.impl.Man;
import org.javatop.proifle.service.impl.Woman;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* @author : Leo
* @version 1.0
* @date 2024-01-10 21:01
* @description : 配置
*/
@Configuration
public class BeanConfig {
@Bean("person")
@Conditional(ManPersonCondition.class)
Person man() {
return new Man();
}
@Bean("person")
@Conditional(WomanPersonCondition.class)
Person woman() {
return new Woman();
}
}
这个配置类,大家重点注意两个地方:
- 两个
Bean
的名字都为 person ,这不是巧合,而是有意取的。两个 Bean 的返回值都为其父类对象 Person。 - 每个
Bean
上都多了 @Conditional 注解,当 @Conditional 注解中配置的条件类的 matches 方法返回值为 true 时,对应的 Bean 就会生效
然后我们开始测试。
java
package org.javatop.proifle;
import org.javatop.proifle.config.BeanConfig;
import org.javatop.proifle.service.Person;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author : Leo
* @version 1.0
* @date 2024-01-10 21:26
* @description :
*/
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().getSystemProperties().put("person", "男人");
ctx.register(BeanConfig.class);
ctx.refresh();
Person person= (Person) ctx.getBean("person");
System.out.println(person.getName());
}
}
可以看到控制台只打印了man,说明只有ManPersonCondition这个条件生效了,并返回true,所以我们Spring中只注册了这个一个bean。
6.@Profile多环境配置注解
下面我们更改下我们上面的程序配置。
java
package org.javatop.proifle.config;
import org.javatop.proifle.condition.ManPersonCondition;
import org.javatop.proifle.condition.WomanPersonCondition;
import org.javatop.proifle.service.Person;
import org.javatop.proifle.service.impl.Man;
import org.javatop.proifle.service.impl.Woman;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
/**
* @author : Leo
* @version 1.0
* @date 2024-01-10 21:01
* @description : 配置
*/
@Configuration
public class BeanConfig {
@Bean("person")
// @Conditional(ManPersonCondition.class)
@Profile("man")
Person Yellow() {
return new Man();
}
@Bean("person")
// @Conditional(WomanPersonCondition.class)
@Profile("woman")
Person black() {
return new Woman();
}
}
这次不需要条件注解了,取而代之的是 @Profile 。然后在 Test方法中,按照如下方式加载 Bean:
java
package org.javatop.proifle;
import org.javatop.proifle.config.BeanConfig;
import org.javatop.proifle.service.Person;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author : Leo
* @version 1.0
* @date 2024-01-10 21:26
* @description :
*/
public class Test {
public static void main(String[] args) {
// AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
// ctx.getEnvironment().getSystemProperties().put("person", "男人");
// ctx.register(BeanConfig.class);
// ctx.refresh();
// Person person= (Person) ctx.getBean("person");
// System.out.println(person.getName());
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("man");
ctx.register(BeanConfig.class);
ctx.refresh();
Person person= (Person) ctx.getBean("person");
System.out.println(person.getName());
}
}
可以看出来,和刚才我们的测试结果一摸一样。
这样看起来 @Profile 注解貌似比 @Conditional 注解还要方便,那么 @Profile 注解到底是什么实现的呢?
我们来看一下 @Profile的源码:
java
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Profiles;
@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();
}
可以看到,它也是通过条件注解来实现的。条件类是 ProfileCondition 。
到这里就看明白了,其实还是我们在条件注解中写的那一套东西,只不过 @Profile 注解自动帮我们实现了而已。
本文由博客一文多发平台 OpenWrite 发布!