全局异常处理

全局异常处理

1. ControllerAdvice注解结合ExceptionHandle

1.定义

@ControllerAdvice注解是Spring3.2中新增的注解,学名是Controller增强器,作用是给Controller控制器添加统一的操作或处理

2.@ControllerAdvice的作用

对于@ControllerAdvice,我们比较熟知的用法是结合@ExceptionHandler用于全局异常的处理,但其作用不止于此。ControllerAdvice拆开来就是Controller Advice,关于Advice,在Spring的AOP中,是用来封装一个切面所有属性的,包括切入点和需要织入的切面逻辑。这里ControllerAdvice也可以这么理解,其抽象级别应该是用于对Controller进行切面环绕的,而具体的业务织入方式则是通过结合其他的注解来实现的。@ControllerAdvice是在类上声明的注解,其用法主要有三点:

  • 结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。
  • 结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的。
  • 结合方法型注解@ModelAttribute,表示其注解的方法将会在目标Controller方法执行之前执行。

从上面的讲解可以看出,@ControllerAdvice的用法基本是将其声明在某个bean上,然后在该bean的方法上使用其他的注解来指定不同的织入逻辑。不过这里@ControllerAdvice并不是使用AOP的方式来织入业务逻辑的,而是Spring内置对其各个逻辑的织入方式进行了内置支持。

官方解释

`Indicates the annotated class assists a “Controller”. Serves as a specialization of @Component, allowing for implementation classes to be autodetected through classpath scanning. It is typically used to define @ExceptionHandler, @InitBinder, and @ModelAttribute methods that apply to all @RequestMapping methods. One of annotations(), basePackageClasses(), basePackages() or its alias value() may be specified to define specific subsets of Controllers to assist. When multiple selectors are applied, OR logic is applied - meaning selected Controllers should match at least one selector. The default behavior (i.e. if used without any selector), the @ControllerAdvice annotated class will assist all known Controllers. Note that those checks are done at runtime, so adding many attributes and using multiple strategies may have negative impacts (complexity, performance). Since: 3.2 Author: Rossen Stoyanchev, Brian Clozel, Sam Brannen

上面的意思是带有 @ControllerAdvice的类,作为 @Controller类 的组成部分,通常用来定义 @ExceptionHandler, @InitBinder, 和 @ModelAttribute 这样的方法,作用的返回由这个注解的 annotations(), basePackageClasses(), basePackages() 或者别名 value()筛选,如果同时设置了这几个值,因为多个条件之间的关系是 OR的关系,所以满足任何一个条件的 Controller 都会被作用。`

在启动应用之后,被@ExceptionHandler、@InitBinder和@ModelAttribute注解的方法都会作用在被@RequestMappping注解的方法上

3.@ExceptionHandle

  • @ExceptionHandler的作用主要在于声明一个或多个类型的异常,当符合条件的Controller抛出这些异常之后将会对这些异常进行捕获,然后按照其标注的方法的逻辑进行处理,从而改变返回的视图信息。

4、demo

package com.example.base.exception;

import com.example.base.entity.ResultDTO;
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 javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.SocketTimeoutException;

/**
 * @author: 
 * @create: 2020-03-12 09:47
 * @description: 全局异常处理
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
    * @Description: 处理控制针异常
    * @Author: Xingxing ma
    * @Date: 2020-03-12 15:57
    */
    @ExceptionHandler(value =NullPointerException.class)
    @ResponseBody
    public static ResultDTO nullPointerHandler(HttpServletRequest req, NullPointerException e){
        logger.error(e.getMessage(),e);
        return new ResultDTO(CodeConstant.CODE_CLIENT_REQ_ERROR,"空指针异常");
    }

    /**
    * @Description: 处理IOException
    * @Author: 
    * @Date: 2020-03-12 15:57
    */
    @ExceptionHandler(value =IOException.class)
    @ResponseBody
    public ResultDTO IoExceptionHandler(HttpServletRequest req, IOException e){
        logger.error(e.getMessage(),e);
        return new ResultDTO(CodeConstant.CODE_CLIENT_REQ_ERROR,"网络异常");
    }

    /**
    * @Description: 处理格式转换异常
    * @Date: 2020-03-12 15:57
    */
    @ExceptionHandler(value =NumberFormatException.class)
    @ResponseBody
    public ResultDTO umberFormaExceptionHandler(HttpServletRequest req, NumberFormatException e){
       logger.error(e.getMessage(),e);
//        return new ResultDTO(CodeConstant.CODE_CLIENT_REQ_ERROR,"数字格式转换失败",e.getMessage());
        return new ResultDTO(CodeConstant.CODE_CLIENT_REQ_ERROR,"数字格式转换失败");
    }

    /**
    * @Description: 连接超时异常
    * @Date: 2020-03-12 16:05
    */
    @ExceptionHandler(value =SocketTimeoutException.class)
    @ResponseBody
    public ResultDTO socketTimeoutExceptionHandler(HttpServletRequest req, SocketTimeoutException e){
        logger.error(e.getMessage(),e);
        return  new ResultDTO(CodeConstant.CODE_TIMEOUT,"连接超时");
    }

    /**
    * @Description: 如没有任何异常匹配,统一按Exception处理程序异常
    * @Author: Xingxing ma
    * @Date: 2020-03-12 15:32
    */
    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public ResultDTO exceptionHandler(HttpServletRequest req, Exception e){
        logger.error(e.getMessage(),e);
        return new ResultDTO(CodeConstant.CODE_DEFAULT_FAIL,"程序异常");
    }
}


  • @ExceptionHandler标注的多个方法分别表示只处理特定的异常。这里需要注意的是当Controller抛出的某个异常多个@ExceptionHandler标注的方法都适用时,Spring会选择最具体的异常处理方法来处理,也就是说

    @ExceptionHandler(Exception.class)这里标注的方法优先级最低,只有当其它方法都不适用时,才会来到这里处理。

  • @controllerAdvice注解的CommonExceptionHandler 类中,exceptionHandler()方法上加了

    @ExceptionHandler(Exception.class)注解,表示处理当控制器抛出Exception异常时,将会委托该方法来处理。

  • @controllerAdvice最为实用的一个场景就是将所有@ExceptionHandler方法收集到一个类中,这样所有的异常都能在一个地方进行一致处理。

  • @ControllerAdvice默认所有控制层的抛出的异常都会在这个类进行处理
    @ControllerAdvice(annotations = {PCInfoController .class}) 配置你需要拦截的控制器,
    @ControllerAdvice(basePackages = “com.demo”) 配置你需要路径下的控制器

