优雅的controller层设计
controller层设计
§ Controller 层逻辑
MVC架构下,我们的web工程结构会分为三层,自下而上是dao层,service层和controller层。controller层为控制层,主要处理外部请求。调用service层,一般情况下,controller层不应该包含业务逻辑,controller的功能应该有以下五点:
⑴、接收请求并解析参数
⑵、业务逻辑执行成功做出响应
⑶、异常处理
⑷、转换业务对象
⑸、调用 Service 接口
§ 普通写法
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service(@Validated @RequesBody UserRequestBo requestBo) throws Exception {Result result = new Result();// 参数校验if (StringUtils.isNotEmpty(requestBo.getId())|| StringUtils.isNotEmpty(requestBo.getType())|| StringUtils.isNotEmpty(requestBo.getName())|| StringUtils.isNotEmpty(requestBo.getAge())) {throw new Exception("必输项校验失败");} else {// 调用service更新user,更新可能抛出异常,要捕获try {int count = 0;User user = userService.queryUser(requestBo.getId());if (ObjectUtils.isEmpty(user)) {result.setCode("11111111111");result.setMessage("请求失败");return result;}// 转换业务对象UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);if ("02".equals(user.getType())) {// 退回修改的更新count = userService.updateUser(userDTO)}else if ("03".equals(user.getType())) {// 已生效状态,新增一条待复核count = userService.addUser(userDTO);}// 组装返回对象result.setData(count);result.setCode("00000000");result.setMessage("请求成功");} catch (Exception ex) {// 异常处理result.setCode("111111111111");result.setMessage("请求失败");}}return result;}
}
§ 优化思路
1、调用 Service 层接口
一般情况下,controller作为控制层调用service层接口,不应该包含任何业务逻辑,所有的业务操作,都放在service层实现,把controller层相关代码去掉
controller层就变成了:
@RestController
public class TestController {
@Autowired
private UserService userService;@PostMapping("/test")
public Result service(@Validated @RequesBody UserRequestBo requestBo) throws Exception {Result result = new Result();// 参数校验if (StringUtils.isNotEmpty(requestBo.getId())|| StringUtils.isNotEmpty(requestBo.getType())|| StringUtils.isNotEmpty(requestBo.getName())|| StringUtils.isNotEmpty(requestBo.getAge())) {throw new Exception("必输项校验失败");} else {// 调用service更新user,更新可能抛出异常,要捕获try {// 转换业务对象UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);int count = userService.updateUser(userDTO);// 组装返回对象result.setData(count);result.setCode("00000000");result.setMessage("请求成功");} catch (Exception ex) {// 异常处理result.setCode("EEEEEEEE");result.setMessage("请求失败");}}return result;
}
}
2、参数校验
其实大多数的参数校验就是判空或者空字符串,那么我们可以用@NotBlank等注解。在UserRequestBo类中name属性上加上@NotBlank注解
优化后如下:
@Data
public class UserRequestBo {@NotBlankprivate String id;@NotBlankprivate String type;@NotBlankprivate String name;@NotBlankprivate String age;
}
controller层就变成了:
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service( @Validated @RequesBody UserRequestBo requestBo) throws Exception {Result result = new Result();// 调用service更新user,更新可能抛出异常,要捕获try {// 转换业务对象UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);int count = userService.updateUser(userDTO);// 组装返回对象result.setData(count);result.setCode("00000000");result.setMessage("请求成功");} catch (Exception ex) {// 异常处理result.setCode("EEEEEEEE");result.setMessage("请求失败");}return result;}
}
备注:@NotNull、@NotBlank、@NotEmpty的区别,也适用于代码中的校验方法
@NotNull:平常用于基本数据的包装类(Integer,Long,Double等等),如果@NotNull 注解被使用在 String 类型的数据上,则表示该数据不能为 Null,但是可以为空字符串(“”),空格字符串(“ ”)等。
@NotEmpty:平常用于 String、Collection集合、Map、数组等等,@NotEmpty 注解的参数不能为 Null 或者 长度为 0,如果用在String类型上,则字符串也不能为空字符串(“”), 但是空格字符串(“ ”)不会被校验住。
@NotBlank:平常用于 String 类型的数据上,加了@NotBlank 注解的参数不能为 Null ,不能为空字符串(“”), 也不能会空格字符串(“ ”),多了一个trim()得到效果。
3、统一封装返回对象
代码中无论是业务成功或者失败,都需要封装返回对象,目前代码中都是哪里用到就在哪里进行封装
我们可以统一封装返回对象
优化后如下:
@Data
public class Result<T> {private String code;private String message;private T data;// 请求成功,指定datapublic static <T> Result<T> success(T data) {return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);}// 请求成功,指定data和指定messagepublic static <T> Result<T> success(String message, T data) {return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);}// 请求失败public static Result<?> failed() {return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);}// 请求失败,指定messagepublic static Result<?> failed(String message) {return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);}// 请求失败,指定code和messagepublic static Result<?> failed(String code, String message) {return new Result<>(code, message, null);}
}
controller层就变成了:
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service(@Validated @RequesBody UserRequestBo requestBo) throws Exception {// 调用service更新user,更新可能抛出异常,要捕获try {// 转换业务对象UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);int count = userService.updateUser(userDTO);// 组装返回对象Result.success(count);} catch (Exception ex) {// 异常处理Result.failed(ex.getMessage());}}
}
4、统一的异常捕获
Controller层和service存在大量的try-catch,都是重复代码并且看起来也不优雅。可以给controller层的方法加上切面来统一处理异常。用@ControllerAdvice注解(@RestControllerAdvice也可以),用来定义controller层的切面,添加@Controller注解的类中的方法执行都会进入该切面,同时我们可以使用@ExceptionHandler来对不同的异常进行捕获和处理,对于捕获的异常,我们可以进行日志记录,并且封装返回对象。
优化后如下:
// @RestControllerAdvice(basePackages = "com.ruoyi.web.controller.demo.test"), 指定包路径进行切面
// @RestControllerAdvice(basePackageClasses = TestController.class) , 指定Contrller.class进行切面
// @RestControllerAdvice 不带参数默认覆盖所有添加@Controller注解的类
@RestControllerAdvice(basePackageClasses = TestController.class)
public class TestControllerAdvice {@AutowiredHttpServletRequest httpServletRequest;private void logErrorRequest(Exception e){// 组装日志内容String logInfo = String.format("报错API URL: %S, error = ", httpServletRequest.getRequestURI(), e.getMessage());// 打印日志System.out.println(logInfo);}/*** {@code @RequestBody} 参数校验不通过时抛出的异常处理*/@ExceptionHandler({MethodArgumentNotValidException.class})public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {// 打印日志logErrorRequest(ex);// 组织异常信息,可能存在多个参数校验失败BindingResult bindingResult = ex.getBindingResult();StringBuilder sb = new StringBuilder("校验失败:");for (FieldError fieldError : bindingResult.getFieldErrors()) {sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");}return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), sb.toString());}/*** 业务层异常,如果项目中有自定义异常则使用自定义业务异常,如果没有,可以和其他异常一起处理** @param exception* @return*/@ExceptionHandler(RuntimeException.class)protected Result serviceException(RuntimeException exception) {logErrorRequest(exception);return Result.failed(exception.getMessage());}/*** 其他异常** @param exception* @return*/@ExceptionHandler({HttpClientErrorException.class, IOException.class, Exception.class})protected Result serviceException(Exception exception) {logErrorRequest(exception);return Result.failed(exception.getMessage());}
}
controller层就变成了:
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service( @Validated @RequesBody UserRequestBo requestBo) throws Exception {UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(requestBo, userDTO);// 调用service层接口int count = userService.updateUser(userDTO);//组装返回对象return Result.success(count);}
}
5、转换业务对象
代码中可能有很多个地方转换同一个业务对象,入参UserRequestBo可以转换为userDTO,可以理解为这是UserRequestBo的一个特性或者能力,我们可以参考充血模式的思想,在UserRequestBo中定义convertToUserDTO方法,我们的目的是转换业务对象,至于使用什么方式转换,调用方并不关心,现在使用的BeanUtils.copyProperties(),如果有一天想修改成使用Mapstruct来进行对象转换,只需要修改UserRequestBo的convertToUserDTO方法即可,不会涉及到业务代码的修改。
优化后代码:
@Data
public class UserRequestBo {@NotBlankprivate String id;@NotBlankprivate String type;@NotBlankprivate String name;@NotBlankprivate String age;/*** UserRequestBo对象为UserDTO* */public UserDTO convertToUserDTO(){UserDTO userDTO = new UserDTO();// BeanUtils.copyProperties要求字段名和字段类型都要保持一致,如果有不一样的字段,需要单独setBeanUtils.copyProperties(this, userDTO);userDTO.setType(this.getType());return userDTO;}
}
controller层就变成了:
@RestController
public class TestController {@Autowiredprivate UserService userService;@PostMapping("/test")public Result service(@Validated @RequesBody UserRequestBo requestBo) throws Exception {return Result.success(userService.updateUser(requestBo.convertToUserDTO()));}
}
优化结束,打完收工。
相关文章:
优雅的controller层设计
controller层设计 Controller 层逻辑 MVC架构下,我们的web工程结构会分为三层,自下而上是dao层,service层和controller层。controller层为控制层,主要处理外部请求。调用service层,一般情况下,contro…...
同步、通信、死锁
基础概念竞争资源引起两个问题死锁:因资源竞争陷入永远等待的状态饥饿:一个可运行程序由于其他进程总是优先于它,而被调用程序总是无限期地拖延而不能执行进程互斥:若干进程因相互争夺独占型资源而产生的竞争关系进程同步…...
【聚类】谱聚类解读、代码示例
【聚类】谱聚类详解、代码示例 文章目录【聚类】谱聚类详解、代码示例1. 介绍2. 方法解读2.1 先验知识2.1.1 无向权重图2.1.2 拉普拉斯矩阵2.2 构建图(第一步)2.2.1 ϵ\epsilonϵ 邻近法2.2.2 k 近邻法2.2.3 全连接法2.3 切图(第二步…...
最牛逼的垃圾回收期ZGC(1),简介
1丶什么是ZGC? ZGC是JDK 11中引入的一种可扩展的、低延迟的垃圾收集器。ZGC最主要的特点是:在非常短的时间内(一般不到10ms),就可以完成一次垃圾回收,而且这个时间是与堆的大小无关的。另外,ZGC支持非常大…...
微服务的Feign到底是什么
Feign是什么 分区是一种数据库优化技术,它可以将大表按照一定的规则分成多个小表,从而提高查询和维护的效率。在分区的过程中,数据库会将数据按照分区规则分配到不同的分区中,并且可以在分区中使用索引和其他优化技术来提高查询效…...
JavaScript 正则表达式
正则表达式(英语:Regular Expression,在代码中常简写为regex、regexp或RE)使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式。搜索模式可用于文本搜索和文本替换。什么是正则表达式?正则表达式是由一…...
【批处理脚本】-1.15-文件内字符串查找命令find
"><--点击返回「批处理BAT从入门到精通」总目录--> 共7页精讲(列举了所有find的用法,图文并茂,通俗易懂) 在从事“嵌入式软件开发”和“Autosar工具开发软件”过程中,经常会在其集成开发环境IDE(CodeWarrior,S32K DS,Davinci,EB Tresos,ETAS…)中,…...
【手撕面试题】JavaScript(高频知识点二)
目录 面试官:请你谈谈JS的this指向问题 面试官:说一说call apply bind的作用和区别? 面试官:请你谈谈对事件委托的理解 面试官:说一说promise是什么与使用方法? 面试官:说一说跨域是什么&a…...
Web学习1_HTML
在学校期间学的Web知识忘了一些,很多东西摸棱两可,现重新系统的学习一下。 首先下载安装完vsc后并下载拓展文件live server(模拟一个服务器) Auto Rename Tag(在写网页时,自动对齐前后标签)在设…...
华为OD机试真题Java实现【靠谱的车】真题+解题思路+代码(20222023)
靠谱的车 题目 程序员小明打了一辆出租车去上班。出于职业敏感,他注意到这辆出租车的计费表有点问题,总是偏大。 出租车司机解释说他不喜欢数字4,所以改装了计费表,任何数字位置遇到数字4就直接跳过,其余功能都正常。 比如: 23再多一块钱就变为25; 39再多一块钱变…...
【C++入门(下篇)】C++引用,内联函数,auto关键字的学习
前言: 在上一期我们进行了C的初步认识,了解了一下基本的概念还学习了包括:命名空间,输入输出以及缺省参数等相关的知识。今天我们将进一步对C入门知识进行学习,主要还需要大家掌握我们接下来要学习的——引用…...
基于合作型Stackerlberg博弈的考虑差别定价和风险管理的微网运行策略研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
2023年全国最新保安员精选真题及答案8
百分百题库提供保安员考试试题、保安职业资格考试预测题、保安员考试真题、保安职业资格证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 81.以下各组情形都属于区域巡逻中异常情况的是()。 A&#x…...
JavaScript高级程序设计读书分享之6章——MapSet
JavaScript高级程序设计(第4版)读书分享笔记记录 适用于刚入门前端的同志 Map 作为 ECMAScript 6 的新增特性,Map 是一种新的集合类型,为这门语言带来了真正的键/值存储机制。Map 的大多数特性都可以通过 Object 类型实现,但二者之间还是存在…...
改进的 A*算法的路径规划(路径规划+代码+毕业设计)
引言 近年来,随着智能时代的到来,路径规划技术飞快发展,已经形成了一套较为成熟的理论体系。其经典规划算法包括 Dijkstra 算法、A算法、D算法、Field D算法等,然而传统的路径规划算法在复杂的场景的表现并不如人意,例…...
Tina_Linux存储性能参考指南
OpenRemoved_Tina_Linux_存储性能_参考指南 1 概述 1.1 编写目的 介绍TinaLinux 存储性能的测试方法和历史数据,提供参考。 1.2 适用范围 Tina V3.0 及其后续版本。 1.3 相关人员 适用于TinaLinux 平台的客户及相关技术人员。 2 经验性能值 Flash 性能与实…...
NCRE计算机等级考试Python真题(四)
第四套试题1、以下选项中,不属于需求分析阶段的任务是:A.需求规格说明书评审B.确定软件系统的性能需求C.确定软件系统的功能需求D.制定软件集成测试计划正确答案: D2、关于数据流图(DFD)的描述,以下选项中正…...
LeetCode每周刷题总结2.20-2.26
本栏目记录本人每周写的力扣题的相关学习总结。 虽然开新的栏目都没有完成 70.爬楼梯 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 解题思路: 斐波那契数列递归 class Solution {…...
u盘里删除的文件可以恢复吗?分享解决方法
u盘里删除的文件可以恢复吗?不知道使用过U盘的你,是否遇到过这样的问题呢?其实正常情况下,在电脑中操作u盘,并删除相关的文件,删除的文件是不会经过电脑回收站的。想要找回就需要借助相关的恢复工具才能实现。下面小编给大家分享…...
十、vben框架如何使用table来写报表
在项目开发的过程中,有很多特殊的table样式,有的时候后端会用帆软来写报表,但是有的特殊的报表后端就不能支持实现了,那么前端是如何实现的呢,今天我们就来讲讲。 先上效果图: 本次使用的tsx组件来写的报表…...
jQuery:入门
jQuery 入门 Date: January 19, 2023 目标: 能够说出什么是 jQuery 能够说出 jQuery 的优点 能够简单使用 jQuery 能够说出 DOM 对象和 jQuery 对象的区别 jQuery 概述 JavaScript 库 仓库: 可以把很多东西放到这个仓库里面。找东西只需要到仓库里…...
实例3:树莓派呼吸灯
实例3:树莓派呼吸灯 实验目的 通过背景知识学习,了解digital与analog的区别。通过GPIO对外部LED灯进行呼吸控制,熟悉PWM技术。 实验要求 通过python编程,用GPIO控制LED灯,使之亮度逐渐增大,随后减小&am…...
android适配ipv6,请求慢?
先贴一篇我们经常能搜索到的解决方案: Android 在 4G 下访问 IPV6 慢的解决方案 文章很有参考意义,但也并不是所有请求慢的的原因! 本文是另一种原因,有兴趣就继续往下看一看. 使用的okhttp框架,模式支持ipv6和ipv4协议,但两种协议同时存在时…...
【LeetCode】剑指 Offer(10)
目录 题目:剑指 Offer 27. 二叉树的镜像 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 题目:剑指 Offer 28. 对称的二叉树 - 力扣࿰…...
学校AI视频行为分析监测系统 opencv
学校AI视频行为分析监测系统通过pythonopencv网络模型AI视频分析技术,学校AI视频行为分析监测算法对学校区域人员打架行为识别、跌倒行为识别、翻墙识别、人员聚众识别、攀高识别、抽烟行为等进行智能识别预警。OpenCV的全称是Open Source Computer Vision Library&…...
内存数据库的设计与实现(已在大型项目中应用)
一、概况 1、设计总图 组成,由Redis集群缓存,普通缓存,传统数据库,各类数据驱动 2、内存数据库的增删改查,分页查询 组成,由数据查询,分页查询,数据存储,数据修改,数据删除 3、内存数据库的驱动 组成,由驱动适配器,普通缓存驱动,Redis缓存驱动 4、内存数据库与…...
Linux基础命令-stat显示文件的状态信息
文章目录 stat 命令介绍 语法格式 基本参数 测试三个时间的变化过程 1)使用cat命令 2)使用echo命令 3)使用chmod命令 4)使用vim命令 参考实例 1)显示文件的状态信息 2)以简洁的形式显示状态信…...
SQL入门DEMO
单表查询 ● --查询订购日期在1996年7月1日至1996年7月15日之间的订单的订购日期、订单ID、客户ID和雇员ID等字段的值 ● --查询供应商的ID、公司名称、地区、城市和电话字段的值。条件是“地区等于华北”并且“联系人头衔等于销售代表”。 –查询供应商的ID、公司名称、地…...
辉光管时钟学习制作及开源软硬件工程
文章目录前言开源地址辉光管项目介绍辉光管的工作条件硬件部分部分介绍充电电路驱动电路不足之处软件部分总结前言 作为一个电子人,一直想做一个辉光管时钟,算是大学的一个心愿,终于在快要毕业前做了一个,下面把软件和硬件的部分…...
动手学深度学习(第二版)学习笔记 第三章
第三章 线性神经网络 代码:d2l-zh/pytorch/chapter_linear-networks 3.1 线性回归 3.1. 线性回归 — 动手学深度学习 2.0.0 documentation 解析解 线性回归的解可以用一个公式简单地表达出来,这类解叫作解析解(analytical solution&…...
黔江城乡建设委员会的网站/网站seo推广招聘
data与el的2种写法 1.el有2种写法 (1).new Vue时候配置el属性。 new Vue({el:#root, //第一种写法data:{name:尚硅谷}})(2).先创建Vue实例,随后再通过vm.$mount(#root)指定el的值。 const v new Vue({data:{name:尚硅谷} })console.log(v) v.$mount(#root) //第…...
树莓派做网站/互联网培训
本篇文章给大家带来的内容是关于MySQL如何通过实例化对象参数查询数据 ?(源代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。public static string QueryByEntity(T t) where T : new(){ string resultstr s…...
江苏专业做网站的公司/数字营销网站
转自:http://blog.chinaunix.net/uid-9688646-id-3087625.html 一个由C/C编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数参数值,局部变量的值等。其操作方式类似于数据结构中…...
如何进行账号推广/网站优化公司怎么选
Axure RP 8.0 函数对照表axureRP8.0中有很多丰富的函数,这些函数在制作高保真原型时会经常用到,这些函数能够提高我们的工作效率,但一定要注意不要一味地“沉迷”于函数的研究,不要过于纠结某些函数的使用,否则有可能会…...
网站关键词做的越多越好吗/南宁seo网站排名优化公司
ApiOperation(value "说明", httpMethod "get", notes "信息")ApiOperation.httpMethod.get改成httpMethod.GET即可...
wordpress is author/企业网站设计欣赏
MarketMonitor是2009年微软专业开发者大会上StreamInsight小组资深程序经理Torsten Grabs演讲中的第一个StreamInsight Demo。这个Demo演示了如何从一个随机的股票报价源中通过WHERE子句过滤出想要的结果。 准备工作 下载安装StreamInsight目前的最新版本StreamInsight 1.1&…...