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.rhkb.cn/news/325346.html

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

相关文章

数据结构绪论

1.数据&#xff1a;是客观事物的符号表示&#xff0c;是一切能够输入到计算机中&#xff0c;并被计算机处理的符号的总称。 数据元素&#xff1a;数据的基本单位。 数据项&#xff1a;是数据元素中独立的&#xff0c;最小的单位。 数据对象&#xff1a;是性质相同的数据元素…

OSTE-Web-Log-Analyzer:基于Python的Web服务器日志自动化分析工具

关于OSTE-Web-Log-Analyzer OSTE-Web-Log-Analyzer是一款功能强大的Web服务器日志自动化分析工具&#xff0c;该工具专为安全研究人员设计&#xff0c;能够使用Python Web日志分析工具&#xff08;Python Web Log Analyzer&#xff09;帮助广大研究人员以自动化的形式实现Web服…

云动态摘要 2024-05-12

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 [免费试用]即刻畅享自研SaaS产品 腾讯云 2024-04-25 涵盖办公协同、营销拓客、上云安全保障、数据分析处理等多场景 云服务器ECS试用产品续用 阿里云 2024-04-14 云服务器ECS试用产品续用…

Linux学习笔记7---仿STM32自建寄存器库

为了开发方便&#xff0c;ST 官方为 STM32F103 编写了一个叫做 stm32f10x.h 的文件&#xff0c;在这个文件里面定义了 STM32F103 所有外设寄存器。而有些芯片是没有这种寄存器库的&#xff0c;在没有的情况下要学会自己建立一个寄存器库。NXP 官方并没有为 I.MX6UL 编写类似 st…

Linux与windows网络管理

文章目录 一、TCP/IP1.1、TCP/IP概念TCP/IP是什么TCP/IP的作用TCP/IP的特点TCP/IP的工作原理 1.2、TCP/IP网络发展史1.3、OSI网络模型1.4、TCP/IP网络模型1.5、linux中配置网络网络配置文件位置DNS配置文件主机名配置文件常用网络查看命令 1.6、windows中配置网络CMD中网络常用…

springboot中mybatisplus注意事项

使用代码生成工具CodeGenerator 需要修改的内容 dsc.setUsername(“root”); mysql账号dsc.setPassword(“root”); mysql密码strategy.setInclude(“crm_edu”); 表名pc.setModuleName(“eduservice”); //模块名 package com.test.demo;import com.baomidou.mybatisplus.a…

Flink checkpoint 源码分析- Checkpoint snapshot 处理流程

背景 在上一篇博客中我们分析了代码中barrier的是如何流动传递的。Flink checkpoint 源码分析- Checkpoint barrier 传递源码分析-CSDN博客 最后跟踪到了代码org.apache.flink.streaming.runtime.io.checkpointing.CheckpointedInputGate#handleEvent 现在我们接着跟踪相应…

面向电商家居行业3D室内场景合成中的空间感知

本文主要介绍了3D场景合成技术在电商领域&#xff0c;尤其是家居家装行业的应用。它解释了如何使用3D场景合成创建逼真的室内设计&#xff0c;让消费者能够交互式地查看和体验产品&#xff0c;提高购物的趣味性和效率。文章提到了两种主要的3D室内场景生成算法&#xff1a;传统…

Electron | 桌面应用的开发神器

初探 Electron 教程将介绍 Electron 打包应用的全过程&#xff0c;从本地测试&#xff0c;打包&#xff0c;到 GitHub 自动化。讲解 Electron Forge 和 Electron Builder 的用法&#xff0c;以及如何在 GitHub Actions 中自动化生成和发布应用。 官方资源 Electron Document…

Vue.js【路由】

初识路由 提到路由&#xff08;Route&#xff09;&#xff0c;一般我们会联想到网络中常见的路由器&#xff08;Router&#xff09;&#xff0c;那么路由和路由器之间有什么关联呢&#xff1f;路由是指路由器从一个接口接收到数据&#xff0c;根据数据的目的地址将数据定向传送…

Linux开发--Linux内核开发移植

