quarkus依赖注入之八:装饰器(Decorator)

2023-11-12 01:11

本文主要是介绍quarkus依赖注入之八:装饰器(Decorator),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本篇是《quarkus依赖注入》系列的第八篇,目标是掌握quarkus实现的一个CDI特性:装饰器(Decorator)
  • 提到装饰器,熟悉设计模式的读者应该会想到装饰器模式,个人觉得下面这幅图很好的解释了装饰器模式,左下角的红框是关键点:自己的send方法中,先调用父类的send(也就是被装饰类的send),然后才是自己的业务逻辑
54
  • quarkus也支持装饰器模式,通过注解DecoratorDelegate实现,今天咱们就通过实战掌握如何在quarks框架下通过装饰器扩展应用
  • quarkus是按照CDI的标准来支持装饰器模式的,下图来自官方文档

image-20220409210857612

  • 接下来进入实战环节

实战功能说明

  • 网上讲述装饰器模式的文章中,有个咖啡价格的例子非常经典,如下图所示:
  1. 一杯意式浓缩咖啡(Espresso)价格3美元
  2. 拿铁(Latte)由意式浓缩+牛奶组成,价格是意式浓缩和牛奶之和,即5美元
  3. 焦糖玛奇朵(CaramelMacchiato)由拿铁+焦糖组成,价格比拿铁多了焦糖的1美元,即6美元
  4. 每种咖啡都是一种对象,价格由getPrice方法返回
流程图 - 2022-04-09T165959.292
  • 在上述场景中,当咖啡的内容不断丰富,咖啡价格也要做相应调整,装饰器的作用是让代码优雅的应对变化,对内代码整洁低耦合,对外保持统一接口getPrice

  • 装饰器模式本身并不是本篇的重点,咱们还是聚焦quarkus下的装饰器功能:在咖啡价格的基础上,通过装饰器计算出拿铁的价格

  • 接下来开始编码

编码实战

  • 首先定义接口Coffee.java,不论是意式浓缩、拿铁、还是其他种类,对外都称之为Coffee,都有getPrice方法
package com.bolingcavalry.decorator;public interface Coffee {/*** 咖啡名称* @return*/String name();/*** 当前咖啡的价格* @return*/int getPrice();
}
  • 然后是最基础的意式浓缩咖啡,非常简单的一个bean,定价3美元,这里有个细节要注意:name方法中写死了字符串Espresso,而没用getClass().getSimpleName(),这是因为在quarkus容器中,Espresso的bean并非Espresso类型,而是动态生成的代理类,所以getClass返回的类不是Espresso
package com.bolingcavalry.decorator.impl;import com.bolingcavalry.decorator.Coffee;import javax.enterprise.context.ApplicationScoped;/*** 意式浓缩咖啡,价格3美元*/
@ApplicationScoped
public class Espresso implements Coffee {@Overridepublic String name() {return "Espresso";}@Overridepublic int getPrice() {return 3;}
}
  • 接下来就是重点了,拿铁,由意式浓缩+牛奶组成,代码如下,有几处要注意的地方稍后会提到
