解决使用注解的spring中aop,无法拦截方法中调用其他的方法(函数内部调用无法拦截)问题

最近在项目中要记录用户日志,使用自定义注解进行aop拦截,发现可以拦截调用类的第一个方法,方法中调用其他方法,则其他方法无法拦截,经过查询,发现是aop自身的动态代理造成的,下面我贴出项目的代码:

1、自定义注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogController {
    String operationContent() default "";
}

2、spring的aop代码

@Aspect
@Component
public class UserLogAspect {

    @Pointcut("@annotation(com.jingli.creditchain.service.userlog.LogController)")
    public void methodAspect() {

    }

    @SuppressWarnings("rawtypes")
    @After("methodAspect()")
    public void doAfter(JoinPoint joinPoint) {
        try {
            //拦截的实体类
            Object target = joinPoint.getTarget();
            //拦截的方法名称
            String methodName = joinPoint.getSignature().getName();
            //拦截的参数类型
            Class[] parameterTypes = ((MethodSignature)joinPoint.getSignature()).getMethod().getParameterTypes();
            //通过反射获得拦截的method
            Method method = target.getClass().getMethod(methodName, parameterTypes);
            String content = method.getAnnotation(LogController.class).operationContent();
            //打印注解上的说明
            System.out.println("================="+content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

3、controller中进行方法拦截

    @LogController(operationContent = "findApply")
    @RequestMapping(path = "/list", method = RequestMethod.POST)
    public List<ApplyVm> findApply(@RequestBody QueryInnerApplyVm query) {
        //转换请求vm,在转换的方法上也加上拦截(转换方法在同一个类中)
        QueryParam queryParam = queryInnerApplyVmToQueryParam(query);
        return applyService.findApply(queryParam)
            .stream()
            .filter(Objects::nonNull)
            .map(this::assignApplyVm)
            .collect(Collectors.toList());
    }


//转换方法
@LogController(operationContent = "queryInnerApplyVmToQueryParam")
public QueryParam queryInnerApplyVmToQueryParam(QueryInnerApplyVm vm) {
    return new QueryParam();
}

4、测试,看打印结果

=================findApply

可以看到,方法中调用的第二个方法并没有拦截。

原因:

当调用这个类的时候,调用的是代理对象,代理类内部代码大致是:

private ApplyController applyController;

public List<ApplyVm> findApply(@RequestBody QueryInnerApplyVm query) {

    //也就是说,在调用其他函数时,其实是被代理对象调用的,所以方法是不会被增强,也就是没
        有拦截。调用其他函数时,如果是代理对象调用的,那就可以被拦截。
    QueryParam queryParam = applyController.queryInnerApplyVmToQueryParam(query);
       
}

5、修改代码

拿到ApplicationContext,从容器中拿对象,如果那个对象有代理对象,就可以获得代理对象。

//工具类
@Component
public class BeanUtil implements ApplicationContextAware {
    private  static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext){
        BeanUtil.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> bean){
        return applicationContext.getBean(bean);
    }
}
    @LogController(operationContent = "findApply")
    @RequestMapping(path = "/list", method = RequestMethod.POST)
    public List<ApplyVm> findApply(@RequestBody QueryInnerApplyVm query) {
        QueryParam queryParam = BeanUtil.getBean(this.getClass()).queryInnerApplyVmToQueryParam(query);
    }

6、测试

=================queryInnerApplyVmToQueryParam

=================findApply

第二个方法也被拦截了。

7、总结

spring是不建议这种使用aop有函数内部调用的,可以把调用的函数写到另一个类中。但是这样的话,代码会有点乱,所以我项目中看情况如果上面的写法方便的话我还是用上面的方法把=。=

这个问题一开始困扰我好久,上网查查这问题有大神解答,十分感谢,也记录一下。

### 使用 Spring AOP 拦截普通方法 为了实现对普通方法拦截Spring AOP 提供了一种面向切面编程的方式。通过定义切入点和通知来指定哪些方法应该被拦截以及在这些方法执行前后要做什么操作。 #### 定义 Aspect 类 创建一个类并标注 `@Aspect` 注解以表明这是一个切面类: ```java import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Component @Aspect public class LoggingAspect { // 切入点和通知逻辑将在这里定义 } ``` #### 编写 Before Advice 下面是一个简单的例子,在目标方法之前打印日志信息: ```java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // ... 继续上面的LoggingAspect类 private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); StringBuilder sb = new StringBuilder(); for (Object arg : args) { sb.append(arg).append(", "); } if(sb.length()>0){ sb.setLength(sb.length()-2); } logger.info("Method {} is called with arguments {}", methodName, sb.toString()); } ``` 这段代码会在任何匹配表达式的公共方法被执行前触发,并记录下该方法名及其参数列表[^1]。 #### 应用配置 确保应用程序上下文中启用了aspectJ自动代理支持,可以通过XML文件或注解方式完成。对于大多数现代项目来说,推荐使用注解形式开启AOP功能: ```properties @Configuration @EnableAspectJAutoProxy(proxyTargetClass=true) public class AppConfig {} ``` 以上设置会告诉Spring容器为所有符合条件的目标对象生成CGLIB子类实例作为代理对象,从而允许拦截私有方法和其他非接口场景下的方法调用[^2]。 #### 测试效果 当访问带有相应包路径的服务端点时,比如控制器中的某个处理函数,就会看到控制台输出相应的前置日志信息了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值