认识SpringBoot中的条件注解

认识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 中得到了广泛的使用,大量的自动化配置都是通过条件注解来实现的。

//此注解可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) 
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

从代码中可以看到,需要传入一个Class数组,并且需要继承Condition接口:

public interface Condition {
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

Condition是个接口,需要实现matches方法,返回true则注入bean,false则不注入。

  • 标注在类方法上: 一个方法只能注入一个bean实例,所以@Conditional标注在方法上只能控制一个bean实例是否注入。
  • 标注在类上: 一个类中可以注入很多实例,@Conditional标注在类上就决定了一批bean是否注入

4.常见的条件注解

  1. @ConditionalOnBean: 当容器中存在指定的Bean时,满足条件。
  2. @ConditionalOnMissingBean: 当容器中不存在指定的Bean时,满足条件。
  3. @ConditionalOnClass: 当类路径下存在指定的类时,满足条件。
  4. @ConditionalOnMissingClass: 当类路径下不存在指定的类时,满足条件。
  5. @ConditionalOnProperty: 当配置文件中存在指定的属性,并且具有特定的值时,满足条件。
  6. @ConditionalOnResource: 当类路径下存在指定的资源时,满足条件。
  7. @ConditionalOnExpression: 当SpEL(Spring Expression Language)表达式的结果为true时,满足条件。
  8. @ConditionalOnJava: 当Java版本匹配指定的条件时,满足条件。
  9. @ConditionalOnWebApplication: 当应用是一个Web应用时,满足条件。
  10. @ConditionalOnNotWebApplication: 当应用不是一个Web应用时,满足条件。

5.应用实践

我们定义一个Person接口。

package org.javatop.service;

/**
 * @author : Leo
 * @version 1.0
 * @date 2024-01-10 21:09
 * @description :
 */
public interface Person {
    String getName();
}

Person接口中有一个getName()方法。然后继续定义两个实现类

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";
    }
}
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的条件类,如下:

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("男人");
    }

}
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。

接下来我们来配置 配置类

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 就会生效

然后我们开始测试。

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。

认识SpringBoot中的条件注解

6.@Profile多环境配置注解

下面我们更改下我们上面的程序配置。

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:

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());
    }
}

可以看出来,和刚才我们的测试结果一摸一样。

认识SpringBoot中的条件注解

这样看起来 @Profile 注解貌似比 @Conditional 注解还要方便,那么 @Profile 注解到底是什么实现的呢?

我们来看一下 @Profile的源码:

/*
 * 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

认识SpringBoot中的条件注解

到这里就看明白了,其实还是我们在条件注解中写的那一套东西,只不过 @Profile 注解自动帮我们实现了而已。

本文由博客一文多发平台 OpenWrite 发布!

原文链接:https://juejin.cn/post/7349545661110517797 作者:程序员Leo说

(0)
上一篇 2024年3月24日 上午10:22
下一篇 2024年3月24日 上午10:33

相关推荐

发表回复

登录后才能评论