AOP实现

我们需要在AOP里面统一处理异常,包装成相同的对象ResultBean给前台。

ResultBean定义

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实现

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;
}
}

晓风轻建议

对于未知异常,给相关责任人发送邮件通知,第一时间知道异常,实际工作中非常有意义。

返回码定义

关于怎么样定义返回码,个人经验是 粗分异常,不能太细。如没有登陆返回-1,没有权限返回-2,参数校验错误返回1,其他未知异常返回-99等。需要注意的是,定义的时候,需要调用方单独处理的异常需要和其他区分开来,比如没有登陆这种异常,调用方不需要单独处理,前台调用请求的工具类统一处理即可。而参数校验异常或者没有权限异常需要调用方提示给用户,没有权限可能除了提示还会附上申请权限链接等,这就是异常的粗分。

WARNING

返回码不要太细,千万不要标题为空返回1,描述为空返回2,字段X非法返回3,这种定义看上去很专业,实际上会把前台和自己累死。

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。请对比 程序员你为什么这么累?里面原来的代码查看,没有对比就没有伤害。

/**
* 配置对象处理器
*
* @author 晓风轻 https://github.com/xwjie/PLMCodeTemplate
*/

@RequestMapping("/config")
@RestController
public class ConfigController {

private final ConfigService configService;

public ConfigController(ConfigService configService) {
this.configService = configService;
}

@GetMapping("/all")
public ResultBean<Collection<Config>> getAll() {
return new ResultBean<Collection<Config>>(configService.getAll());
}

/**
* 新增数据, 返回新对象的id
*
* @param config
* @return
*/

@PostMapping("/add")
public ResultBean<Long> add(Config config) {
return new ResultBean<Long>(configService.add(config));
}

/**
* 根据id删除对象
*
* @param id
* @return
*/

@PostMapping("/delete")
public ResultBean<Boolean> delete(long id) {
return new ResultBean<Boolean>(configService.delete(id));
}

@PostMapping("/update")
public ResultBean<Boolean> update(Config config) {
configService.update(config);
return new ResultBean<Boolean>(true);
}
}

为什么不用ExceptionHandler

这是我发帖后问的最多的一个问题,很多人说为什么不用 ControllerAdvice + ExceptionHandler 来处理异常?觉得是我在重复发明轮子。首先,这2这都是AOP,本质上没有啥区别。而最重要的是ExceptionHandler只能处理异常,而我们的AOP除了处理异常,还有一个很重要的作用是打印日志,统计每一个controller方法的耗时,这在实际工作中也非常重要和有用的特性!

TIP

就算你使用ExceptionHandler,也不要成功和失败的时候返回不一样的数据格式,否则前台很难写好代码。

为什么不用Restful风格

这也是问的比较多的一个问题。如果你提供的接口是给前台调用的,而你又在实际工作中前后台开发都负责的话,我觉得你应该不会问这个问题。诚然,restful风格的定义很优雅,但是在前台调用起来却非常的麻烦,前台通过返回的ResultBean的code来判断成功失败显然比通过http状态码来判断方便太多。第2个原因,使用http状态码返回出错信息也值得商榷。系统出错了返回400我觉得没有问题,但一个参数校验不通过也返回400,我个人觉得是很不合理的,是无法接受的。

晓风轻总结

有统一的接口定义规范,然后有AOP实现,先有思想再有技术。技术不是关键,AOP技术也很简单,这个帖子的关键点不是技术,而是习惯和思想,不要捡了芝麻丢了西瓜。网络上讲技术的贴多,讲习惯、风格的少,这些都是我工作多年的行之有效的经验之谈,望有缘人珍惜。

晓风轻技术小站