• 代理对象上的注解获取失败总结


    一、问题背景

    项目系统在运行时,突然发现了通知无法消费处理的异常,在排除通知未发送的原因后,发现是系统中对于kafka的定阅出现了问题。报错的信息如下:

    log":"java.lang.NullPointerException: null\n","stream":"stdout","time":"2022-05-30T07:05:06.931666672Z"}
    {"log":"\u0009at com.zte.sdon.urm.framework.event.UrmEventHandleListenerRegistery.serverStarted(UrmEventHandleListenerRegistery.java:25)\n","stream":"stdout","time":"2022-05-30T07:05:06.93168196Z"}
    {"log":"\u0009at io.dropwizard.lifecycle.setup.LifecycleEnvironment$ServerListener.lifeCycleStarted(LifecycleEnvironment.java:117)\n","stream":"stdout","time":"2022-05-30T07:05:06.931689514Z"}
    {"log":"\u0009at org.eclipse.jetty.util.component.AbstractLifeCycle.setStarted(AbstractLifeCycle.java:194)\n","stream":"stdout","time":"2022-05-30T07:05:06.931695308Z"}
    {"log":"\u0009at 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从日志中发现是如下代码的第25行出现了空指针
    在这里插入图片描述

    二、问题分析

    从上图代码中箭头指向分析,分析理论上是不可能为空的,因为这些服务,就是从IOC容器上根据注解方式过滤出的对象,再取这些对象上的注解,怎么会就找不到对应注解了呢?

    Debug后,发现取的对象,不再是原始对象,而是代理对象,通过团队内咨询,果然是有一个需求里增加了AOPA增强,导致进入到IOC里的是代理对象,如下的AOP注解**@Mutex**

    @UrmEventHandleListener
    @Service
    @Slf4j
    public class AddLinksHandleListener {
        @Subscribe
        @Mutex(classConvertingToLockList = AddLinksLockListConverter.class)
        public void onAddLinks(AddLinks addLinks) {
            log.info("receive addLinks notification : {}", addLinks);
            OriginalLinkAddOperation originalLinkAddOperation = OriginalLinkAddOperation.getInstance(addLinks);
            try {
                originalLinkAddOperation.apply();
            } catch (Exception e) {
                log.warn("onAddLinks failed, exception is ", e);
                throw e;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    直接从代理对象getClass是获取不到原始类型的,自然也就无法找到其注解。
    JDK代理和CGLIB代理

    com.sun.proxy.$Proxy0
    class com.zte.sdn.oscp.anno.proxy.ByeService

    EnhancerByCGLIB" role="presentation" style="text-align: center; position: relative;">EnhancerByCGLIB
    3c26428b

    三、问题修改

    本项目中,针对原始注解,增加了**@Inherited**描述,这样保证即使是生成的代理对象,其注解信息不丢失。

    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Inherited
    public @interface XXEventHandleListener {
        boolean async() default false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    四、更进一步

    经过上面的修改,可能对于加一个@Inherited功能正常,还是不明白为什么?

    以JDK动态代理为例,原理上最终生成的代理对象类实际上一个ProxyXX的类同时实现了原来类的接口。(JDK动态代码要求有接口)

    这样代理对象的接口也是可以获取到原来注解的,而不管其是否有@Inherited 注解。前提是注解要在接口类上。

    public static void main(String[] args) {
            HelloService helloService = new HelloService();
    
            Hello helloServiceProxy = (Hello) Proxy.newProxyInstance(HelloService.class.getClassLoader(), new Class[]{Hello.class}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("before");
                    return method.invoke(helloService, args);
                }
            });
            helloServiceProxy.say();
            Arrays.stream(helloService.getClass().getAnnotations()).forEach(System.out::println);
            //com.sun.proxy.$Proxy0
            System.out.println(helloServiceProxy.getClass());
            final Annotation[] annotations = helloServiceProxy.getClass().getInterfaces()[0].getAnnotations();
            //@com.zte.sdn.oscp.anno.proxy.XXEventHandleListener(async=false)
            Arrays.stream(annotations).forEach(System.out::println);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    CGLIB代理,其代理对象是一个继承原来类的新类(不需要接口)。这样获取其父类,理论也是可以得到注解的,如下:

        public static void main(String[] args) {
            ByeService byeService = new ByeService();
            final ByeService byeServiceProxy = create(byeService);
    
            byeServiceProxy.say();
            //class com.zte.sdn.oscp.anno.proxy.ByeService$$EnhancerByCGLIB$$3c26428b
            System.out.println(byeServiceProxy.getClass());
            final Annotation[] annotations = byeServiceProxy.getClass().getSuperclass().getAnnotations();
            //@com.zte.sdn.oscp.anno.proxy.XXEventHandleListener(async=false)
            Arrays.stream(annotations).forEach(System.out::println);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    那在CGLIB中,为了使者用无感知是否代理,则需要在注解里增加@Inherited,这样代理对象的类则也可以继承该注解

        public static void main(String[] args) {
            ByeService byeService = new ByeService();
            final ByeService byeServiceProxy = create(byeService);
    
            byeServiceProxy.say();
            //class com.zte.sdn.oscp.anno.proxy.ByeService$$EnhancerByCGLIB$$3c26428b
            System.out.println(byeServiceProxy.getClass());
            Annotation[] annotations = byeServiceProxy.getClass().getSuperclass().getAnnotations();
            //@com.zte.sdn.oscp.anno.proxy.XXEventHandleListener(async=false)
            Arrays.stream(annotations).forEach(System.out::println);
    
            //这要正确获取到正确注解,则需要增加@Inherited
            annotations = byeServiceProxy.getClass().getAnnotations();
            //@com.zte.sdn.oscp.anno.proxy.XXEventHandleListener(async=false)
            Arrays.stream(annotations).forEach(System.out::println);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    五、最后总结

    1、从上文的分析来看,如果是CGLIB代理的原理,通过增加@Inherited可以保证注解信息不丢失。
    2、而JDK代理,只有在接口上的注解才不会丢失

    最后框架提供的根据注解获取对象,作为框架其并不知道使用了何种代理

    final List<?> allServices = ServiceUtils.getLocator().getAllServices(UrmEventHandleListener.class);
    
    • 1

    六、源码记录

    
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    
    
    public class AnnotationProxyTest {
    
        public static void main1(String[] args) {
            HelloService helloService = new HelloService();
    
            Hello helloServiceProxy = (Hello) Proxy.newProxyInstance(HelloService.class.getClassLoader(), new Class[]{Hello.class}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("before");
                    return method.invoke(helloService, args);
                }
            });
            helloServiceProxy.say();
            Arrays.stream(helloService.getClass().getAnnotations()).forEach(System.out::println);
            //com.sun.proxy.$Proxy0
            System.out.println(helloServiceProxy.getClass());
            final Annotation[] annotations = helloServiceProxy.getClass().getInterfaces()[0].getAnnotations();
            //@com.zte.sdn.oscp.anno.proxy.XXEventHandleListener(async=false)
            Arrays.stream(annotations).forEach(System.out::println);
        }
    
        public static void main(String[] args) {
            ByeService byeService = new ByeService();
            final ByeService byeServiceProxy = create(byeService);
    
            byeServiceProxy.say();
            //class com.zte.sdn.oscp.anno.proxy.ByeService$$EnhancerByCGLIB$$3c26428b
            System.out.println(byeServiceProxy.getClass());
            Annotation[] annotations = byeServiceProxy.getClass().getSuperclass().getAnnotations();
            //@com.zte.sdn.oscp.anno.proxy.XXEventHandleListener(async=false)
            Arrays.stream(annotations).forEach(System.out::println);
    
            //这要正确获取到正确注解,则需要增加@Inherited
            annotations = byeServiceProxy.getClass().getAnnotations();
            //@com.zte.sdn.oscp.anno.proxy.XXEventHandleListener(async=false)
            Arrays.stream(annotations).forEach(System.out::println);
        }
    
        public static <T> T create(Object target) {
            Enhancer enhancer = new Enhancer();
            //设置父类
            enhancer.setSuperclass(target.getClass());
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(obj, args);
                }
            });
            return (T) enhancer.create();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
  • 相关阅读:
    ubuntu无法virtualenv创建python虚拟环境的解决
    pyinstaller打包教程(pycharm)
    NMap 使用技巧总结(二)
    【NOI模拟赛】防AK题(生成函数,单位根,Pollard-Rho)
    智能护栏碰撞监测系统:强化道路安全的智能守卫
    Java开发者的Python快速进修指南:控制之if-else和循环技巧
    【Mysql】mysql | sql优化 | 优化using filesort
    01-Kafka之单机和集群安装
    C++之运算符重载的简明了解其原理
    快速理解 Node.js 版本差异:3 分钟指南
  • 原文地址:https://blog.csdn.net/sunquan291/article/details/125459875