package com.bolingcavalry.decorator.impl;import com.bolingcavalry.decorator.Coffee;
import io.quarkus.arc.Priority;
import io.quarkus.logging.Log;import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;@Decorator
@Priority(11)
public class Latte implements Coffee {/*** 牛奶价格:2美元*/private static final int MILK_PRICE = 2;@Delegate@InjectCoffee delegate;@Overridepublic String name() {return "Latte";}@Overridepublic int getPrice() {// 将Latte的代理类打印出来,看quarkus注入的是否正确Log.info("Latte's delegate type : " + this.delegate.name());return delegate.getPrice() + MILK_PRICE;}
}
  • 上述代码有以下几处要注意
  1. 先明确目的:我们设计Latte这个bean,本意是通过装饰器模式来装饰Espresso,因此才会用到quarkus的装饰器功能
  2. 使用quarkus的装饰器功能时,有两件事必须要做:装饰类要用注解Decorator修饰,被装饰类要用注解Delegate修饰
  3. 因此,Latte被注解Decorator修饰,Latte的成员变量delegate是被装饰类,要用注解Delegate修饰,
  4. Latte的成员变量delegate并未指明是Espresso,quarkus会选择Espresso的bean注入到这里
  5. 在getPrice方法中打印出delegate.name方法的返回值,验证delegate的身份,以确认quarkus注入的是否正确
  6. 注解Priority很重要,留在接下来的CaramelMacchiato类(焦糖玛奇朵)写完后再说清楚
  • 接下来是CaramelMacchiato类(焦糖玛奇朵),有几处要注意的地方稍后会说明
package com.bolingcavalry.decorator.impl;import com.bolingcavalry.decorator.Coffee;
import io.quarkus.arc.Priority;
import io.quarkus.logging.Log;import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;/*** 焦糖玛奇朵:拿铁+焦糖*/
@Decorator
@Priority(10)
public class CaramelMacchiato implements Coffee {/*** 焦糖价格:1美元*/private static final int CARAMEL_PRICE = 1;@Delegate@InjectCoffee delegate;@Overridepublic String name() {return "CaramelMacchiato";}@Overridepublic int getPrice() {// 将CaramelMacchiato的代理类打印出来,看quarkus注入的是否正确Log.infov("CaramelMacchiato's delegate type : " + this.delegate.name());return delegate.getPrice() + CARAMEL_PRICE;}
}
  • CaramelMacchiato代码的逻辑和Latte的差不多,都用了注解Decorator和Delegate,目的是为了做Latte的装饰器
  • 重点关注的是成员变量delegate,其类型、名称、注解,都和Latte的delegate一模一样:
@Delegate
@Inject
Coffee delegate;

重要知识点

  • 看到这里,相信您也发现了问题所在:CaramelMacchiato和Latte都有成员变量delegate,其注解和类型声明都一模一样,那么,如何才能保证Latte的delegate注入的是Espresso,而CaramelMacchiato的delegate注入的是Latte呢?
  • 此刻就是注解Priority在发挥作用了,CaramelMacchiato和Latte都有注解Priority修饰,属性值却不同,属性值越大越接近原始类Espresso,如下图,所以,Latte装饰的就是Espresso,CaramelMacchiato装饰的是Latte
流程图 - 2022-04-09T203421.135

单元测试类

  • 最后是单元测试类,成员变量的类型是Coffee,也就是说quarkus容器会自动注入装饰过的CaramelMacchiato类型的bean,而testDecoratorPrice方法中断言coffee.getPrice()的值等于6,如果注入caffee的bean不是CaramelMacchiato类型,断言就会失败
package com.bolingcavalry;import com.bolingcavalry.decorator.Coffee;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;import javax.inject.Inject;@QuarkusTest
public class DecoratorTest {@InjectCoffee coffee;@Testpublic void testDecoratorPrice() {Assertions.assertEquals(6, coffee.getPrice());}
}

验证

  • 执行单元测试,如下图,单元测试通过表示coffee注入的是CaramelMacchiato类型的bean,再看右侧的日志,CaramelMacchiato的成员变量delegate是Latte类型,Latte的成员变量delegate是Espresso类型,都按照咱们的预期准确注入了
    67

  • 紧接着再做个尝试:将Latte的注解Priority的属性值改小,小于CaramelMacchiato的10,如下图红框,如此一来,CaramelMacchiato的优先级更大,因此更靠近Espresso,由它去装饰Espresso,Latte离Espresso更远,所以它装饰的是CaramelMacchiato

image-20220409205649353
  • 再次运行单元测试,如下图,首先测试依旧能通过,这个好理解,无论装饰逻辑怎么变,最终的bean的getPrice返回值,都是意式浓缩+牛奶+焦糖的价格之和,然后在看右侧日志信息,果然,CaramelMacchiato注入的成员变量是Espresso,Latte注入的成员变量是CaramelMacchiato

68

  • 至此,装饰器的编码实战已完成,相信您可以在应用中用熟练使用装饰器来扩展bean能力,并且保持与原有bean之间的代码低耦合

与拦截器的不同

