栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

AOP--代理那点事

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

AOP--代理那点事

之前在一次项目中有同事吐槽说使用了注解切面,但是在debug的时候神奇的发现,注解失效了,神奇的地方是,同一个调用链上的方法有的方法回生效 ,有的则不会会。这是一个比较有意思的问题,下面就针对这个奇怪的问题做一次复盘分析。

解决问题的四要素就得搬出来了,复现--猜测--求证--结论  

场景复现

注解

@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value =ElementType.METHOD)
public @interface PrintLog {
}

切面
@Aspect
@Slf4j
@Component
public class LogAspect {
    @Pointcut("@annotation(annotationAspect.annotations.PrintLog)")
    public void aspectAction(){}

    @Before("aspectAction()&&@annotation(printLog)")
    public void before(JoinPoint joinPoint, PrintLog printLog){
        log.info("注解作用的方法名: " + joinPoint.getSignature().getName());
        log.info("所在类的简单类名: " +
               joinPoint.getSignature().getDeclaringType().getSimpleName());
    }
}


控制层

public class LogController {
    @Resource
    private LogService logService;
    @RequestMapping("log")
    public Response aspect(@RequestBody Log log) {
        return logService.aspect(log);
    }
}

业务层

@Service
@Slf4j
public class LogService {
    @PrintLog
    public Response aspect(Log log) {
        doSomething(log);
        return Response.success(log);
    }
    @PrintLog
    public void doSomething(Log logs){
        log.info("doSomething");
        log.info("{}",logs);
    }
}

启动后预想的日志效果应该是这样:

注解作用的方法名: aspect

所在类的简单类名: LogService

注解作用的方法名: doSomething

所在类的简单类名: LogService

doSomething

Log(address=102002111659, time=20220618, line=102, token=aspect)

但是实际的效果是:

注解作用的方法名: aspect

所在类的简单类名: LogService

doSomething

Log(address=102002111659, time=20220618, line=102, token=aspect)

大胆猜测

现象告诉我们doSomething 这个方法虽然贴了注解,但是并没有生效,但是aspect这个方法贴的注解是生效了的,所以猜测注解本身存在问题。

排除注解本身不生效问题,我们大胆猜测:

  1. Logservice.dosomething 方法没有被代理,先不管对不对,先验,不对我们回头再重新开始。

小心求证

那么为什么Logservice.dosomething没有被代理上,而Logservice.aspect方法被代理上了?

我们先通过debug来获取一些信息--

1.下面这张图中控制层中这个logservice是一个代理对象;

下面两张图中已经进入业务层代码了,

2.LogService .aspect方法的切面注解已经生效了,打印了日志,下一步要执行的是dosomething方法;

3.调用dosomething方法的对象可以看到是this对象,即,LogService 的实例;

4.注意看左边,明显和调用 logservice .aspect方法状态不同,调用logservice .aspect方法之前还经历了两个步骤:

LogService$$EnhancerBySpringCGLIB$$bd77074 (annotationAspect.services)

LogService$$FastClassBySpringCGLIB$$86f0c27a (annotationAspect.services)

收获四条有用的信息,根据这些信息我们可以得出,logservice .aspect方法之所以能够打印出日志是因为这个方法执行了切面方法,而logservice .dosomething则没有,为什么呢?因为调用logservice .aspect方法是通过代理对象调用的,而logservice .dosomething则是由this调用的,就是这点不同导致了logservice .aspect方法打印了日志,logservice .dosomething没有打印日志。

结论

代理的理念是对方法功能进行增强,而不影响当前类,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。即,增强的代码不会出现在目标类字节码文件中,spring的AOP本身就是基于代理模式实现的,如本文中的日志切面代理,打印日志的代码只会存在于代理类的字节码文件中,不会被添加到LogService类中去,所以当使用this调用 dosomething方法时只是一个普通的调用并没有经过日志切面,也就不会打印日志了。

其实我们还可以看出当前代理模式使用的是CGLIB 代理,CGLIB代理是基于继承来实现的,还有一种代理模式 JDK代理,它是基于接口实现的,优缺点各不同。

Java提倡面向接口编程,而JDK代理非常契合这个理念,但是呢,使用JDK代理时目标类必须要继承一个接口,这使得程序猿在写BUG时十分不爽, 所以项目中使用CGLIB的比JDK代理的要多,这是因为spring针对代理做了策略---默认使用JDK代理(看来是支持面向接口编程的),但是如果目标类没有接口则使用CGLIB代理。

各位小伙伴要注意一点的是,CGLIB还是存在坑的,因为它是基于继承的,所以如果目标类中的方法不能被继承那么也是无法被代理的,比如,final。。。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/980888.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号