springMvc实现自定义全局异常捕获器(含ImmutableMap讲解)

在代码的编写的过程中,异常(bug)无处不在,有些使我们能明显意识到需要我们提前避免的,有些确实我们意料之外的。比如我们在做一个新增操作时,为了防止插入失败我们有时会这么做:

 try {
      checkgroupService.add(checkGroup,checkitemIds);
       setmealService.generaStaticSetmealDetailHtml(setmealService.findAll());
    } catch (Exception e) {
        e.printStackTrace();
          return new Result(false, MessageConstant.ADD_CHECKGROUP_FAIL);
    }

但每一个增删改查操作都进行这样的try..catch操作就产生了大量的冗余代码,同时返回的格式也并不统一。
或者有些我们意识不到的,比如角标越界,IO异常,或者空指针。这些报错就比较尴尬了,容易返回前端一面一个大大地500错误。这对我们的用户来说是非常不友好的。
那么今天我就给大家讲一下,相对来说更加合理的一种解决方案:
通过springMvc实现自定义全局异常捕获器

本文所用的pom,pojo,log4j均在文末提供

好了开始进入正文:

一、自定义异常流程:

1、自定义异常类型。

2、自定义错误代码及错误信息。

3、对于可预知的异常由程序员在代码中主动抛出,由SpringMVC统一捕获。

​ 可预知异常是程序员在代码中手动抛出本系统定义的特定异常类型,由于是程序员抛出的异常,通常异常信息比较齐全,程序员在抛出时会指定错误代码及错误信息,获取异常信息也比较方便。

4、对于不可预知的异常(运行时异常)由SpringMVC统一捕获Exception类型的异常。

​ 不可预知异常通常是由于系统出现bug、或一些不要抗拒的错误(比如网络中断、服务器宕机等),异常类型为RuntimeException类型(运行时异常)。

5、可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。

看不懂?
那就来张图或许更容易懂一些:
在这里插入图片描述

二、自定义一个异常类用来抛出我们自定的异常

package com.huawei.exception;

import com.huawei.domain.ResultCode;

public class ExceptionCoustm extends RuntimeException {
    private ResultCode resultCode;

    public ResultCode getResultCode() {
        return resultCode;
    }

    public ExceptionCoustm(ResultCode resultCode) {
//        super必须在构造的第一行,this也一样,所以两者不能同时出现
        super("错误代码:"+resultCode.code()+"错误信息:"+resultCode.message());
        this.resultCode = resultCode;

    }
}

先看这一段代码,很多小伙伴或许会疑惑:为什么要继承RuntimeException。答案很简单,为了在需要抛出异常的可以编译通过(不用try…catch)。同时需要在继承父类构造(message参数)来完成存储我们的错误信息。

二、定义静态调用类(可以省略)

package com.huawei.exception;

import com.huawei.domain.ResultCode;

public class ExceptionCast {
    private static  ResultCode resultCode;
    public static void cast(){
       throw new ExceptionCoustm(resultCode);
    }
}

这个类只是单纯为了抛出异常方便,可以直接在需要抛出异常的地方throw new ExceptionCoustm(参数)
我们抛出异常可以这样抛了:

if (id==null&& id.length()==0){
            ExceptionCast.cast(CommonCode.EXCEPTIONCOUSTM);
        }

三、定义捕获类(重点都在这了)

package com.huawei.exception;

import com.google.common.collect.ImmutableMap;
import com.huawei.domain.CommonCode;
import com.huawei.domain.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
@ResponseBody
public class ExceptionCatch {


    //    log4j打印日志
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);


    //使用EXCEPTIONS存放异常类型和错误代码的映射,
    private static ImmutableMap<Class<? extends Throwable>,ResultCode> EXCEPTIONS;


    //使用builder来构建一个异常类型和错误代码的异常集合
    protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCode> builder = ImmutableMap.builder();

    //    加载异常类型,可以加载n个
    static {
        builder.put(IOException.class,CommonCode.IO_EXCEPTION);
    }

    //捕获自定义异常类型
    @ExceptionHandler(ExceptionCoustm.class)
    public ResultCode exceptionCoustm(ExceptionCoustm exceptionCoustm){
        ResultCode resultCode = exceptionCoustm.getResultCode();
        LOGGER.error("catch exception:{}",exceptionCoustm.getMessage());
        exceptionCoustm.printStackTrace();
        return CommonCode.EXCEPTIONCOUSTM;
    }

    @ExceptionHandler(Exception.class)
    public ResultCode exception(Exception exception){
//        打印记录日志
        LOGGER.error("catch exception:{}",exception.getMessage());
        exception.printStackTrace();
        if (EXCEPTIONS==null){
//            ImmutableMap创建完成,不可在更改数据
            EXCEPTIONS=builder.build();
        }

//        获取异常类型,与定义相符的存入(resultCode)多态
        ResultCode resultCode = EXCEPTIONS.get(exception.getClass());
//
        if (resultCode!=null){
            return resultCode;
        }else {
//            如果没有匹配的类型返回其他类型
            return CommonCode.OTHER_EXCEPTION;
        }

    }


}

