资讯专栏INFORMATION COLUMN

Spring自定义注解不生效原因解析及解决方法

xbynet / 2639人阅读

摘要:自定义注解不生效原因解析及解决方法背景项目中,自己基于实现了一套缓存注解。但是最近出现一种情况缓存竟然没有生效,大量请求被击穿到层,导致压力过大。至此,问题得到解决。

自定义注解不生效原因解析及解决方法 背景:

项目中,自己基于spring AOP实现了一套java缓存注解。但是最近出现一种情况:缓存竟然没有生效,大量请求被击穿到db层,导致db压力过大。现在我们看一下具体代码情形(代码为伪代码,只是为了说明一下具体情况)。

interface A {
    int method1(..);
    int method2(..);
    ... ...
}

class AImpl implements A {
    @Override
    @CacheMM(second=600)      //这里的@CacheMM就是我实现的自定义缓存注解
    public int method1(..) {
        ... ...
        method2(..);
        ... ...
    }
    
    @Override
    @CacheMM(second=600)
    public int method2(..) {
        ... ...
    }
}

如上代码,当调用method1时,发现method2注解并没有生效。

分析:

这是为什么呢?别急,我们带着这个问题去看了一下注解的实现类。(这里就不贴缓存注解的实现代码了)我的自定义注解是直接extends AbstractBeanFactoryPointcutAdvisor类然后实现其中的getPointcut() 和 getAdvice() 实现的。(其实这里可以直接使用aop环绕通知的,原理都差不多,我是为了熟悉源码才这样写的)。

接下来,我们继续往下分析,我们都知道基于spring aop实现的注解,在spring 中,如果有aop实现,那么容器注入的是该类的代理类,这里的代理类是aop 动态代理生成的代理类。Spring aop 的动态代理有两种:一种是jdk的动态代理,一种是基于CGLIB的。这两个的区别我就不多说了,如果你的业务类是基于接口实现的,则使用jdk动态代理,否则使用CGLIB动态代理。 我这里使用的是接口实现,所以我们就顺着思路去看一下jdk动态代理的具体实现。

上边的业务代码类我已经贴出。而需要生成代理对象(proxy),分成两步:

生成代理对象需要建立代理对象(proxy)和真实对象(AImpl)的代理关系

实现代理方法

在JDK动态代理中需要实现接口:java.lang.reflect.InvocationHandler.

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;   
import java.lang.reflect.Proxy;   
  
public class AProxy implements InvocationHandler   
{   
    private Object target;   
      
    /**  
     * 生成代理对象,并和真实服务对象绑定.  
     * @param target 真实服务对象  
     * @return 代理对象 */   
    public Object bind(Object target)   
    {   
        this.target = target;   
          
        //生成代理对象,并绑定.   
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), //类的加载器   
                              target.getClass().getInterfaces(), //对象的接口,明确代理对象挂在哪些接口下   
                              this);//指明代理类,this代表用当前类对象,那么就要求其实现InvocationHandler接口的invoke方法   
          
        return proxy;   
    }   
          
    /**  
     * 当生成代理对象时,第三个指定使用AProxy进行代理时,代理对象调用的方法就会进入这个方法。  
     * @param proxy 代理对象  
     * @param method  被调用的方法  
     * @param args 方法参数  
     * @return 代理方法返回。  
     * @throws Throwable  异常处理 */   
     @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable   
     {   
        System.err.println("反射真实对象方法前");   
        Object obj = method.invoke(target, args);//相当于AImpl类中对应方法调用.   
        System.err.println("反射真实对象方法后");   
          
        return obj;   
    }  
}  

代码中,Object obj = method.invoke(target,args) 通过反射调度真实对象的方法,这个很重要。我们知道其实虽然aop是通过代理对象去实现一些附加的操作的,但是真正的类方法调用还是通过反射调用真实对象的。这个时候,我们回头看一下问题,我们AImpl中有两个方法,其中method2是在method1内部调用的。当调用method1时,spring内部其实调用的是代理类AProxy类的invoke,这个时候在执行真实对象方法钱去执行method1中的一些附加操作。然后,在通过反射进入对应AImpl类中调用method1方法。注意,这个时候,已经不在代理对象中操作了,由于method2的调用是在method1内部调用的,所以在这里实际调用method2的是真实对象,并不是代理对象。 所以,就导致method2上的缓存注解没有生效。

解决:

