自定义异常拦截
自定义异常拦截一般有两种方式:
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);
}
}
原文地址: https://blog.csdn.net/zl_action/article/details/101204492
本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
相关文章