前言

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

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4

at cn.keats.TestAdd.main(TestAdd.java:20)

这样做有以下几点坏处:

用户体验很不友好,可能用户会吐槽一句:这是什么XX网站。然后不再访问了

如果这个用户是同行,他不仅看到了项目代码的结构,而且看到抛出的是这么低级的索引越界异常,会被人家看不起

用户看到网站有问题,打电话给客服,客服找到产品,产品叫醒正在熟睡/打游戏的你。你不仅睡不好游戏打不了还得挨批评完事改代码

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

异常处理

一般的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{

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 ResultOK(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* @author Keats

* @date 2019/11/30 8:55

*/

public static ResultException(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,一个热爱技术的程序员,鉴于技术有限,如果本文有什么纰漏或者兄台还有其他更好的建议/实现方式,欢迎留言评论,谢谢您!

文章来源: www.cnblogs.com,作者:后青春期的Keats,版权归原作者所有,如需转载,请联系作者。

原文链接:https://www.cnblogs.com/keatsCoder/p/11967890.html

java spring异常处理_Spring项目中优雅的异常处理相关推荐

  1. python切面异常处理_Spring项目中优雅的异常处理

    Spring项目中优雅的异常处理 前言 如今的Java Web项目多是以 MVC 模式构建的,通常我们都是将 Service 层的异常统一的抛出,包括自定义异常和一些意外出现的异常,以便进行事务回滚, ...

  2. 重新学习Spring一--Spring在web项目中的启动过程

    1 Spring 在web项目中的启动过程 Spring简介 Spring 最简单的功能就是创建对象和管理这些对象间的依赖关系,实现高内聚.低耦合.(高内聚:相关性很强的代码组成,既单一责任原则:低耦 ...

  3. junit5_在Java 8之前的项目中使用JUnit 5

    junit5 这篇文章演示了如何在Java 8之前的项目中使用JUnit 5,并解释了为什么它是一个好主意. JUnit 5至少需要Java 8作为运行时环境,因此您想将整个项目更新为Java8.但是 ...

  4. java 获取sqlsession_获取Java的MyBatis框架项目中的SqlSession的方法

    从XML中构建SqlSessionFactory从XML文件中构建SqlSessionFactory的实例非常简单.这里建议你使用类路径下的资源文件来配置. String resource = &qu ...

  5. java项目使用junit_在Java 8之前的项目中使用JUnit 5

    java项目使用junit 这篇文章演示了如何在Java 8之前的项目中使用JUnit 5,并解释了为什么它是一个好主意. JUnit 5至少需要Java 8作为运行时环境,因此您想将整个项目更新为J ...

  6. 在Java 8之前的项目中使用JUnit 5

    这篇文章演示了如何在Java 8之前的项目中使用JUnit 5,并解释了为什么它是一个好主意. JUnit 5至少需要Java 8作为运行时环境,因此您想将整个项目更新为Java8.但是有时由于某些原 ...

  7. ThinkPHP6项目基操(13.实战部分 项目中的自定义异常处理总结 错误页面API错误)

    项目中的自定义异常处理总结 错误页面&API错误 前言 一.异常分类 1. 控制器找不到 2. 方法找不到 3. 请求资源不存在 4. 系统內部异常.HTTP异常等 二.异常处理 1. 前置处 ...

  8. Java Spring Security示例教程中的2种设置LDAP Active Directory身份验证的方法

    LDAP身份验证是全球范围内最流行的企业应用程序身份验证机制之一,而Active Directory (Microsoft针对Windows的LDAP实现)是另一种广泛使用的LDAP服务器. 在许多项 ...

  9. java jndi使用_Java项目中使用JNDI连接数据库

    因为写的大作业经常用到数据库连接 所以自己写了个数据库连接的类 package DB_Link_info;/* * 数据库链接信息 */public class DB_link_Info {publi ...

最新文章

  1. 程序员的“凡尔赛文学”大赏!
  2. 9 个可以快速掌握的 Java 性能调优技巧
  3. Boostrap的按钮下拉菜单
  4. SAP 财务会计结构
  5. Java千万数据导入mysql_java之5分钟插入千万条数据
  6. php密码怎么用md5,如何使用PHP使用MD5加密此密码?
  7. Smack 4.3.2 发布,XMPP(jabber) 的 Java 客户端类库
  8. windows下C语言多线程编程
  9. activiti高亮显示图片_Excel全自动甘特图,工作进度显示,极简设计快手套用
  10. php字符串去重和去空,php去空格
  11. 2021年计算机保研面试题
  12. 一年太久,只争朝夕——2018年终总结
  13. linux的核心安装下的命令行,linux lilo命令参数及用法详解--linux安装核心载入,开机管理程...
  14. 微商扫码发货管理系统开发
  15. c++ 常用总结(三)
  16. 手机短号(C语言————AC)
  17. 微积分学 \ 线性代数 \ 概率论与数理统计
  18. 疯狂足球——Android手机游戏开发(实习报告)
  19. 练习八:利用有限状态机进行时序逻辑的设计
  20. 2012最新个税税率表及速算扣除数

热门文章

  1. 抗住 60 亿次攻击,起底阿里云安全的演进之路 | 问底中国 IT 技术演进
  2. 打破硬件边界,华为EMUI分布式技术如何连接万物
  3. 为什么要进入 5G 时代?
  4. 火速收藏!Android 开发者必会的内存泄漏指南
  5. 华为拟安装“俄版安卓”;百度回应内部贪腐;Android Studio 3.5 RC2 发布 | 极客头条...
  6. 万字长文详解如何用 Python 玩转 OpenGL | CSDN 博文精选
  7. 西部数据中断与华为合作;苹果首次参加天猫 618;罗永浩第三次出质锤子股权 | 极客头条...
  8. 马云:遇见好老板很重要;锤子上海法人变更;摩拜更名美团单车 | 极客头条...
  9. Linux 进入 5.0 时代!
  10. 想了解“云+AI”究竟怎么“玩儿”,还要看这里、看这里!