2.Aop处理全局异常

自定义异常类

public class LmServiceException extends RuntimeException {


    private static final long serialVersionUID = 1L;

    private Integer errCode;

    public LmServiceException() {
        super();
    }

    public LmServiceException(String errMsg) {
        super(errMsg);
    }

    public LmServiceException(String errMsg, Throwable t) {
        super(errMsg, t);
    }

    public LmServiceException(int errCode, String errMsg) {
        this(errMsg);
        this.errCode = errCode;
    }

    public LmServiceException(int errCode, String errMsg, Throwable t) {
        this(errMsg, t);
        this.errCode = errCode;
    }

    public int getErrCode() {
        return errCode;
    }

    public void setErrCode(int errCode) {
        this.errCode = errCode;
    }
}


异常处理的方法

public class LmExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(LiomExceptionHandler.class);

    public static LmServiceException handle(Throwable t) throws LiomServiceException {
        logger.error(t.getMessage(), t);

        t = t.getCause() != null ? t.getCause() : t;
        //处理请求参数异常
        if (t instanceof ConstraintViolationException) {
            StringBuilder msgSB = new StringBuilder("请求参数非法,{");
            Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) t).getConstraintViolations();
            for (ConstraintViolation<?> constrainTviolation : constraintViolations) {
                msgSB.append('[');
                msgSB.append(constrainTviolation.getPropertyPath());
                msgSB.append("违反");
                msgSB.append(constrainTviolation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName());
                msgSB.append("约束:");
                msgSB.append(constrainTviolation.getMessage());
                msgSB.append(']');
            }
            msgSB.append('}');
            logger.error(msgSB.toString());
            return new LmServiceException(CodeConstant.CODE_CLIENT_REQ_ERROR, msgSB.toString(), t);
        } else if (isTimeoutException(t)) {
            return new LmServiceException(CodeConstant.CODE_UNKNOW, "调用关联系统处理超时,请稍后重试", t);
        }
        String msg = t.getMessage();
        if (StringUtils.isBlank(msg)) {
            msg = t.getClass().getName();
        }
        if (msg.length() > 256) {
            return new LmServiceException(CodeConstant.CODE_DEFAULT_FAIL, "系统处理异常", t);
        }
        try {
            Object status = PropertyUtils.getProperty(t, "errCode");
            if (status != null) {
                return new LiomServiceException((int) status, msg, t);
            }
        } catch (Exception e1) {
            logger.warn(e1.getMessage());
        }
        return new LmServiceException(CodeConstant.CODE_DEFAULT_FAIL, msg, t);
    }

    private static boolean isTimeoutException(Throwable t) {
        while (t != null) {
            if (t instanceof SocketTimeoutException || t instanceof RemoteAccessException) {
                return true;
            }
            t = t.getCause();
        }
        return false;
    }
}

同getCause()方法,去判断异常的类型,然后分别进行处理,返回类型为刚才自定的异常类

@Aspect
@Component
@Order
public class RestControllerExceptionAspect {
    @Pointcut("within(com.example.base..*) && " +
            "(@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.GetMapping))")
    private void httpExceptionPointcut() {
    }

    @Around(value = "httpExceptionPointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        try {
            return proceedingJoinPoint.proceed();
        } catch (Exception t) {
            if (((MethodSignature) proceedingJoinPoint.getSignature()).getReturnType() == ResultDTO.class) {
                return new ResultDTO<>(liomServiceException.getErrCode(), liomServiceException.getMessage());
            } else {
                throw liomServiceException;
            }
        }
    }

配置@Pointcut的取值范围,确定切点(可以是注解或者包名)

通过@Around环绕通知,获取到当前方法的返回值类型,然后进行统一的处理

更多技术文章请关注公众号:架构师Plus,
扫码添加
全局异常处理_第1张图片

你可能感兴趣的:(java)