Linux内核开发移植 Linux内核版本变迁及其获得 Linux是最受欢迎的自由电脑操作系统内核&#xff0c; 是一个用C语言写成&#xff0c; 并且符合POSIX标准的类Unix操作系统 Linux是由芬兰黑客Linus Torvalds开发的&#xff0c; 目的是尝试在英特尔x86架构上提供自由免费的类Un…

IDEA及Maven配置代理及Maven中央仓库配置详解

一、配置代理 首先&#xff0c;需要本地开启代理入口&#xff0c;如图。 这个跟你使用代理软件有关。像我使用的是qv2ray。 其次&#xff0c;idea配置代理&#xff0c;如图。 1.1 idea配置代理 打开Settings&#xff0c;如图 1.2 maven配置代理 maven配置代理&#xff0c;修…

[单机]完美国际_V155_GM工具_VM虚拟机

[端游] 完美国际单机版V155一键端PC电脑网络游戏完美世界幻海凌云家园 本教程仅限学习使用&#xff0c;禁止商用&#xff0c;一切后果与本人无关&#xff0c;此声明具有法律效应&#xff01;&#xff01;&#xff01;&#xff01; 教程是本人亲自搭建成功的&#xff0c;绝对是…

易图讯三维电子沙盘-大数据处理服务

易图讯科技10名高级大数据工程师&#xff0c;高效、快速进行POI、DEM、高清卫星影像、地形地貌、路网、矢量地图等海量大数据处理服务。 免费专业提供POI、AOI、DEM、高清卫星影像、地形地貌、路网、矢量地图等海量大数据处理服务。 1年更新2次POI、高清卫星影像。

Unity基础

概述 基础知识 3D教学 数学计算公共类Mathf 练习: 三角函数 练习&#xff1a; Unity中的坐标系 Vector3向量 向量模长和单位向量 向量加减乘除 练习&#xff1a; 向量点乘 向量叉乘 向量插值运算 Quaternion四元数 为何要使用四元数 四元数是什么 四元数常用方法 四元数计算 练…

怎么制作流程图?介绍制作方法

怎么制作流程图&#xff1f;在日常生活和工作中&#xff0c;流程图已经成为我们不可或缺的工具。无论是项目规划、流程优化&#xff0c;还是学习理解复杂系统&#xff0c;流程图都能帮助我们更直观地理解和表达信息。然而&#xff0c;很多人可能并不清楚&#xff0c;其实制作流…

cmu15445 2023fall project3 详细过程(下)QUERY EXECUTION

QUERY EXECUTION task3/task4 Task #3 - HashJoin Executor and Optimization1、HashJoin1.1 思路1.2 代码 2 NestedLoopJoin优化为HashJoin2.1 思路2.2 代码 Task #4 Sort Limit Executors Top-N Optimization Window Functions1、Sort1.1 思路1.2 代码 2、Limit Executors2…

CCPD车牌检测识别数据集

CCPD 是一个在开源免费的中国城市车牌识别数据集。 1. 介绍 CCPD (Chinese City Parking Dataset, ECCV)是中国城市车牌数据集&#xff0c;共有两个CCPD2019和CCPD2020 数据集&#xff0c;总数据量约35W左右&#xff0c;可用于车牌检测和识别模型算法开发。 CCPD 发表的论文:…

从0开始学python(七)

目录 前言 1 break、continue和pass函数 1.1 break 1.2 continue 1.3 pass 2、序列的索引及切片操作 2.1字符串的索引和切片 2.1.1 字符串索引 2.1.2 字符串切片 总结 前言 上一篇文章我们介绍了python中的循环结构&#xff0c;包括for和while的使用。本章接着往下讲。…

excel常见图表大全

Excel图表是一种以图形形式呈现数据的工具&#xff0c;它将数字和统计信息转化为直观的视觉元素&#xff0c;如线图、柱状图、饼图等。这些图表可以帮助人们更容易地理解数据的趋势、关系和模式。 使用场景 Excel图表广泛应用于各个领域&#xff0c;包括&#xff1a; 商务分…