当前位置:网站首页>这是我见过写得最烂的Controller层代码...
这是我见过写得最烂的Controller层代码...
2022-07-25 08:38:00 【石杉的架构笔记】
文章来源:https://c1n.cn/XAu85
目录
接口定义
Controller 规范
实现代码
接口定义
工作中,少不了要定义各种接口,系统集成要定义接口,前后台掉调用也要定义接口。接口定义一定程度上能反应程序员的编程功底。
列举一下工作中我发现大家容易出现的问题:
| 返回格式不统一
同一个接口,有时候返回数组,有时候返回单个;成功的时候返回对象,失败的时候返回错误信息字符串。
工作中有个系统集成就是这样定义的接口,真是辣眼睛。这个对应代码上,返回的类型是 map,json,object,都是不应该的。
实际工作中,我们会定义一个统一的格式,就是 ResultBean,分页的有另外一个 PageResultBean。
错误范例:
//返回map可读性不好,尽量不要
@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang) {
}
// 成功返回boolean,失败返回string,大忌
@PostMapping("/delete")
public Object delete(long id, String lang) {
try {
boolean result = configService.delete(id, local);
return result;
} catch (Exception e) {
log.error(e);
return e.toString();
}
}| 没有考虑失败情况
一开始只考虑成功场景,等后面测试发现有错误情况,怎么办,改接口呗,前后台都改,劳民伤财无用功。
错误范例:
//不返回任何数据,没有考虑失败场景,容易返工
@PostMapping("/update")
public void update(long id, xxx) {
}| 出现和业务无关的输入参数
如 lang 语言,当前用户信息 都不应该出现参数里面,应该从当前会话里面获取。后面讲 ThreadLocal 会说到怎么样去掉。除了代码可读性不好问题外,尤其是参数出现当前用户信息的,这是个严重问题。
错误范例:
// (当前用户删除数据)参数出现lang和userid,尤其是userid,大忌
@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang, String userId) {
}| 出现复杂的输入参数
一般情况下,不允许出现例如 json 字符串这样的参数,这种参数可读性极差。应该定义对应的 bean。
错误范例:
// 参数出现json格式,可读性不好,代码也难看
@PostMapping("/update")
public Map<String, Object> update(long id, String jsonStr) {
}| 没有返回应该返回的数据
例如,新增接口一般情况下应该返回新对象的 id 标识,这需要编程经验。新手定义的时候因为前台没有用就不返回数据或者只返回 true,这都是不恰当的。别人要不要是别人的事情,你该返回的还是应该返回。
错误范例:
// 约定俗成,新建应该返回新对象的信息,只返回boolean容易导致返工
@PostMapping("/add")
public boolean add(xxx) {
//xxx
return configService.add();
}很多人都觉得技术也很简单,没有什么特别的地方,但是,实现这个代码框架之前,就是要你的接口的统一的格式 ResultBean,aop 才好做。
有些人误解了,之前那篇文章说的都不是技术,重点说的是编码习惯工作方式,如果你重点还是放在什么技术上,那我也帮不了你了。
同样,如果我后面的关于习惯和规范的帖子,你重点还是放在技术上的话,那是丢了西瓜捡芝麻,有很多贴还是没有任何技术点呢。
附上 ResultBean,没有任何技术含量:
@Data
public class ResultBean<T> implements Serializable {
private static final long serialVersionUID = 1L;
public static final int SUCCESS = 0;
public static final int FAIL = 1;
public static final int NO_PERMISSION = 2;
private String msg = "success";
private int code = SUCCESS;
private T data;
public ResultBean() {
super();
}
public ResultBean(T data) {
super();
this.data = data;
}
public ResultBean(Throwable e) {
super();
this.msg = e.toString();
this.code = FAIL ;
}
}统一的接口规范,能帮忙规避很多无用的返工修改和可能出现的问题。能使代码可读性更加好,利于进行aop和自动化测试这些额外工作。大家一定要重视。
Controller 规范
上面 2 段代码,第一个是原生态的,第 2 段是我指定了接口定义规范,使用 AOP 技术之后最终交付的代码,从 15 行到 1 行,自己感受一下。接下来说说大家关注的 AOP 如何实现。
先说说 Controller 规范,主要的内容是就是接口定义里面的内容,你只要遵循里面的规范,controller 就问题不大。
除了这些,还有另外的几点:
所有函数返回统一的 ResultBean/PageResultBean 格式,原因见我的接口定义这个贴。没有统一格式,AOP 无法玩。
ResultBean/PageResultBean 是 controller 专用的,不允许往后传!
Controller 做参数格式的转换,不允许把 json,map 这类对象传到 services 去,也不允许 services 返回 json、map。一般情况下!写过代码都知道,map,json 这种格式灵活,但是可读性差,如果放业务数据,每次阅读起来都比较困难。定义一个 bean 看着工作量多了,但代码清晰多了。
参数中一般情况不允许出现 Request,Response 这些对象,主要是可读性问题。一般情况下。
不需要打印日志,日志在 AOP 里面会打印,而且我的建议是大部分日志在 Services 这层打印。规范里面大部分是不要做的项多,要做的比较少,落地比较容易。
ResultBean 定义带泛型,使用了 lombok。
@Data
public class ResultBean<T> implements Serializable {
private static final long serialVersionUID = 1L;
public static final int NO_LOGIN = -1;
public static final int SUCCESS = 0;
public static final int FAIL = 1;
public static final int NO_PERMISSION = 2;
private String msg = "success";
private int code = SUCCESS;
private T data;
public ResultBean() {
super();
}
public ResultBean(T data) {
super();
this.data = data;
}
public ResultBean(Throwable e) {
super();
this.msg = e.toString();
this.code = FAIL;
}
}AOP 代码:主要就是打印日志和捕获异常,异常要区分已知异常和未知异常,其中未知的异常是我们重点关注的。
可以做一些邮件通知啥的,已知异常可以再细分一下,可以不同的异常返回不同的返回码:
/**
* 处理和包装异常
*/
public class ControllerAOP {
private static final Logger logger = LoggerFactory.getLogger(ControllerAOP.class);
public Object handlerControllerMethod(ProceedingJoinPoint pjp) {
long startTime = System.currentTimeMillis();
ResultBean<?> result;
try {
result = (ResultBean<?>) pjp.proceed();
logger.info(pjp.getSignature() + "use time:" + (System.currentTimeMillis() - startTime));
} catch (Throwable e) {
result = handlerException(pjp, e);
}
return result;
}
private ResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) {
ResultBean<?> result = new ResultBean();
// 已知异常
if (e instanceof CheckException) {
result.setMsg(e.getLocalizedMessage());
result.setCode(ResultBean.FAIL);
} else if (e instanceof UnloginException) {
result.setMsg("Unlogin");
result.setCode(ResultBean.NO_LOGIN);
} else {
logger.error(pjp.getSignature() + " error ", e);
//TODO 未知的异常,应该格外注意,可以发送邮件通知等
result.setMsg(e.toString());
result.setCode(ResultBean.FAIL);
}
return result;
}
}AOP 配置:关于用 java 代码还是 xml 配置,这里我倾向于 xml 配置,因为这个会不定期改动。
<!-- aop -->
<aop:aspectj-autoproxy />
<beans:bean id="controllerAop" class="xxx.common.aop.ControllerAOP" />
<aop:config>
<aop:aspect id="myAop" ref="controllerAop">
<aop:pointcut id="target"
expression="execution(public xxx.common.beans.ResultBean *(..))" />
<aop:around method="handlerControllerMethod" pointcut-ref="target" />
</aop:aspect>
</aop:config>现在知道为什么要返回统一的一个 ResultBean 了:
为了统一格式
为了应用 AOP
为了包装异常信息
分页的 PageResultBean 大同小异,大家自己依葫芦画瓢自己完成就好了。
贴一个简单的 controller(左边的箭头表示 AOP 拦截了)。请对比吐槽我见过的最烂的 java 代码里面原来的代码查看,没有对比就没有伤害。

