热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Spring生态研习【五】:Springboot中bean的条件注入shihuc

条件注入注解的灵活运用,可以让spring应用变
条件注入注解的灵活运用,可以让spring应用变得简单高效,还简洁!

在springboot中,开发的确变的简单了很多,但是,开发者现在希望开发傻瓜式的方便搞定项目中的各种奇怪的需求最好了,不用烧脑,本来程序猿的生活就是枯燥的,不要再给自己添加更多的烦恼。

 

今天,就为了方便这点,介绍下,如何解决在开发过程中,一些场景下,为了实现一个配置模块中,基于开关量或者选择配置项,实现不同功能,例如,在一个session共享模块当中,解决session是基于header传递还是基于COOKIE传递这两种应用场景,有些应用中希望基于header传递sessionId,但是有些应用中希望基于COOKIE传递sessionId,然后,session共享模块,是一个非常基础的组件,差不多是一个开箱即用的功能块。所以呢,最好能配置好,然后只需要基于配置文件中的某个选项,就能实现运行在不同的工作模式下。这个能否做到呢?真的只需要改一下配置中的开关量就能实现吗?

 

能否实现,这里卖个关子,先不说,介绍完了本篇博文后,细心的读者一定知道答案,或者说一定能明白能否做,怎么做!

 

第一大点:先介绍一下springboot中能够支持的或者说封装好的常用的条件注入的注解

1 @ConditionalOnBean

1.1 基本使用案例

@Component
@ConditionalOnBean(name="aBean")
public class BBean {
  private final ABean aBean;
  public BBean(ABean aBean) {
      // ...
  }
}

 

1.2 使用说明

只有当beang的名称为aBean存在的时候,才会注入BBean。

 

2 @ConditionalOnMissingBean

2.1 基本案例

@Bean
@ConditionalOnMissingBean(name = "notExistsBean")
public BeanToCreate createOneBean() {
    return new BeanToCreate("notExistsBean");
}

 

2.2 使用说明

只有当bean名称为notExistsBean不存在的时候,BeanToCreate类型的bean才会被创建,和@ConditionalOnBean的使用方式相反

 

3 @ConditionalOnClass

3.1 基本使用案例

@Bean
@ConditionalOnClass(DependedClz.class)
public InjectIfClzExists injectIfClzExists() {
    return new InjectIfClzExists("dependedClz");
}

 

3.2 使用说明

只有当Class为DependedClz.class存在的时候,才会注入类型为InjectIfClzExists的bean,使用上和@ConditionalOnBean有些类似。

 

4 @ConditionalOnMissingClass

4.1 使用案例

@Bean
@ConditionalOnMissingClass("com.shihuc.bean.clz.DependedClz")
public InjectIfClzNotExists injectIfClzNotExists() {
    return new InjectIfClzNotExists("com.shihuc.bean.clz.DependedClz");
}

 

4.2 使用说明

只有当类com.shihuc.bean.clz.DependedClz不存在的时候,才会注入类型为InjectIfClzNotExists的bean。

 

5 @ConditionalOnProperty

5.1 基本使用案例

springboot的项目中配置文件application.properties文件中有如下配置:

#.....
section.condition_field=noti
section.condition_property=test
#...
@Bean
@ConditionalOnProperty("section.condition_field")
public PropertyExistBean propertyExistBean() {
    return new PropertyExistBean("section.condition_field");
}

 

5.2 使用说明

主要是根据配置文件中的参数,来决定是否需要创建这个bean,这样就给了我们一个根据配置来控制Bean的选择的手段了,这个非常的好用。因为application.properties文件中存在section.condition_field这个属性,所以,PropertyExistBean这个bean会被创建出来。

 

5.3 扩展用法

5.3.1 注解定义

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

    /**
     * Alias for {@link #name()}.
     * @return the names
     * 注意,这个value和name不能同时使用
     */
    String[] value() default {};

    /**
     * A prefix that should be applied to each property. The prefix automatically ends
     * with a dot if not specified.
     * @return the prefix
     */
    String prefix() default "";

    /**
     * The name of the properties to test. If a prefix has been defined, it is applied to
     * compute the full key of each property. For instance if the prefix is
     * {@code app.config} and one value is {@code my-value}, the full key would be
     * {@code app.config.my-value}
     * 

* Use the dashed notation to specify each property, that is all lower case with a "-" * to separate words (e.g. {@code my-long-property}). * @return the names */ String[] name() default {}; /** * The string representation of the expected value for the properties. If not * specified, the property must not be equal to {@code false}. * @return the expected value */ String havingValue() default ""; /** * Specify if the condition should match if the property is not set. Defaults to * {@code false}. * @return if should match if the property is missing */ boolean matchIfMissing() default false; }

 

当我想实现配置文件中存在属性aaa.bbb且其属性的值为ccc时,才注入bean实例DDDD(名为dddd)。

@Bean("dddd")
@ConditionalOnProperty(value="aaa.bbbb", havingValue="ccc")
public DDDD propertyExistBean() {
    return new DDDD("aaa.bbb");
}

 

