Spring Boot实践——统一异常处理

Stella981
• 阅读 541

注解说明

@ControllerAdvice,是Spring3.2提供的新注解,从名字上可以看出大体意思是控制器增强。让我们先看看@ControllerAdvice的实现:

/**
 * Specialization of {@link Component @Component} for classes that declare
 * {@link ExceptionHandler @ExceptionHandler}, {@link InitBinder @InitBinder}, or
 * {@link ModelAttribute @ModelAttribute} methods to be shared across
 * multiple {@code @Controller} classes.
 *
 * <p>Classes with {@code @ControllerAdvice} can be declared explicitly as Spring
 * beans or auto-detected via classpath scanning. All such beans are sorted via
 * {@link org.springframework.core.annotation.AnnotationAwareOrderComparator
 * AnnotationAwareOrderComparator}, i.e. based on
 * {@link org.springframework.core.annotation.Order @Order} and
 * {@link org.springframework.core.Ordered Ordered}, and applied in that order
 * at runtime. For handling exceptions, an {@code @ExceptionHandler} will be
 * picked on the first advice with a matching exception handler method. For
 * model attributes and {@code InitBinder} initialization, {@code @ModelAttribute}
 * and {@code @InitBinder} methods will also follow {@code @ControllerAdvice} order.
 *
 * <p>Note: For {@code @ExceptionHandler} methods, a root exception match will be
 * preferred to just matching a cause of the current exception, among the handler
 * methods of a particular advice bean. However, a cause match on a higher-priority
 * advice will still be preferred to a any match (whether root or cause level)
 * on a lower-priority advice bean. As a consequence, please declare your primary
 * root exception mappings on a prioritized advice bean with a corresponding order!
 *
 * <p>By default the methods in an {@code @ControllerAdvice} apply globally to
 * all Controllers. Use selectors {@link #annotations()},
 * {@link #basePackageClasses()}, and {@link #basePackages()} (or its alias
 * {@link #value()}) to define a more narrow subset of targeted Controllers.
 * If multiple selectors are declared, OR logic is applied, meaning selected
 * Controllers should match at least one selector. Note that selector checks
 * are performed at runtime and so adding many selectors may negatively impact
 * performance and add complexity.
 *
 * @author Rossen Stoyanchev
 * @author Brian Clozel
 * @author Sam Brannen
 * @since 3.2
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {

    /**
     * Alias for the {@link #basePackages} attribute.
     * <p>Allows for more concise annotation declarations e.g.:
     * {@code @ControllerAdvice("org.my.pkg")} is equivalent to
     * {@code @ControllerAdvice(basePackages="org.my.pkg")}.
     * @since 4.0
     * @see #basePackages()
     */
    @AliasFor("basePackages")
    String[] value() default {};

    /**
     * Array of base packages.
     * <p>Controllers that belong to those base packages or sub-packages thereof
     * will be included, e.g.: {@code @ControllerAdvice(basePackages="org.my.pkg")}
     * or {@code @ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"})}.
     * <p>{@link #value} is an alias for this attribute, simply allowing for
     * more concise use of the annotation.
     * <p>Also consider using {@link #basePackageClasses()} as a type-safe
     * alternative to String-based package names.
     * @since 4.0
     */
    @AliasFor("value")
    String[] basePackages() default {};

    /**
     * Type-safe alternative to {@link #value()} for specifying the packages
     * to select Controllers to be assisted by the {@code @ControllerAdvice}
     * annotated class.
     * <p>Consider creating a special no-op marker class or interface in each package
     * that serves no purpose other than being referenced by this attribute.
     * @since 4.0
     */
    Class<?>[] basePackageClasses() default {};

    /**
     * Array of classes.
     * <p>Controllers that are assignable to at least one of the given types
     * will be assisted by the {@code @ControllerAdvice} annotated class.
     * @since 4.0
     */
    Class<?>[] assignableTypes() default {};

    /**
     * Array of annotations.
     * <p>Controllers that are annotated with this/one of those annotation(s)
     * will be assisted by the {@code @ControllerAdvice} annotated class.
     * <p>Consider creating a special annotation or use a predefined one,
     * like {@link RestController @RestController}.
     * @since 4.0
     */
    Class<? extends Annotation>[] annotations() default {};

}

该注解使用@Component注解,这样的话当我们使用context:component-scan扫描时也能扫描到。

通过类型的描述,可以得知几点:

  1.     @ControllerAdvice是一个@Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。
  2.     Spring4之前,@ControllerAdvice在同一调度的Servlet中协助所有控制器。Spring4已经改变:@ControllerAdvice支持配置控制器的子集,而默认的行为仍然可以利用。
  3.     在Spring4中, @ControllerAdvice通过annotations(), basePackageClasses(), basePackages()方法定制用于选择控制器子集

不过据经验之谈,只有配合@ExceptionHandler最有用,其它两个不常用。

如果单使用@ExceptionHandler,只能在当前Controller中处理异常。但当配合@ControllerAdvice一起使用的时候,就可以摆脱那个限制了。

实现方式

一、统一异常处理中心(Handler)

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSONObject;

