解决: 设置了切面环绕通知后,方法内部异常无法被全局异常处理器捕获输出(自定义切面日志)

如题

其实知道后也很简单,原理是因为 aop切面,环绕通知是最强大的,当你设置了对某个方法的环绕通知后,它内部会捕获这个方法所抛出的所有异常,然后你的全局自定义异常处理器当然就捉不到异常了(这个老六。。。)

解决

也很简单,既然我偷拿了异常,那我再扔地上不就行了?反正你也会去捡。。。

在这里插入图片描述

aop 切面日志自定义

背景:设备与系统交互,需求:捕获每次交互记录,传参/返回值情况,并存入数据库。

使用:Maven: org.aspectj:aspectjweaver:1.9.5 jar包

话不多说,直接上代码:

① 切面支持抽象类

package com.yunsystem.system.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

public abstract class AspectSupport {

    Method resolveMethod(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Class<?> targetClass = point.getTarget().getClass();

        Method method = getDeclaredMethod(targetClass, signature.getName(),
                signature.getMethod().getParameterTypes());
        if (method == null) {
            throw new IllegalStateException("无法解析目标方法: " + signature.getMethod().getName());
        }
        return method;
    }

    private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethod(superClass, name, parameterTypes);
            }
        }
        return null;
    }
}

② 继承实现自定义日志类

package com.yunsystem.system.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yunsystem.common.annotation.ControllerEndPoint1;
import com.yunsystem.common.annotation.ControllerEndpoint;
import com.yunsystem.common.model.system.DeviceAccessLog;
import com.yunsystem.system.service.DeviceAccessLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;

/**
 * 设备访问系统后台切面日志
 */
@Slf4j
@Aspect
@Component
public class DeviceAccessControllerAspect extends AspectSupport{

    private DeviceAccessLog accessLog = new DeviceAccessLog();

    @Autowired
    private DeviceAccessLogService deviceAccessLogService;


    @Pointcut("@annotation(com.yunsystem.common.annotation.ControllerEndPoint1)")  // 表示标注了该注解ControllerEndPoint1的所有方法
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //增加返回值
        Object proceed = null;
        try {
            // 执行方法
            proceed = point.proceed();
        } catch (Throwable e) {
            log.error("目标方法执行返回值为:"+e.getMessage());
            //e.printStackTrace();
            //环绕通知最强大,包括了全局异常处理,所以使用了环绕通知,业务异常被捕获后,全局异常捕获不到,会失效。
            // 此时只要重新抛出来,给全局异常自行捕获就好
            throw e;
        }
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        // 保存日志
        saveLog(point, time,proceed);
        //关键,同时该参数作为入参存储在数据库中。
        return proceed;
    }

    private void saveLog(ProceedingJoinPoint joinPoint, long time, Object proceed) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取注解
        ControllerEndPoint1 controllerEndpoint = method.getAnnotation(ControllerEndPoint1.class);
        if (controllerEndpoint != null) {
            // 注解上的描述 上报类型,0 申请 1 上报
            String operation = controllerEndpoint.operation();
            accessLog.setAccess_type(Integer.parseInt(operation));   // 访问类型
        }
        // 请求的方法名
//        String className = joinPoint.getTarget().getClass().getName();
//        String methodName = signature.getName();
//        accessLog.setMethod(className + "." + methodName + "()");
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();

        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);


//        log.info("Request请求参数: "+ argString);
//        log.info("Request请求参数名: "+ paramNames);

        if (args != null && paramNames != null) {   // 默认字段上传按顺序sn、型号、名称

            if (args.length > 1) {  // get请求参数
                accessLog.setSn(args[0].toString());
                accessLog.setType(args[1].toString());
                accessLog.setName(args[2].toString());
            } else {  // post请求参数——json串
                //String json1 = JSONObject.toJSONString(args);
                String json2 = JSONObject.toJSONString(args[0]);  // 转成json字符串
                JSONObject jsonObject = JSONObject.parseObject(json2);
                accessLog.setSn(jsonObject.getString("sn"));
                accessLog.setDevice_type(jsonObject.getString("type"));
                accessLog.setCustomer_name(jsonObject.getString("name"));
            }

        }

        accessLog.setAccess_time(new Date());   // 访问时间
        assert controllerEndpoint != null;
        //查询返回值
        log.info("target=" + joinPoint.getTarget());
        log.info("kind=" + joinPoint.getKind());
        log.info("proceed=" + proceed.toString());  //返回结果
        if (controllerEndpoint.operation().equals("0")) { // 0对应申请激活码接口
            accessLog.setInfo(proceed.toString());  // info保存get请求接口的返回数据
        } else {  // 否则对应上报接口
            String json2 = JSONObject.toJSONString(args[0]);  // 转成json字符串
            accessLog.setInfo(proceed +json2); // 保存上报的数据和返回结果
        }

