前言

如今的Java Web项目多是以 MVC 模式构建的,通常我们都是将 Service 层的异常统一的抛出,包括自定义异常和一些意外出现的异常,以便进行事务回滚,而 Service 的调用者 Controller 则承担着异常处理的责任,因为他是与 Web 前端交互的最后一道防线,如果此时还不进行处理则用户会在网页上看到一脸懵逼的

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
at cn.keats.TestAdd.main(TestAdd.java:20)

这样做有以下几点坏处:

  1. 用户体验很不友好,可能用户会吐槽一句:这是什么XX网站。然后不再访问了
  2. 如果这个用户是同行,他不仅看到了项目代码的结构,而且看到抛出的是这么低级的索引越界异常,会被人家看不起
  3. 用户看到网站有问题,打电话给客服,客服找到产品,产品叫醒正在熟睡/打游戏的你。你不仅睡不好游戏打不了还得挨批评完事改代码

哎,真惨。因此一般我们采用的方法会是像这样:

异常处理

一般的Controller处理

Service代码如下:

@Service
public class DemoService {
public String respException(String param){
if(StringUtils.isEmpty(param)){
throw new MyException(ExceptionEnum.PARAM_EXCEPTION);
}
int i = 1/0;
return "你看不见我!";
}
}

Controller代码如下:

@RestController
public class DemoController {
@Autowired
private DemoService demoService; @PostMapping("respException")
public Result respException(){
try {
return Result.OK(demoService.respException(null));
} catch (MyException e){
return Result.Exception(e, null);
}
catch (Exception e) {
return Result.Error();
}
}
}

如果此时发送如下的请求:

http://localhost/respException

服务器捕捉到自定义的异常 MyException,而返回参数异常的Json串:

{
"code": 1,
"msg": "参数异常",
"data": null
}

而当我们补上参数:

http://localhost/respException?param=zhangsan

则服务器捕捉到 by zero 异常,会返回未知错误到前端页面

{
"code": -1,
"msg": "未知错误",
"data": null
}

这样就会在一定程度上规避一些问题,例如参数错误就可以让用户去修改其参数,当然这一般需要前端同学配合做页面的参数校验,必传参数都有的时候再向服务器发送请求,一方面减轻服务器压力,一方面将问题前置节省双方的时间。但是这样写有一个坏处就是所有的Controller方法中关于异常的部分都是一样的,代码非常冗余。且不利于维护,而且一些不太熟悉异常机制的同学可能会像踢皮球一样将异常抓了抛,抛完又抓回来,闹着玩呢。。。(笔者就曾经接手过一个跑路同学的代码这样处理异常,那简直是跟异常捉迷藏呢!可恨)我们在Service有全局事务处理,在系统中可以有全局的日志处理,这些都是基于Spring 的一大杀器:AOP(面向切面编程) 实现的,AOP是什么呢?

AOP

AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。如果说我们常用的OOP思想是从上到下执行业务流程的话,AOP就相当于在我们执行业务的时候横切一刀,如下图所示:

而Advice(通知)是AOP思想中重要的一个术语,分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around)五种。具体通知所表示的意义我这里不多赘述,网上关于Spring核心原理的讲解都会提及。而我们熟知的 Service 事务处理其实就是基于AOP AfterThrowing 通知实现的事务回滚。我们自定义的日志处理也可以根据不同的需求定制不同的通知入口。那既然如此,我们为何不自定义一个全局异常处理的切面去简化我们的代码呢?别急,且继续向下看。

优雅的处理异常

Spring 在 3.2 版本已经为我们提供了该功能: @ControllerAdvice 注解。此注解会捕捉Controller层抛出的异常,并根据 @ExceptionHandler 注解配置的方法进行异常处理。下面是一个示例工程,主要代码如下:

Result类:

此 Result 采用泛型的方式,便于在 Swagger 中配置方法的出参。使用静态工厂方法是的对象的初始化更加见名只意。对于不存在共享变量问题的 Error 对象,采用双重校验锁懒汉单例模式来节省服务器资源(当然最好还是整个项目运行中一直没有初始化它让人更加舒服。)

package cn.keats.util;