6 @ConditionalOnExpression

6.1 使用案例

配置文件application.properties中存在下面的配置内容:

conditional.flag=true

java对应代码:

@Bean
@ConditionalOnExpression("#{\'true\'.equals(environment[\'conditional.flag\'])}")
public ExpressTrueBean expressTrueBean() {
    return new ExpressTrueBean("express true");
}

 

6.2 使用说明

相比较前面的Bean,Class是否存在,配置参数property是否存在或者有某个值而言,这个依赖SPEL表达式的,使用起来就功能显得更加强大了;其主要就是执行Spel表达式,根据返回的true/false来判断是否满足条件。

 

第二大点: spring基于Condition接口和@Conditional注解进行注入bean

这个相当于是条件注入bean的根源解决方案,上述其他几个ConditionalOnXXXX的注解,都是这个Conditional注解的具体场景的定制版,假如没有能够满足自己的应用场景的,或者说要自己实现一个比较特殊的条件注入呢,例如多个条件同时成立之类,怎么办呢,那就需要通过实现Condition接口然后基于@Conditional注解进行使用了。

 

1 @Conditional注解定义

//此注解可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    /**
     * All {@link Condition}s that must {@linkplain Condition#matches match}
     * in order for the component to be registered.
     */
    Class[] value();
}

注意,这个注解就一个参数value,且入参是一个Condition的Class的数组。

 

2 Condition是什么?

@FunctionalInterface
public interface Condition {

    /**
     * Determine if the condition matches.
     * @param context the condition context
     * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
     * or {@link org.springframework.core.type.MethodMetadata method} being checked
     * @return {@code true} if the condition matches and the component can be registered,
     * or {@code false} to veto the annotated component\'s registration
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

 

3. 使用案例

假设属性配置文件中,有两个环境参数,一个是温度temp,一个是湿度humi,只有当温度高于30度,且湿度大于50个点时,启用Linux,当温度小于30度且湿度小于50个点时,启用Windows,这个只是为了说明在一个@Conditional里面将多个条件满足该如何实现,还有其他的业务场景,可以参照这个案例。

3.1 配置文件参数

#温度数据,摄氏温度
conditional.prop.temp=29
#湿度数据,百分比,这里不带百分号,相当于扩大100倍,使用的时候除以100
conditional.prop.humi=51

 

3.2 定义bean

有一个HeWoBean的接口,以及两个实现类HelloBean和WorldBean。

/**
 * @Author: chengsh05
 * @Date: 2019/8/29 16:17
 */
public interface HeWoBean {

    public String toString();
}
/**
 * @Author: chengsh05
 * @Date: 2019/8/29 15:52
 */
public class HelloBean implements HeWoBean {

    public String getHhh() {
        return hhh;
    }

    public void setHhh(String hhh) {
        this.hhh = hhh;
    }

    public String getEee() {
        return eee;
    }

    public void setEee(String eee) {
        this.eee = eee;
    }

    String hhh;

    String eee;

    public HelloBean(String hh, String ee) {
        this.hhh = hh;
        this.eee = ee;
    }

    @Override
    public String toString() {
        return this.hhh + ", " + this.eee;
    }
}
/**
 * @Author: chengsh05
 * @Date: 2019/8/29 15:54
 */
public class WorldBean implements HeWoBean {
    public String getWww() {
        return www;
    }

    public void setWww(String www) {
        this.www = www;
    }

    public String getOoo() {
        return ooo;
    }

    public void setOoo(String ooo) {
        this.ooo = ooo;
    }

    String www;
    String ooo;

    public WorldBean(String ww, String oo) {
        this.www = ww;
        this.ooo = oo;
    }

    @Override
    public String toString() {
        return this.www + ", " + this.ooo;
    }
}

 

3. condition接口实现类及@Conditional应用

/**
 * @Author: chengsh05
 * @Date: 2019/8/29 9:08
 * @Description: 配置信息中,温度和湿度条件满足的时候,即当温度temp大于30度,湿度大于50%,启用Linux
 */
public class LinuxTime implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String tempStr = context.getEnvironment().getProperty("conditional.prop.temp");
        float temp = Float.valueOf(tempStr);
        String humiStr = context.getEnvironment().getProperty("conditional.prop.humi");
        float humi = Float.valueOf(humiStr);
        if(temp > 30 && humi > 60){
            return true;
        }
        return false;
    }
}
/**
 * @Author: chengsh05
 * @Date: 2019/8/29 9:07
 * @Description: 配置信息中,温度和湿度条件满足的时候,即当温度temp小于30度,湿度小于50%,启用windows
 */
public class WindowsTime implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String tempStr = context.getEnvironment().getProperty("conditional.prop.temp");
        float temp = Float.valueOf(tempStr);
        String humiStr = context.getEnvironment().getProperty("conditional.prop.humi");
        float humi = Float.valueOf(humiStr);
        if(temp <30 && humi <60){
            return true;
        }
        return false;
    }
}
/**
 * @Author: chengsh05
 * @Date: 2019/8/29 15:50
 */
@Configuration
public class MyConditional {