//        sysLog.setResp(proceed.toString());
        // 随机生成id
        String id = UUID.randomUUID().toString().substring(0, 32).replace("-","");
        accessLog.setId(id);
        // 保存系统日志
        deviceAccessLogService.saveLog(accessLog);
    }

}

③ 日志实体类

package com.yunsystem.common.model.system;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "device_access_log")
public class DeviceAccessLog {

    @Id
    private String id;

    private String sn; // sn号

    private String name; // 名称

    private String type; // 型号

    private Integer access_type; // 访问类型 0申请 1上报

    private String info;  // 上报内容详情

    private Date access_time;  // 访问时间
}

④ service 接口

package com.yunsystem.system.service;


import com.yunsystem.common.error.BusinessException;
import com.yunsystem.common.model.system.DeviceAccessLog;
import com.yunsystem.common.model.system.Log;
import com.yunsystem.common.vo.business.ActiveCodeInfoVO;
import com.yunsystem.common.vo.system.DeviceAccessLogVO;
import com.yunsystem.common.vo.system.PageVO;
import org.springframework.scheduling.annotation.Async;

public interface DeviceAccessLogService {

    /**
     * 日志列表
     * @param pageNum
     * @param pageSize
     * @param deviceAccessLogVO
     * @return
     */
    PageVO<DeviceAccessLogVO> findDeviceAccessLogVOList(Integer pageNum, Integer pageSize, DeviceAccessLogVO deviceAccessLogVO);

    /**
     * 异步保存操作日志
     */
    @Async("CodeAsyncThreadPool")
    void saveLog(DeviceAccessLog accessLog);

    /**
     * 日志详情
     * @param id
     * @return
     */
    DeviceAccessLogVO detail(String id) throws BusinessException;


    /**
     * 删除日志(一经使用不能删除)
     * @param id
     */
    void delete(String id) throws BusinessException;
}

⑤ serviceImpl即对上面接口方法的实现,细节略。

⑥ 最重要的自定义注解,用于标注在controller的方法上,异步记录日志

package com.yunsystem.common.annotation;

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 ControllerEndPoint1 {

    String operation() default "";

    String exceptionMessage() default "系统内部异常";
}

⑦ 然后看controller引用:

在这里插入图片描述
在这里插入图片描述
注意这里的operation的值是我用来区分这两个接口的,用来保存日志时做相应的不同处理。


over~

如果`doGetAuthenticationInfo`方法抛出自定义异常无法全局异常处理捕获,可能是因为全局异常处理没有正确配置或者没有被正确调用。以下是一些可能的原因和解决方法: 1. 检查全局异常处理的配置是否正确。在web.xml文件中,需要正确配置全局异常处理的名称和类名,例如: ```xml <filter> <filter-name>GlobalExceptionHandler</filter-name> <filter-class>com.example.GlobalExceptionHandler</filter-class> </filter> <filter-mapping> <filter-name>GlobalExceptionHandler</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` 其中,`filter-name`指定了全局异常处理的名称,`filter-class`指定了全局异常处理的类名。`filter-mapping`用于指定拦截的URL模式。请确保这些配置项正确无误。 2. 确认全局异常处理是否被正确调用。在`doFilter`方法中,需要调用`filterChain.doFilter`方法来继续处理请求。如果没有调用该方法,那么请求将不会继续处理,也就无法触发全局异常处理。请确保在`doFilter`方法中正确调用了`filterChain.doFilter`方法。 ```java public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException { try { // 执行过滤链 filterChain.doFilter(req, res); } catch (MyException e) { // 处理自定义异常 e.printStackTrace(); } catch (Exception e) { // 处理其他异常 e.printStackTrace(); } } ``` 3. 确认自定义异常是否正确抛出。在`doGetAuthenticationInfo`方法中,需要正确抛出自定义异常,例如: ```java if (user == null) { throw new MyException("User not found!"); } ``` 请确保自定义异常被正确抛出,并且没有被其他异常或错误覆盖。 如果以上方法无法解决问题,可以尝试使用调试工具来定位问题,例如使用IDE的调试功能来查看异常抛出的堆栈信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值