import cn.keats.exception.MyException;
import lombok.Data; /**
* 功能:统一返回结果,直接调用对应的工厂方法
*
* @author Keats
* @date 2019/11/29 18:20
*/
@Data
public class Result<T> {
private Integer code;
private String msg;
private T data; /**
* 功能:响应成功
*
* @param data 响应的数据
* @return woke.cloud.property.transformat.Result
* @author Keats
* @date 2019/11/30 8:54
*/
public static <T> Result<T> OK(T data){
return new Result<>(0, "响应成功", data);
} private static Result errorResult;
/**
* 功能:返回错误,此错误不可定制,全局唯一。一般是代码出了问题,需要修改代码
*
* @param
* @return Result
* @author Keats
* @date 2019/11/30 8:55
*/
public static Result Error(){
if(errorResult == null){
synchronized (Result.class){
if(errorResult == null){
synchronized (Result.class){
errorResult = new Result<>(-1, "未知错误", null);
}
}
}
}
return errorResult;
} /**
* 功能:返回异常,直接甩自定义异常类进来
*
* @param e 自定义异常类
* @param data 数据,如果没有填入 null 即可
* @return woke.cloud.property.transformat.Result<T>
* @author Keats
* @date 2019/11/30 8:55
*/
public static <T> Result<T> Exception(MyException e, T data){
return new Result<>(e.getCode(), e.getMsg(), data);
} /**
* 功能:为了方便使用,使用静态工厂方法创建对象。如需新的构造方式,请添加对应的静态工厂方法
*
* @author Keats
* @date 2019/11/30 8:56
*/
private Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}

自定义异常类:

package cn.keats.exception;

import lombok.Getter;

/**
* 功能:系统自定义异常类。继承自RuntimeException,方便Spring进行事务回滚
*
* @author Keats
* @date 2019/11/29 18:50
*/
@Getter
public class MyException extends RuntimeException{
private Integer code;
private String msg; public MyException(ExceptionEnum eEnum) {
this.code = eEnum.getCode();
this.msg = eEnum.getMsg();
}
}

异常代码枚举类:

package cn.keats.exception;

import lombok.AllArgsConstructor;
import lombok.Getter; /**
* 功能:异常枚举
*
* @author Keats
* @date 2019/11/29 18:49
*/
@Getter
@AllArgsConstructor
public enum ExceptionEnum {
PARAM_EXCEPTION(1,"参数异常"),
USER_NOT_LOGIN(2,"用户未登录"),
FILE_NOT_FOUND(3,"文件不存在,请重新选择"); private Integer code;
private String msg;
}

异常切面:

其中 @RestControllerAdvice 是spring 4.3 添加的新注解,是 @ControllerAdvice 和 @ResponseBody 的简写方式,类似与 @RestController 与 @Controller 的关系

package cn.keats.advice;

import cn.keats.exception.MyException;
import cn.keats.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice; /**
* 功能:全局异常处理器,Controller异常直接抛出
*
* @return
* @author Keats
* @date 2019/11/30 10:28
*/
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
/**
* 功能:其余非预先规避的异常返回错误
*
* @param e
* @return woke.cloud.property.transformat.Result
* @author Keats
* @date 2019/11/30 10:08
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result ResponseException(Exception e) {
log.error("未知错误,错误信息:", e);
return Result.Error();
} /**
* 功能:捕捉到 MyException 返回对应的消息
*
* @param e
* @return woke.cloud.property.transformat.Result
* @author Keats
* @date 2019/11/30 10:07
*/
@ExceptionHandler(value = MyException.class)
@ResponseBody
public Result myException(MyException e) {
log.info("返回自定义异常:异常代码:" + e.getCode() + "异常信息:" + e.getMsg());
return Result.Exception(e, null);
}
}

此时的 Controller 方法可以这样写:

package cn.keats.controller;

import cn.keats.service.DemoService;
import cn.keats.util.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
public class DemoController {
@Autowired
private DemoService demoService; @PostMapping("respException")
public Result respException(String param) throws Exception {
return Result.OK(demoService.respException(param));
} @PostMapping("respError")
public Result respError() throws Exception {
return Result.OK(demoService.respException(null));
}
}

省略的大部分的异常处理代码,使得我们只需要关注业务,一方面提高了代码质量,可阅读性,另一方面也提高了我们的开发速度。美哉!

启动项目,进行测试没有问题。

我是 Keats,一个热爱技术的程序员,鉴于技术有限,如果本文有什么纰漏或者兄台还有其他更好的建议/实现方式,欢迎留言评论,谢谢您!

