Spring编程使用DDD的小把戏

场景

现在流行充血领域层,在原本只存储对象的java类中,增加一些方法去替代原本写在service层的crud,

但是例如service这种一般都是托管给spring的,我们使用的ORM也都托管给spring,这样方便在service层调用mybatis的mapper、或者jpa这种和spring结合的类,使用自动注入 @Resource、@Autowired 就行了,

但是像entity、vo这种保存信息的对象,一般都是直接new的,或者从数据库中查出然后被映射出来的,以及从前端传入到接口层的,它们并没有被spring托管,

如果在他们自身中想去使用注解引入spring相关的类,则无法实现,通过SpringContext.getBean这种硬编码感觉又不大雅观。

需求

我的应用场景是从Rest接口传入的参数,例如 save接口、update接口,通过 @RequestBody 传入的对象自身能不能直接调用Spring中的Service来实现自身的CRUD?这样自身可以完成一些验证以及数据库交互操作,并且代码也内聚在自身逻辑里,不会让service中充斥过多的CRUD代码,造成阅读代码上的不方便,每个参数中有自己的逻辑,并且不会很多,读起来就会相对清晰些。

举例

例如下面的代码,我想让参数自身就可以进行对数据库的交互,而不是将params传给service,然后在service中进行处理,

需要考虑的问题就是,如何让 SaveParams 这种前端接收的参数能被spring托管,这样就可以使用spring的bean了。

/*** 保存* @param params* @return*/
@PostMapping("save")
public RestResponse save(@RequestBody @Validated SaveParams params) {params.save();return success();
}/*** 更新* @param params* @return*/
@PostMapping("update")
public RestResponse update(@RequestBody @Validated UpdateParams params) {params.update();return success();
}

参数内部

参数内部是这个样子,但是这样肯定是不行的,用起来一定会报错,因为 SaveParams 并不属于spring的bean,

而是spring mvc的参数解析,将前端传入的参数构建成了 SaveParams

public class SaveParams extends Vo {@Autowiredprivate Service Service;/*** 保存自身*/@Transactionalpublic void save() {Service.save(this);}}

OK,知道问题所在,那么想让他成为spring的bean,第一步我们应该是给他头上也加上spring的注解,对吧?修改如下:

@Component 使用这个注解将类注册成spring bean的注解,为什么还要加上 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) ?

因为spring bean默认是单例模式,加上上面的Scope意味着每次都实例化创建一个新的bean,这符合我们的需求,

因为我们的接口每次收到请求都是一个全新的参数,自然不可能用单例的,每个接口都是自己的一个生命周期。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class SaveParams extends Vo {@Autowiredprivate Service Service;/*** 保存自身*/@Transactionalpublic void save() {Service.save(this);}}

问题2

结合上面的接口代码和对params增加的注解就可以直接使用了吗?显然不是,因为接口接收参数时,并不会因为我们的参数类加上了注解就帮我们注册成一个spring的bean给到我们,

我们需要自己去做这个事情,就是在接口接收到这个参数的时候,我们需要将他变成spring的bean,我们需要做一个拦截,做一个参数的篡改。

实现将接收参数变成spring bean

如何篡改?选时机即可,就选在接口刚接收到这个参数并解析完毕的时候,

我们利用spring给我们的拦截点 RequestBodyAdvice ,在接口接收完毕参数后,检验参数是否存在 @Component 注解,

如果存在,则使用 SpringUtils.getBean 从spring容器中新创建一个bean出来,

然后将之前的参数复制到这个bean里面来,这样这个bean既拿到了参数,又拿到了spring容器中自动注入的其他bean,二者结合,这个params就可以自己玩了,

这里实体之间的复制我使用了 BeanUtils.copyProperties(o, newObject, ignoreFields.toArray(new String[]{})); ,因为老参数里面的自动注入bean一定会是null,直接用会把spring新创建的给覆盖掉,所以这里要忽略一下自动注入的属性,

这时候我们把新的bean,返回回去,在接口里,params自己调用自己的save方法就不会报错了。

