自定义异常拦截

2020-10-30 00:00:00 异常 自定义 拦截

自定义异常拦截一般有两种方式:

1.使用@RestControllerAdvice或者@ControllerAdvice注解定义全局异常拦截

2.基于AOP的异常拦截

使用注解方式

这两种注解都在org.springframework.web.bind.annotation包下面

《自定义异常拦截》

 两者区别:

1)注解有@ControllerAdvice的类, 需要在具体方法上同时添加@ExceptionHandler和@ResponseBody注解;

2)注解有@RestControllerAdvice的类,只需要在具体方法上添加@ExceptionHandler注解。

这里主要展示@RestControllerAdvice的使用

package com.framework.web.exception;

import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;
import com.common.core.domain.AjaxResult;
import com.common.exception.BusinessException;
import com.common.exception.DemoModeException;
import com.common.utils.ServletUtils;
import com.common.utils.security.PermissionUtils;

/**
 * 全局异常处理器
 * 
 */
@RestControllerAdvice
public class GlobalExceptionHandler
{
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 权限校验失败 如果请求为ajax返回json,普通请求跳转页面
     */
    @ExceptionHandler(AuthorizationException.class)
    public Object handleAuthorizationException(HttpServletRequest request, AuthorizationException e)
    {
        log.error(e.getMessage(), e);
        if (ServletUtils.isAjaxRequest(request))//此处ServletUtils属于自定义,当前不作说明
        {
            return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
        }
        else
        {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("error/unauth");
            return modelAndView;
        }
    }

    /**
     * 请求方式不支持
     */
    @ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
    public AjaxResult handleException(HttpRequestMethodNotSupportedException e)
    {
        log.error(e.getMessage(), e);
        return AjaxResult.error("不支持' " + e.getMethod() + "'请求");
    }

    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public AjaxResult notFount(RuntimeException e)
    {
        log.error("运行时异常:", e);
        return AjaxResult.error("运行时异常:" + e.getMessage());
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e)
    {
        log.error(e.getMessage(), e);
        return AjaxResult.error("服务器错误,请联系管理员");
    }

    /**
     * 自定义异常,属于自定义异常BusinessException,然后使用此处拦截
     */
    @ExceptionHandler(BusinessException.class)
    public Object businessException(HttpServletRequest request, BusinessException e)
    {
        log.error(e.getMessage(), e);
        if (ServletUtils.isAjaxRequest(request))
        {
            return AjaxResult.error(e.getMessage());
        }
        else
        {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("errorMessage", e.getMessage());
            modelAndView.setViewName("error/business");
            return modelAndView;
        }
    }

}

AOP拦截方式

注解

package com.common.annotation;

import com.common.enums.DataOperateType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 异常拦截注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExceptionIntercept {

    DataOperateType operateType(); //操作类型 可以自定义
}

枚举类

package com.common.enums;

/**
 * 数据操作类型
 *
 */
public enum DataOperateType {
    /**
     * 修改
     */
    UPDATE,

    /**
     * 插入
     */
    INSERT,

    /**
     * 删除
     */
    DELETE
}

 切面类

1)pointcut/value:这两个属性的作用是一样的,它们都用于指定该切入点对应的切入表达式。一样既可是一个已有的切入点,也可以直接定义切入点表达式。当指定了pointcut属性后,value属性值将会被覆盖。

2)throwing:该属性指定一个形参名,用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。除此之外,在Advice方法中定义该参数时,指定的类型,会限制方法必须抛出指定类型的异常。此处使用e,参数中也要使用e,名字保持一致

3)Pointcut:其中有以下两种使用方式,举例:

@within(org.springframework.stereotype.Service),拦截带有 @Service 注解的类的所有方法
@annotation(org.springframework.web.bind.annotation.RequestMapping),拦截带有@RquestMapping的注解方法

 

package com.framework.aspectj;

import com.common.annotation.ExceptionIntercept;
import com.common.enums.DataOperateType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 异常拦截处理
 */
@Aspect
@Component
public class ExceptionAspect {

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


    /**
     * 配置织入点,ExceptionIntercept是注解,此处只是拦截有该注解的方法
     */
    @Pointcut("@annotation(com.common.annotation.ExceptionIntercept)")
    public void exceptionPointCut() {
    }

    @Before("exceptionPointCut()")
    public void doBefore(JoinPoint point) {
//        System.out.println("ExceptionAspect的前置通知开始");
    }

    @AfterReturning("exceptionPointCut()")
    public void doAfter(JoinPoint point) {
//        System.out.println("ExceptionAspect的后置通知开始");
    }

