【小家Spring】详解Spring AOP的底层代理JdkDynamicAopProxy和ObjenesisCglibAopProxy的源码分析(介绍CGLIB使用中的坑)(下)

简介: 【小家Spring】详解Spring AOP的底层代理JdkDynamicAopProxy和ObjenesisCglibAopProxy的源码分析(介绍CGLIB使用中的坑)(下)

关于代理出现的:类型转换常见错误


java.lang.ClassCastException: com.sun.proxy.$Proxy7 cannot be cast to XXXXX

这一看就知道是JDK的代理对象不能转换为xxx对象。这是由JDK动态代理可能导致的。比如:


public class Main {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new Demo());
        proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, target) -> {
                    System.out.println("你被拦截了:方法名为:" + method.getName() + " 参数为--" + Arrays.asList(args1));
                }
        );
        // 如果使用的是JDK的动态代理,这里若实现实现类接收,就报错:java.lang.ClassCastException: com.fsx.maintest.$Proxy0 cannot be cast to com.fsx.maintest.Demo
        //Demo demo = (Demo) proxyFactory.getProxy();
        DemoInter demo = (DemoInter) proxyFactory.getProxy();
        //你被拦截了:方法名为:hello 参数为--[]
        //this demo show
        demo.hello();
    }
}
// 不要再实现接口,就会用CGLIB去代理
class Demo implements DemoInter{
    @Override
    public void hello() {
        System.out.println("this demo show");
    }
}
interface DemoInter{
    void hello();
}


这个例子就好比我们经常使用@Autowired自动注入的时候,建议注入接口(因为注入实现类且是JDK动态代理的话,是会出现类似错误的)

    @Autowired
    private HelloService helloService; // 建议注入接口而不是实现类
    //@Autowired
    //private HelloServiceImpl helloServiceImpl; //不建议直接注入实现类


上面例子,若采用的是CGLIB动态代理,不管是用接口还是实现类,都不会有问题。因此可见总体来说,CGLIb还是更加强大一些的

CGLIB动态代理 字段为null 导致的坑


先不说别的,看下面例子:(一定让你摸不着头脑)


public class Main {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new Demo());
        proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, target) -> {
                    System.out.println("你被拦截了:方法名为:" + method.getName() + " 参数为--" + Arrays.asList(args1));
                }
        );
        Demo demo = (Demo) proxyFactory.getProxy();
        //你被拦截了:方法名为:setAge 参数为--[10]
        demo.setAge(10);
        //你被拦截了:方法名为:getAge 参数为--[]
        System.out.println(demo.getAge()); //10
        System.out.println(demo.age); //null 对你没看错,这里是null
        System.out.println(demo.findAge()); //null 对你没看错,这里是null
    }
}
// 不要再实现接口,就会用CGLIB去代理
class Demo {
    public Integer age;
    // 此处用final修饰了  CGLIB也不会代理此方法了
    public final Integer findAge() {
        return age;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}


what?what a fuck?

是不是此时你有此种感觉:不再相信java了。明明我都用set把age赋值了,为毛我拿出来却仍旧为null呢?(但通过getAge()方法获取正常)


如何解决?

最直接也是最推荐的方案,就是遵从上面所说得规范,小心谨慎为事。

另外,本处我也提供一个简单的工具,配置上就能就绝get/set的问题。(但知其然必须知其所以然,才能更好的解决一类问题,而不是这一个问题)。仅供参考:

@Aspect
@Order(Integer.MIN_VALUE)
public class SetterAspect {
    // 切所有的set方法
    @After(value = "execution(* *.set*(*)) && args(value)", argNames = "value")
    public void after(JoinPoint jp, Object value) {
        Object proxy = jp.getThis();
        // 拿到目标对象
        Object target = jp.getTarget();
        if (AopUtils.isAopProxy(proxy)) {//只有代理对象才需要处理
            try {
                Class<?> proxyClass = proxy.getClass();
                Class<?> targetClass = target.getClass();
                String methodName = jp.getSignature().getName();
                Method m = BeanUtils.findDeclaredMethod(proxyClass, methodName, new Class[]{value.getClass()});
                PropertyDescriptor descriptor = BeanUtils.findPropertyForMethod(m);
                String propName = descriptor.getName();
                // 调用目标对象的set方法
                Field f = targetClass.getClass().getDeclaredField(propName);
                if (f != null) {
                    f.setAccessible(true);
                    f.set(proxy, value);
                }
            } catch (Exception e) {
                e.printStackTrace();//记录好异常进行处理  
            }
        }
    }
}  


解释:


若了解CGLIB的原理,甚至看过它生成的代理类的源码的话,此原因就可一句道破。


原理简述:假设有个类A,会在字节码的层面上动态生成一个类B并加载进JVM里面。B继承自A同时又有对A的引用,B会重写所有的A类里面的非Final、非private方法,从而可以在目标方法调用前后进行对应的增强了。


本文中:demo.setAge(10);执行的是代理对象的setAge()方法,所以set进去的值是给了代理对象的,目标对象仍然我null。而我们findAge()方法因为我标注了final,因此不能被CGLIB代理,所以只能从目标对象里拿值。因此它也只能拿到null


而我们调用的getAge()方法它被代理过,所以他能拿到正确的值:10。


备注:若采用JDK动态代理不回存在此种现象,因为接口代理的都是方法。

另外:建议若代理的时候,final慎用。同时大多数情况下字段还是private掉吧,然后暴露出get方法给外部调用比较好。

总结


JdkDynamicAopProxy 入口方法是动态代理的 invoke() 方法,CGLIB 使用的是 DynamicAdvisedInterceptor.intercept()方法


JdkDynamicAopProxy使用的MethodInvocation 是: ReflectiveMethodInvocation 子类,

CGLIB 使用的是CglibMethodInvocation


它俩都是ProxyMethodInvocation接口的实现类。并且CglibMethodInvocation是继承自ReflectiveMethodInvocation的


CGLib更适合代理不需要频繁实例化的类,而Spring绝大多数Bean都是单例的,因此在Spring AOP中我极力推荐使用CGLib,它的功能更强大些

相关文章
|
5月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
593 0
|
4月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
6月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
6月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
安全 Java Spring
Spring之Aop的底层原理
Spring之Aop的底层原理
|
Java Spring 容器
【Spring AOP底层实现原理】
【Spring AOP底层实现原理】
500 0
|
设计模式 Java uml
Spring AOP 原理
Spring AOP 原理
141 0
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。