@ControllerAdvice :这里我们用的一种用途
可以实现三种功能:
1.实现全局异常捕获 结合 @ExceptionHandler(CustomException.class) 来指定要捕获的异常类型
2.实现全局数据绑定 结合@ModelAttribute 来实现全局绑定
3.全局数据预处理 比较鸡肋

 private static ImmutableMap<Class<? extends Throwable>,ResultCode> EXCEPTIONS;

说一下这个集合吧。这是一个goole的产品,ImmutableMap的特点的一旦创建不可改变,并且线程安全。同时这个集合是一个虚拟类(被abstract修饰),这段代码并没有进行实例化。
如果上面关于为什么用这个集合不明白的话可以参考这篇文章:https://blog.csdn.net/qq_27093465/article/details/53212577

protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCode> builder = ImmutableMap.builder();

定义ImmutableMap.Builder对象,方便存放异常类型。那么有的小伙伴又会问既然是Map集合为何不直接put,那岂不更省事?
当然不能!看下面这段ImmutableMap的源码:

 ImmutableMap() {
    }

    public final V put(K k, V v) {
        throw new UnsupportedOperationException();
    }

    public final V remove(Object o) {
        throw new UnsupportedOperationException();
    }

    public final void putAll(Map<? extends K, ? extends V> map) {
        throw new UnsupportedOperationException();
    }

    public final void clear() {
        throw new UnsupportedOperationException();
    }

put方法都抛出了异常,禁止了直接添加以保证集合的安全性,这也就说明了:ImmutableMap的特点的一旦创建不可改变,并且线程安全。

 static {
        builder.put(IOException.class,CommonCode.IO_EXCEPTION);
        //还可以放更多的映射关系
    }

使用静态代码块在编译时就将映射关系,保证在集合创建前,映射关系注入集合。当然也可以使用:

 Map<Class<? extends Throwable>ResultCode> EXCEPTIONS =
            new ImmutableMap.Builder<Class<? extends Throwable> ResultCode>().
                    put(IOException.class,CommonCode.IO_EXCEPTION);
                    .build();

把上边的三段代码去掉也可以。ImmutableMap就讲到这了。
@ExceptionHandler(ExceptionCoustm.class) 这个注解是用来捕获指定的异常类型(这里只捕获自定义的异常类型)。

ResultCode resultCode = EXCEPTIONS.get(exception.getClass());

将捕获的异常放入ImmutableMap判断是否有映射关系,根据是否有映射关系来返回不同的响应信息,集合中没有的就统一返回其他异常(一般定义为系统繁忙)
pojo文件:

package com.huawei.domain;
//自定义枚举类
public enum CommonCode implements ResultCode {
    EXCEPTIONCOUSTM(false,10000,"自定义异常"),
    OTHER_EXCEPTION(false,99999,"不可预测异常"),
    IO_EXCEPTION(false,20000,"IO异常");

    private Boolean success;
    private Integer code;
    private String message;

    CommonCode(Boolean success, Integer code, String message) {
        this.success = success;
        this.code = code;
        this.message = message;
    }

    @Override
    public boolean success() {
        return success;
    }

    @Override
    public int code() {
        return code;
    }

    @Override
    public String message() {
        return message;
    }
}
package com.huawei.domain;

public interface ResultCode {
    //操作是否成功,true为成功,false操作失败
    boolean success();
    //操作代码
    int code();
    //提示信息
    String message();

}

pom文件

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.huawei</groupId>
  <artifactId>ssm</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>ssm Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <dependencies>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.32</version>
    </dependency>
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.10</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>


    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.3</version>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.7</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.7</version>
    </dependency>
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.6</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>com.google.collections</groupId>
      <artifactId>google-collections</artifactId>
      <version>1.0</version>
    </dependency>

  </dependencies>
  <build>
    <!--maven插件-->
    <plugins>
      <!--jdk编译插件-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>utf-8</encoding>
        </configuration>
      </plugin>
      <!--tomcat插件-->
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <!-- tomcat7的插件, 不同tomcat版本这个也不一样 -->
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <!-- 通过maven tomcat7:run运行项目时,访问项目的端口号 -->
          <port>8080</port>
          <!-- 项目访问路径  本例:localhost:9090,  如果配置的aa, 则访问路径为localhost:9090/aa-->
          <path>/</path>
        </configuration>
      </plugin>

    </plugins>
  </build>
</project>

log4j

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %l [%30.30m]    %d     %n

### direct messages to file mylog.log ###
#log4j.appender.A=org.apache.log4j.FileAppender
#log4j.appender.A.File=D:\\mylog.log
#log4j.appender.A.append=false
#log4j.appender.A.layout=org.apache.log4j.PatternLayout
#log4j.appender.A.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=debug, stdout
##log4j.rootLogger=warn, stdout,A

springMvc实现自定义全局异常捕获器就先讲到这了,如有不对之处请指正,谢谢。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值