    @Bean("mybean")
    @Conditional(LinuxTime.class)
    public HelloBean createHello() {
        return new HelloBean("hello", "Linux");
    }

    @Bean("mybean")
    @Conditional(WindowsTime.class)
    public WorldBean createWorld() {
        return new WorldBean("world", "Windows");
    }
}

 

4.应用验证

/**
 * @Author: chengsh05
 * @Date: 2019/8/29 16:03
 */
@Controller
@RequestMapping("/condition")
public class ConditionalController {

    @Autowired
    @Qualifier("mybean")
    private HeWoBean myBean;

    @RequestMapping("/check")
    @ResponseBody
    public void check() {
        System.out.println("///||||\\\\ ==> " + myBean.toString());
    }
}

分析来看,LinuxTime因为没有满足温度temp和湿度humi的条件(即在配置文件中的参数),所以,LinuxTime这个bean在MyConditional这个配置类中是不会被创建出来的,即最终HeHoBean这个就只有WorldBean被注入到spring容器了。打印的日志,也证实了这个。

///||||\\ ==> world, Windows

 

 

总结:

1. 基于springboot内置的条件注解,开发一些应用,基于某种条件进行bean的注入还是很方便的,基本可以解决大部分常见场景需求。

2. 基于内置的条件注入注解的组合使用,可以实现多条件约束的bean的注入需求,只有多个条件注入条件都成立时,对应的bean才会被注入到spring的容器。

3. 内置注解不管单独用还是组合使用,都不能搞定你的应用需求,那么可以选择实现condition接口,基于@Conditional注解来自己完成条件注入的需求了。

 

到这里,看官们,你是否有结论了,关于前面提到的,session共享模块,基于配置参数开关量,灵活切换模块工作在header模式还是COOKIE模式?答案是可以的,至于如何实现,结合我这里的介绍,是能得到答案的。

 


推荐阅读
  • 如何在php文件中添加图片?
    本文详细解答了如何在php文件中添加图片的问题,包括插入图片的代码、使用PHPword在载入模板中插入图片的方法,以及使用gd库生成不同类型的图像文件的示例。同时还介绍了如何生成一个正方形文件的步骤。希望对大家有所帮助。 ... [详细]
  • PHP输出缓冲控制Output Control系列函数详解【PHP】
    后端开发|php教程PHP,输出缓冲,Output,Control后端开发-php教程概述全景网页源码,vscode如何打开c,ubuntu强制解锁,sts启动tomcat慢,sq ... [详细]
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的步骤和方法
    本文介绍了在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的详细步骤和方法。首先需要下载最新的Java SE Development Kit 9发行版,然后按照给出的Shell命令行方式进行安装。详细的步骤和方法请参考正文内容。 ... [详细]
  • 导出功能protectedvoidbtnExport(objectsender,EventArgse){用来打开下载窗口stringfileName中 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 如何实现JDK版本的切换功能,解决开发环境冲突问题
    本文介绍了在开发过程中遇到JDK版本冲突的情况,以及如何通过修改环境变量实现JDK版本的切换功能,解决开发环境冲突的问题。通过合理的切换环境,可以更好地进行项目开发。同时,提醒读者注意不仅限于1.7和1.8版本的转换,还要适应不同项目和个人开发习惯的需求。 ... [详细]
  • .NetCoreWebApi生成Swagger接口文档的使用方法
    本文介绍了使用.NetCoreWebApi生成Swagger接口文档的方法,并详细说明了Swagger的定义和功能。通过使用Swagger,可以实现接口和服务的可视化,方便测试人员进行接口测试。同时,还提供了Github链接和具体的步骤,包括创建WebApi工程、引入swagger的包、配置XML文档文件和跨域处理。通过本文,读者可以了解到如何使用Swagger生成接口文档,并加深对Swagger的理解。 ... [详细]
  • 在开发中,有时候一个业务上要求的原子操作不仅仅包括数据库,还可能涉及外部接口或者消息队列。此时,传统的数据库事务无法满足需求。本文介绍了Java中如何利用java.lang.Runtime.addShutdownHook方法来保证业务线程的完整性。通过添加钩子,在程序退出时触发钩子,可以执行一些操作,如循环检查某个线程的状态,直到业务线程正常退出,再结束钩子程序。例子程序展示了如何利用钩子来保证业务线程的完整性。 ... [详细]
  • 【爬虫】关于企业信用信息公示系统加速乐最新反爬虫机制
    ( ̄▽ ̄)~又得半夜修仙了,作为一个爬虫小白,花了3天时间写好的程序,才跑了一个月目标网站就更新了,是有点悲催,还是要只有一天的时间重构。升级后网站的层次结构并没有太多变化,表面上 ... [详细]
  • 渗透测试基础bypass绕过阻挡我们的WAF(下)
    渗透测试基础-bypass ... [详细]
  • 找到JDK下载URL当然去官网找了。目前最新的1.8的下载URL(RPM)如下:http:download.oracle.comotn-pubjavajdk8u161-b122f3 ... [详细]
author-avatar
手机用户2502860565
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有