    /**
     *  抛出异常时执行 

     * @param point
     * @param e
     */
    @AfterThrowing(pointcut = "exceptionPointCut()", throwing = "e")
    public void handleThrowsIng(JoinPoint point, Exception e) {
        Signature signature = point.getSignature();
        Object[] args = point.getArgs();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method == null) {
            return;
        }
        //获取注解
        ExceptionIntercept annotation = method.getAnnotation(ExceptionIntercept.class);
        DataOperateType dataOperateType = annotation.operateType();
        if (dataOperateType == null) {
            return;
        }
        //获取方法的参数类型数组
        Class[] parameterTypes = methodSignature.getParameterTypes();
        String name = method.getDeclaringClass().getName();//类名全称
        String params = parameterTypes[0].getName();//第一个参数名(可以获取多个)
        String dateType = dataOperateType.name();//参数类型
        //TODO 进行下一步操作
    }
}

用例:

    @ExceptionIntercept(operateType = DataOperateType.DELETE)
    public int deleteTRenyuanByIds(String ids) {
        return tRenyuanMapper.deleteTRenyuanByIds(Convert.toStrArray(ids));
    }

 

注意事项:

1.切面的执行顺序,与过滤器、拦截器的区别

过滤器 -> 通过集成Filter实现. 缺点如下
      无法获取请求要访问的类与方法,以及参数. 可以获取原始的http请求与相应

拦截器 -> 基于springmvc提供的拦截器接口,自定义实现.缺点如下
       可以获取请求访问的类与方法 , 但是无法获取请求参数的值. 具体可根据dispatcherServlet跟踪源码

切面 -> 基于spring , 通过aspect注解实现
        可以获取访问的类 方法 以及参数值. 但是无法获取http原始的请求与相应的对象

请求处理顺序:
       过滤器 -> 拦截器 -> 切面
报错处理顺序:
       切面 -> controllerAdvice -> 拦截器 -> 过滤器 -> 服务

2. 被拦截的方法如果无特殊情况不能try catch拦截,否则全局异常拦截类无法获取异常。

3.AOP无法彻底拦截异常,仍然会传递到上一级

AOP的AfterThrowing处理虽然可以对目标方法的异常进行处理,但这种处理与直接使用catch捕捉不同,catch捕捉意味着完全处理该异常,如果catch块中没有重新抛出新的异常,则该方法可能正常结束;而AfterThrowing处理虽然处理了该异常,但它不能完全处理异常,该异常依然会传播到上一级调用者,即JVM。

4.AOP无法拦截内部类方法的调用

资料:https://www.cnblogs.com/dennyzhangdd/p/9242148.html

说明:

在类C中,方法A调用方法B,B方法被AOP拦截,最终A-》B ,B并不会触发AOP

解决:

第一种解决方案:

1.将当前的代理类暴露给线程使用,以下2种自己选一个实现即可。

注解实现方案:springboot:启动类上加注解:@EnableAspectJAutoProxy(exposeProxy=true):

配置实现方案:<aop:aspectj-autoproxy expose-proxy=”true” />

2.A中调用B:不要直接用this(因为this是目标对象,自然无法实现代理类的增强方法@before等),
先去尝试获取代理类:UserServiceImpl service = AopContext.currentProxy() != null ? (UserService)AopContext.currentProxy() : this;

第二种方案:

去除AOP切面增强,把切面方法单独封装接口方法,在需要的地方调用,意思取消AOP方式,使用接口方式,

 

用例:

该注解拦截使用上面的例子:@ExceptionIntercept,本案例采用第一种方案

@Service
public class TRenyuanServiceImpl implements ITRenyuanService {
    

    @Autowired(required = false)
    private TRenyuanMapper tRenyuanMapper;

    /**
     * 新增人员
     * @return
     */
    @Override
    @ExceptionIntercept(operateType = DataOperateType.INSERT)
    public int insertTRenyuan(TRenyuan tRenyuan) {
        return tRenyuanMapper.insertTRenyuan(tRenyuan);
    }

    /**
     * 内部类调用
     * @param tRenyuan
     */
    @Override
    public void addRenYuan(TRenyuan tRenyuan) {
        
        /**  第一种方式:如果该方法出现异常,插入失败,但异常不会被@ExceptionIntercept注解拦截*/
        this.insertTRenyuan(tRenyuan);
        /**  第二种方式:可以拦截 */
        ITRenyuanService itRenyuanService = (ITRenyuanService) AopContext.currentProxy();
        itRenyuanService.insertTRenyuan(tRenyuan);
    }

}
    原文作者:我思故我在zzzl3
    原文地址: https://blog.csdn.net/zl_action/article/details/101204492
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。

相关文章