最后说一句,先有统一的接口定义规范,然后有 AOP 实现,先有思想再有技术。技术不是关键,AOP 技术也很简单,这个帖子的关键点不是技术,而是习惯和思想,不要捡了芝麻丢了西瓜。
网络上讲技术的贴多,讲习惯、风格的少,这些都是我工作多年的行之有效的经验之谈。
------------- END -------------
扫码免费获取600+页石杉老师原创精品文章汇总PDF
原创技术文章汇总
点个在看你最好看边栏推荐
- 【keras bug】Tensor is unhashable if Tensor equality is enabled. Instead, use tensor.experimental_ ref(
- 【无标题】
- Recursive call to print every bit of an integer
- @Feignclient annotated interface. You may not get instances with @autowired
- Source code of short video live broadcast system
- Svg creative underline style JS special effect
- JD cloud and Forrester consulting released a hybrid cloud report that cloud Nativity has become a new engine driving industrial development
- Swift initializer and optional chain
- Redis分片集群
- 第3章业务功能开发(修改线索,数据回显并修改数据)
猜你喜欢

Network solutions for Alibaba cloud services

Redis learning

25 Ph.D. degrees revoked

Data warehouse ODS, DWD floor, 220616, HM,

CentOS 8.2 MySQL installation (xshell6)

Redis best practices

Implementation of depth first and breadth first traversal of binary tree

JD cloud and Forrester consulting released a hybrid cloud report that cloud Nativity has become a new engine driving industrial development

Online shopping E-commerce mall system based on jsp+servlet+mysql+

QA robot sequencing model
随机推荐
PHP reports an error: classes\phpexcel\cell php Line(594) Invalid cell coordinate ESIGN1
@Feignclient annotated interface. You may not get instances with @autowired
Implementation of depth first and breadth first traversal of binary tree
Raspberry pie 3b ffmpeg RTMP streaming
NVIDIA programmable reasoning accelerator tensorrt learning notes (II) - practical operation
@Autowired的使用
Ensembles in RNA counts data in TCGA_ ID to gene_ Method of ID
efcore在Saas系统下多租户零脚本分表分库读写分离解决方案
Tips for improving code sustainability, take connectto method as an example.
Chapter 3 business function development (query clues)
【黑马程序员】Redis学习笔记003:Redis事务
Apartment repair reporting system (idea, SSM, MySQL)
Swift initializer and optional chain
第3章业务功能开发(修改线索,数据回显并修改数据)
Source code of pet adoption management system implemented by ssm+jsp+mysql
JS typewriter animation JS special effect plug-in autotyperjs
Qt|QLable多行展示时更改行间距
[dark horse programmer] redis learning notes 004: master-slave replication + sentinel mode + cluster
[dark horse programmer] redis learning notes 002: persistence: RDB and AOF
[dark horse programmer] redis learning notes 001: introduction to redis + five basic data types