@Slf4j
@ControllerAdvice
public class DDDParamAdvice implements RequestBodyAdvice {...@Overridepublic Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {Object newObject = null;try {// 判断该对象类上是否存在 Component 注解if (o.getClass().isAnnotationPresent(Component.class)) {newObject = SpringUtils.getBean(o.getClass());Field[] fields = ReflectUtil.getFields(o.getClass());List<String> ignoreFields = new ArrayList<>();if (ArrayUtil.isNotEmpty(fields)) {for (Field field : fields) {if (field.isAnnotationPresent(Autowired.class)) {ignoreFields.add(field.getName());}}}BeanUtils.copyProperties(o, newObject, ignoreFields.toArray(new String[]{}));}} catch (Exception e) {log.error("DDDParamAdvice error", e);}return newObject != null ? newObject : o;}...}

还是有问题

到这一步虽然参数被转换成了spring中的bean,可以自己玩转了,但是并没有结束,

我在接口中使用 @Validated 验证时,发现验证不会通过,但是参数实际上是有值的,

通过排查我发现是因为spring的bean,cglib生成子类后,将属性拷贝一份到子类来,子类中的并没有值,

但是使用get方法还是可以正常获取到,具体情况如下图,看似没值,但是get其实有值,

在这里插入图片描述

在这里插入图片描述
真正的值其实被存储在 CGLIB$CALLBACK_1 中,并且可以看到Service其实也已经被注入:

在这里插入图片描述

如何解决

因为 @Validated 验证时机在 RequestBodyAdvice 之后,那么有没有一种办法在通过验证后,我们再将参数转成spring的bean呢?

答案当然是有,参考这篇blog:@Valid @Validated与先于AOP的执行顺序问题

使用AOP拦截controller方法,会让验证在前,AOP在后,所以我们使用AOP来替换参数为bean,而不使用 RequestBodyAdvice 就好了。

所以我们更改拦截如下:

@Component
@Aspect
public class DDDParamsAop {@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")public void aspect() {}@Around("aspect()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {if (joinPoint.getArgs().length == 1) {Object o = joinPoint.getArgs()[0];if (o.getClass().isAnnotationPresent(Component.class)) {Object newObject = SpringUtils.getBean(o.getClass());Field[] fields = ReflectUtil.getFields(o.getClass());List<String> ignoreFields = new ArrayList<>();if (ArrayUtil.isNotEmpty(fields)) {for (Field field : fields) {if (field.isAnnotationPresent(Autowired.class)) {ignoreFields.add(field.getName());}}}BeanUtils.copyProperties(o, newObject, ignoreFields.toArray(new String[]{}));joinPoint.getArgs()[0] = newObject;return joinPoint.proceed(joinPoint.getArgs());}}return joinPoint.proceed();}
}

结束问题

通过更改篡改参数时机,我们绕过了验证器的问题,并且让我们的参数可以自身注入spring其他bean完成相应的逻辑。

结语

上面编写的代码并不完善,例如对参数的拦截点,只拦截了PostMapping, 忽略的参数只忽略了 @Autowired 注解,基本只覆盖了我自身使用的场景,

但是基于这个原理,可以自行拓展,对更多的场景进行适配,完成对Service 代码和逻辑的拆解,将独立的功能封装到各自的实体领域中,方便代码管理与阅读,并且职责清晰。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/838381.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计网面试干货---带你梳理常考的面试题

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、HTTP和HTTPS的区别 1.安全性&#xff1a;HTTPS通过SSL/TLS协议对数据进行加密处理&#xff0c;有效防止数据在传输过…

函数栈帧的创建和销毁(详细理解)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;c语言课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 问题&#xff1a; 1.ebp&#xff0c;esp两个寄存器用来维护函数栈帧 2.main函数也一个函数&#…

Darknet+ros+realsenseD435i+yolo(ubuntu20.04)

一、下载Darknet_ros mkidr -p yolo_ws/src cd yolo_ws/src git clone --recursive https://github.com/leggedrobotics/darknet_ros.git #因为这样克隆的darknet文件夹是空的&#xff0c;将darknet_ros中的darknet的文件替换成如下 cd darknet_ros git clone https://github.…

2024年湖北省安全员-B证证模拟考试题库及湖北省安全员-B证理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年湖北省安全员-B证证模拟考试题库及湖北省安全员-B证理论考试试题是由安全生产模拟考试一点通提供&#xff0c;湖北省安全员-B证证模拟考试题库是根据湖北省安全员-B证最新版教材&#xff0c;湖北省安全员-B证大…

错误: 找不到或无法加载主类问题(已解决)

今天在虚拟机中安装了idea2023.2的版本&#xff0c;运行代码时发现错误找不到主类&#xff01; 直接说结论&#xff1a; 我先clean了一下target&#xff0c;然后重新build&#xff0c;发现maven报错了&#xff0c;idea2023.2默认使用了内置的maven&#xff0c;然后我切换了一下…

Linux基础之僵尸进程与孤儿进程

目录 一、僵尸进程 1.1 什么是僵尸进程 1.2 为什么要有僵尸状态 1.3 观察我们的僵尸状态 1.4 关于僵尸进程的小Tip 二、孤儿进程 2.1 什么是孤儿进程 一、僵尸进程 1.1 什么是僵尸进程 在上一篇文章中&#xff0c;我们有提到过进程的死亡状态的概念&#xff0c;而我们的…

Transformer 模型

文章目录 前言一、模型结构 前言 Transformer 模型是由谷歌在 2017 年提出并首先应用于机器翻译的神经网络模型结构。机器翻译的目标是从源语言&#xff08;Source Language&#xff09;转换到目标语言&#xff08;Target Language&#xff09;。Transformer 结构完全通过注意力…

IDC:2023年中国IT安全软件市场同比增长4.7%

IDC最新发布的《中国IT安全软件市场跟踪报告&#xff0c;2023H2》显示&#xff0c;2023年下半年中国IT安全软件市场厂商整体收入约为169.8亿人民币&#xff08;约合23.5亿元美元&#xff09;&#xff0c;同比上升2.7%。结合全年数据&#xff0c;2023全年中国IT安全软件市场规模…

Linux命令使用

一、ls tree clear 1.1 ls ls&#xff1a;查看当前目录下的文件名ls 目录名&#xff1a;查看指定目录下的文件名ls /&#xff1a;查看根目录下的文件名ls -a&#xff1a;查看当前目录下的所有文件名&#xff0c;包括隐藏文件ls -l&#xff1a;查看当前目录下文件的详细信息…

Java中的数组、Set、List、Map类型的互相转换总结

序言 数组、Set、List、Map是Java语言非常常用的几种数据类型&#xff0c;他们之间存在着千丝万缕的联系。关于底层的数据结构我这里就不再多说啦&#xff0c;直接从应用出发&#xff0c;总结他们之间的转换方法&#xff0c;并给出推荐方法。 大家可以点赞收藏等到需要的时候…

【JAVA】嵌入式软件工程师-2025校招必备-详细整理

一、Java 基础 1.JDK 和 JRE 有什么区别&#xff1f; jdk&#xff1a;java development kit jre&#xff1a;java runtime Environment jdk是面向开发人员的&#xff0c;是开发工具包&#xff0c;包括开发人员需要用到的一些类。 jre是java运行时环境&#xff0c;包括java虚拟机…

SVDD(Singing Voice Deepfake Detection,歌声深度伪造检测)挑战2024

随着AI生成的歌声快速进步&#xff0c;现在能够逼真地模仿自然人类的歌声并与乐谱无缝对接&#xff0c;这引起了艺术家和音乐产业的高度关注。歌声与说话声不同&#xff0c;由于其音乐性质和强烈的背景音乐存在&#xff0c;检测伪造的歌声成为了一个特殊的领域。 SVDD挑战是首个…

电脑常用的PDF阅读器-嗨动PDF编辑器!带你详细了解它

电脑常用的PDF阅读器-嗨动PDF编辑器&#xff01;在数字化信息爆炸的时代&#xff0c;PDF格式的文件因其易于打印和保留原始格式等优点&#xff0c;成为了人们日常工作和学习的常用格式。而对于PDF文件的处理&#xff0c;一款功能强大、操作简便的PDF阅读器是必不可少的。今天&a…

SprintBoot案例-增删改查

黑马程序员JavaWeb开发教程 文章目录 一、准备工作1. 准备数据库表1.1 新建数据库mytlias1.2 新建部门表dept1.3 新建员工表emp 2. 准备一个Springboot工程2.1 新建一个项目 3. 配置文件application.properties中引入mybatis的配置信息&#xff0c;准备对应的实体类3.1 引入myb…

weblogic 任意文件上传 CVE-2018-2894

一、漏洞简介 在 Weblogic Web Service Test Page 中存在一处任意文件上传漏洞&#xff0c; Web Service Test Page 在"生产模式"下默认不开启&#xff0c;所以该漏洞有一定限制。利用该 漏洞&#xff0c;可以上传任意 jsp 文件&#xff0c;进而获取服务器权限。 二…

[链表专题]力扣141, 142

1. 力扣141 : 环形链表 题 : 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾…

数据结构------二叉树经典习题1

博主主页: 码农派大星. 关注博主带你了解更多数据结构知识 1判断相同的树 OJ链接 这道题相对简单,运用我们常规的递归写法就能轻松写出 所以我们解题思路应该这样想: 1.如果p为空&#xff0c;q为空&#xff0c;那么就是两颗空树肯定相等 2.如果一个树为空另一棵树不为空那么…

2024年,诺基亚手机发售仅一天就售罄

在智能手机越来越同质化的今天&#xff0c;各家都只卷性能和相机&#xff0c;大火的 AI 对于咱来说好像实用性又不太大&#xff0c;机圈属实整的有点儿无聊。 不过在阿红这两天上网冲浪的时候&#xff0c;一个陌生又熟悉的名字闯入了我的视线&#xff0c;——诺基亚&#xff08…

维护表空间中的数据文件

目录 向表空间中添加数据文件 从表空间中删除数据文件 删除users表空间中的users02.dbf数据文件 对数据文件的自动扩展设置 Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 维护表空间中的数据文件主要包括向表空间中添…

8个迹象表明你需要一台新笔记本电脑,看一下你的笔记本是否有其中一个

序言 当你第一次打开你的笔记本电脑的盒子时,它会以最高性能运行,电池寿命更长,过热最小,资源使用效率高。然而,随着笔记本电脑的老化,它将不能满足预期用途。以下几个迹象表明,可能是时候寻找并投资一款新设备了。 你的设备不再具有预期用途 如果你的笔记本电脑不再…