/**
 * 统一异常处理
 * @ClassName: GlobalExceptionHandler 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年8月29日 下午2:19:42  
 *
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理所有不可知的异常
     * @Title: handleException 
     * @Description: TODO
     * @Date 2018年8月29日 下午2:21:10 
     * @author OnlyMate
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public JSONObject handleException(HttpServletRequest req, Exception e){
        JSONObject jsonObject = new JSONObject();        Throwable throwable = e.getCause();        logger.error(throwable.getMessage(), e);        if (throwable instanceof DuplicateSubmitException) {            jsonObject.put("resultCode", ((DuplicateSubmitException) throwable).getCode());             jsonObject.put("resultMsg", ((DuplicateSubmitException) throwable).getMsg());        } else {            jsonObject.put("resultCode", "9999");             jsonObject.put("resultMsg", "系统异常");        }
return jsonObject;
    }
    
    /**
     * 处理自定义异常(这里处理的异常是继承RunTimeException的自定义的异常)
     * @Title: handleBusinessException 
     * @Description: TODO
     * @Date 2018年8月29日 下午2:24:55 
     * @author OnlyMate
     * @param e
     * @return
     */
    @ResponseBody
    @ExceptionHandler(CustomBizRuntimeException.class)
    public JSONObject handleBusinessException(CustomBizRuntimeException e){
        logger.error("handleBusinessException ==> "+ e.getErrorMessage(), e);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("resultCode", "9999");
        jsonObject.put("resultMsg", "CustomBizRuntimeException 自定义业务异常类 ==> 系统异常");
        return jsonObject;
    }
    
    /**
     * 处理自定义异常(这里处理的异常是继承Exception的自定义的异常)
     * @Title: handleCommonException 
     * @Description: TODO
     * @Date 2018年8月29日 下午2:24:55 
     * @author OnlyMate
     * @param e
     * @return
     */
    @ResponseBody
    @ExceptionHandler(CustomCommonException.class)
    public JSONObject handleCommonException(CustomCommonException e){
        logger.error("handleCommonException ==> "+ e.getErrorMessage(), e);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("resultCode", "9999");
        jsonObject.put("resultMsg", "CustomCommonException 自定义普通异常类 ==> 系统异常");
        return jsonObject;
    }
}

二、自定义异常

CustomBizRuntimeException,继承RuntimeException,可用于涉及事务操作时抛该异常回滚事务

/**
 * 自定义业务异常类
 * @ClassName: CustomBizRuntimeException 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年9月1日 下午4:17:49  
 *
 */
public class CustomBizRuntimeException extends RuntimeException {
    private static final long serialVersionUID = -8021190492897458166L;
    private String errorMessage = "CustomBizRuntimeException 自定义业务异常类 ";
    
    public String getErrorMessage() {
        return this.errorMessage;
    }
}

CustomCommonException,普通自定义异常

/**
 * 自定义普通异常类
 * @ClassName: CustomCommonException 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年9月1日 下午4:17:49  
 *
 */
public class CustomCommonException extends Exception {
    private static final long serialVersionUID = -8021190492897458166L;
    private String errorMessage = "CustomCommonException 自定义普通异常类";
    
    public String getErrorMessage() {
        return this.errorMessage;
    }
}

三、编写抛出对应异常的Controller

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.onlymate.springboot.exception.CustomBizRuntimeException;
import com.onlymate.springboot.exception.CustomCommonException;

@Controller
@RequestMapping(value="/exception")
public class ExceptionController {
    private Logger logger = LoggerFactory.getLogger(HelloWorldController.class);
    
    /**
     * 抛出CustomCommonException异常,然后处理该异常
     * @Title: index1 
     * @Description: TODO
     * @Date 2018年9月1日 下午4:42:58 
     * @author OnlyMate
     * @return
     * @throws CustomCommonException
     */
    @ResponseBody
    @RequestMapping("/index1")
    public String index1() throws CustomCommonException {
        try {
            String str = null;
            logger.info(str.toString());
        } catch (Exception e) {
            throw new CustomCommonException();
        }
        return "Success";
    }
    
    /**
     * 抛出CustomBizRuntimeException异常,然后处理该异常
     * @Title: index2 
     * @Description: TODO
     * @Date 2018年9月1日 下午4:43:25 
     * @author OnlyMate
     * @return
     */
    @ResponseBody
    @RequestMapping("/index2")
    public String index2() {
        try {
            String str = null;
            logger.info(str.toString());
        } catch (Exception e) {
            throw new CustomBizRuntimeException();
        }
        return "Success";
    }
    
    /**
     * 抛出Exception异常,然后处理该异常
     * @Title: index3 
     * @Description: TODO
     * @Date 2018年9月1日 下午4:43:49 
     * @author OnlyMate
     * @return
     */
    @ResponseBody
    @RequestMapping("/index3")
    public String index3() {
        String str = null;
        logger.info(str.toString());
        return "Success";
    }
}

效果图

一、抛出CustomCommonException异常效果图

页面访问:http://localhost:8088/springboot/exception/index1

Spring Boot实践——统一异常处理

Spring Boot实践——统一异常处理

二、抛出CustomBizRuntimeException异常效果图

页面访问:http://localhost:8088/springboot/exception/index2

Spring Boot实践——统一异常处理

Spring Boot实践——统一异常处理

三、抛出Exception异常效果图

页面访问:http://localhost:8088/springboot/exception/index3

 Spring Boot实践——统一异常处理

Spring Boot实践——统一异常处理

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这