Spring项目中优雅的异常处理的更多相关文章

  1. 如何在NodeJS项目中优雅的使用ES6

    如何在NodeJS项目中优雅的使用ES6 NodeJs最近的版本都开始支持ES6(ES2015)的新特性了,设置已经支持了async/await这样的更高级的特性.只是在使用的时候需要在node后面加 ...

  2. spring 项目中在类中注入静态字段

    有时spring 项目中需要将配置文件的属性注入到类的静态字段中 例如:文件上传 //文件上传指定上传位置 //resource-dev.properties 有如下参数 #upload UPLOAD ...

  3. java web项目(spring项目)中集成webservice ,实现对外开放接口

    什么是WebService?webService小示例 点此了解 下面进入正题: Javaweb项目(spring项目)中集成webservice ,实现对外开放接口步骤: 准备: 采用与spring ...

  4. Spring Boot 中关于自定义异常处理的套路!

    在 Spring Boot 项目中 ,异常统一处理,可以使用 Spring 中 @ControllerAdvice 来统一处理,也可以自己来定义异常处理方案.Spring Boot 中,对异常的处理有 ...

  5. Spring Boot2 系列教程(十三)Spring Boot 中的全局异常处理

    在 Spring Boot 项目中 ,异常统一处理,可以使用 Spring 中 @ControllerAdvice 来统一处理,也可以自己来定义异常处理方案.Spring Boot 中,对异常的处理有 ...

  6. spring项目中使用定时任务

    当我们希望在某个时间点来执行一些业务方法的时候就用到定时任务,在spring的项目中使用定时任务很简单.如下 第一步.加入jar包 <dependency> <groupId> ...

  7. spring项目中如何添加定时器以及在定时器中自动生成sprng注入对象

    最近做了一个java的项目,部门领导给了一套代码让我尽快掌握,说心里话本人真心不喜欢java的这种项目方式,各种配置各种xml文件简直头都大了,下面就将我遇到的其中一个我认为是坑的地方整理出来,希望能 ...

  8. JAVA项目中常用的异常处理情况总结

    JAVA项目中常用的异常知识点总结 1. java.lang.nullpointerexception这个异常大家肯定都经常遇到,异常的解释是"程序遇上了空指针",简单地说就是调用 ...

  9. 9. spring项目中web.xml详解解读

    引言:本篇博客的内容大部分都来自网上,有的是直接copy,有的是自己整理而来.既然网上已经有了,为啥还有自己copy呢? 感觉是因为网上的东西太散了或者是样式不够美观,所以自己又copy了一遍.如有侵 ...

随机推荐

  1. WWW网络请求

    采用WWW获取网络数据: (一)get 1)天气数据下载 private string weatherApi = "http://www.sojson.com/open/api/weathe ...

  2. FBV与CBV

    一.FBV FBV(function base views) 就是在视图里使用函数处理请求,也是我们最开始接触和使用的方式. urls.py urlpatterns = [ path('login/' ...

  3. 数据文件包解析工具类 RandomAccessFile

    public class ReadTextFile { public static void main(String[] args) { pic2txt(); parseFrmFile(); //ur ...

  4. SpringBoot + Redis 执行lua脚本

    1.背景 有时候,我们需要一次性操作多个 Redis 命令,但是 这样的多个操作不具备原子性,而且 Redis 的事务也不够强大,不支持事务的回滚,还无法实现命令之间的逻辑关系计算.所以,一般在开发中 ...

  5. MAVEN(一) 安装和环境变量配置

    一.安装步骤 1.安装maven之前先安装jdk,并配置好环境变量.确保已安装JDK,并 “JAVA_HOME” 变量已加入到 Windows 环境变量. 2.下载maven 进入官方网站下载网址如下 ...

  6. [考试反思]1102csp-s模拟测试98:苟活

    好像没有什么粘文件得分的必要(本来就没多少分了也丢不了多少了) 而且从这次开始小绿框不代表首杀而代表手速了2333 其实我挺菜的,牛一个frepoen送掉100分才跟我并列%%%milkfun mik ...

  7. NOIP模拟 3

    序列 以为自己很对然后光荣T20 (路丽姐姐原谅我吧)果然是把等比数列的定义记错了,一直没发现等比数列里的项是互成倍数的 正解首先就跟据上点初步判断两项能否成为子段的开头 然后处理出可能的最小公比(用 ...

  8. Spring Cloud gateway 六 Sentinel nacos存储动态刷新

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

  9. [转载]1.1 UiPath下载安装与激活

    一.UiPath下载 1.打开官网https://www.uipath.com.cn,点击开始试用 2.选择获取UiPath社区版 3.填写姓.名.电子邮箱地址.专业领域.职位.公司.公司所在行业.国 ...

  10. access,trunk,hybrid端口分析

    1.access 接收:当数据没有tag时打上pvidtag进入,若有则看是否与pvid相等,相等则接收,不想等则丢弃. 转发:看tag是否等于pvid,若等则去tag发送,否则不处理. 2.trun ...