好了,现在知道问题的原因后(动态代理的坑啊,内部调用不走代理类,所以实现的附加操作肯定不会执行了),我们来针对性的解决。我们现在知道这个其实是因为实际执行的不是代理类而导致的,那我们解决的思路就想办法让method2的调用走代理类就可以了。(就是这么简单)

AProxy类我们是可以在spring容器中得到的。下面是修改后的解决方案:

method1(..) {
    ... ...
     // 如果希望调用的内部方法也被拦截,那么必须用过上下文获取代理对象执行调用,而不能直接内部调用,否则无法拦截  
        if(null != AopContext.currentProxy()){  
            AopContext.currentProxy().method2();  
        }else{  
            method2();  
        }      

}

这里的AopContext.currentProxy() 拿到的实际就是代理对象了,这样通过代理对象去调用method2肯定就没有问题了。

还有一种解决方法就是不使用 动态代理织入,使用aspectJ织入,aspectJ直接在源类上进行字节码的插入,而不是以代理的方式进行。

这里可以参考一下
AspectJ 编译时织入(Compile Time Weaving, CTW)

因为这样改动比较大,所以目前我还是采用第一种方案解决问题了。至此,问题得到解决。

总结:

结合Spring aop动态代理的实现原理,提供两种动态代理:JDK代理和CGLIB代理

JDK代理只能对实现了接口的类生成代理,而不能针对类;
CGLIB是针对类实现代理的,主要对指定的类生成一个子类,并覆盖其中的方法,
因为是继承,所以不能使用final来修饰类或方法。所以该类或方法最好不要声明成final

更加详细的解释可以参考这篇博文 哪些方法不能实施Spring AOP事务

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69019.html

相关文章

  • Spring】一次线上@Transational事务注解生效原因探究

    摘要:由于的限制,无法替换被代理类已经被载入的字节码,只能生成并载入一个新的子类作为代理类,被代理类的字节码依然存在于中。区别于前两者,是一种静态代理的实现,即在编译时或者载入类时直接修改被代理类文件的字节码,而非运行时实时生成代理。 现象描述 上周同事发现其基于mySql实现的分布式锁的线上代码存在问题,代码简化如下: @Controller class XService { @A...

    姘存按 评论0 收藏0
  • Bean Validation完结篇:你必须关注的边边角角(约束级联、定义约束、定义校验器、国际

    摘要:和上标注的约束都会被执行注意如果子类覆盖了父类的方法,那么子类和父类的约束都会被校验。 每篇一句 没有任何技术方案会是一种银弹,任何东西都是有利弊的 相关阅读 【小家Java】深入了解数据校验:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validation 6.x使用案例【小家Spring】Spring方法级别数据校...

    niuxiaowei111 评论0 收藏0
  • @Validated和@Valid的区别?校验级联属性(内部类)

    摘要:毕竟永远相信本文能给你带来意想不到的收获使用示例关于数据校验这一块在中的使用案例,我相信但凡有点经验的程序员应该没有不会使用的,并且还不乏熟练的选手。 每篇一句 NBA里有两大笑话:一是科比没天赋,二是詹姆斯没技术 相关阅读 【小家Java】深入了解数据校验:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validati...

    Winer 评论0 收藏0
  • 创建属于己的 Spring Boot 动配置

    摘要:介绍这里有官方提供的演示项目和介绍本笔记也是通过官方提供的演示项目来进行讲解我们可以看到官方的项目中有三个模块和其中是演示如何使用自动配置是自动配置时的一些逻辑处理比较简单其中只有一些项目的依赖比如我们使用的 介绍 这里有官方提供的 演示项目 和 介绍. 本笔记也是通过官方提供的演示项目来进行讲解. 我们可以看到官方的项目中有三个模块, hornetq-sample-app horne...

    DC_er 评论0 收藏0
  • 这样讲 SpringBoot 动配置原理,你应该能明白了吧

    摘要:这里有一个参数,主要是用来指定该配置项在配置文件中的前缀。创建一个配置类,里面没有显式声明任何的,然后将刚才创建的导入。创建实现类,返回的全类名。创建实现类,实现方法直接手动注册一个名叫的到容器中。前言 小伙伴们是否想起曾经被 SSM 整合支配的恐惧?相信很多小伙伴都是有过这样的经历的,一大堆配置问题,各种排除扫描,导入一个新的依赖又得添加新的配置。自从有了 SpringBoot 之后,咋...

    cc17 评论0 收藏0

发表评论

0条评论

xbynet

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<