  • 如果您看过《拦截器》一文,应该会发现,同样的功能用拦截器也能实现,那为何还要多出个装饰器呢?
  • 其实网上也有类似的讨论,首先是Stack Overflow上分析,一个高赞的观点是:通常情况下,一个装饰器被用于一个特定类上,而拦截器用于拦截多个类
  • 这篇2012年的关于CDI的文章《Interceptors and Decorators tutorial》中的对比更好理解:

55

  • 个人理解:
  1. 拦截器适合做一些通用的事情,例如日志、异常处理等,可以为多个bean服务
  2. 装饰器适合做特定的事情,例如本篇的演示代码中,计算价格是被装饰类的特性,其他bean没有这个功能,所以装饰器也只能用在,作为核心功能的增强或者完善

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

这篇关于quarkus依赖注入之八:装饰器(Decorator)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/394077

相关文章

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Python依赖库的几种离线安装方法总结

《Python依赖库的几种离线安装方法总结》:本文主要介绍如何在Python中使用pip工具进行依赖库的安装和管理,包括如何导出和导入依赖包列表、如何下载和安装单个或多个库包及其依赖,以及如何指定... 目录前言一、如何copy一个python环境二、如何下载一个包及其依赖并安装三、如何导出requirem

Python如何快速下载依赖

《Python如何快速下载依赖》本文介绍了四种在Python中快速下载依赖的方法,包括使用国内镜像源、开启pip并发下载功能、使用pipreqs批量下载项目依赖以及使用conda管理依赖,通过这些方法... 目录python快速下载依赖1. 使用国内镜像源临时使用镜像源永久配置镜像源2. 使用 pip 的并

SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解)

《SpringBoot项目注入traceId追踪整个请求的日志链路(过程详解)》本文介绍了如何在单体SpringBoot项目中通过手动实现过滤器或拦截器来注入traceId,以追踪整个请求的日志链... SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排

python安装whl包并解决依赖关系的实现

《python安装whl包并解决依赖关系的实现》本文主要介绍了python安装whl包并解决依赖关系的实现,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录一、什么是whl文件?二、我们为什么需要使用whl文件来安装python库?三、我们应该去哪儿下

Spring AI Alibaba接入大模型时的依赖问题小结

《SpringAIAlibaba接入大模型时的依赖问题小结》文章介绍了如何在pom.xml文件中配置SpringAIAlibaba依赖,并提供了一个示例pom.xml文件,同时,建议将Maven仓... 目录(一)pom.XML文件:(二)application.yml配置文件(一)pom.xml文件:首

使用maven依赖详解

《使用maven依赖详解》本文主要介绍了Maven的基础知识,包括Maven的简介、仓库类型、常用命令、场景举例、指令总结、依赖范围、settings.xml说明等,同时,还详细讲解了Maven依赖的... 目录1. maven基础1.1 简介1.2 仓库类型1.3 常用命令1.4 场景举例1.5 指令总结

SQL注入漏洞扫描之sqlmap详解

《SQL注入漏洞扫描之sqlmap详解》SQLMap是一款自动执行SQL注入的审计工具,支持多种SQL注入技术,包括布尔型盲注、时间型盲注、报错型注入、联合查询注入和堆叠查询注入... 目录what支持类型how---less-1为例1.检测网站是否存在sql注入漏洞的注入点2.列举可用数据库3.列举数据库

Spring核心思想之浅谈IoC容器与依赖倒置(DI)

《Spring核心思想之浅谈IoC容器与依赖倒置(DI)》文章介绍了Spring的IoC和DI机制,以及MyBatis的动态代理,通过注解和反射,Spring能够自动管理对象的创建和依赖注入,而MyB... 目录一、控制反转 IoC二、依赖倒置 DI1. 详细概念2. Spring 中 DI 的实现原理三、

python中poetry安装依赖

《python中poetry安装依赖》本文主要介绍了Poetry工具及其在Python项目中的安装和使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前言1. 为什么pip install poetry 会造成依赖冲突1.1 全局环境依赖混淆:1