从根上理解Cglib与JDK动态代理

简介: 最近在阅读到了Spring源码对于两种动态代理使用在不同场景下的使用,两种方式各有利弊写一篇文加深自己的认识。文中对于源码的涉及较少,更多的是作者自己的理解和举例,然后通过部分源码验证。首先看两个面试经常会遇到的关于Spring的问题:

最近在阅读到了Spring源码对于两种动态代理使用在不同场景下的使用,两种方式各有利弊写一篇文加深自己的认识。文中对于源码的涉及较少,更多的是作者自己的理解和举例,然后通过部分源码验证。
首先看两个面试经常会遇到的关于Spring的问题:

@Configuration和@Component注解的不同

@Configuration修饰的类会被Cglib动态代理,在类内部方法相互调用添加了@Bean注解的方法时通过在切面方法中调用getBean()方法来保证调用该方法返回的都是同一个实例
@Component修饰的类不会被代理,每次方法内部调用都会生成新的实例,这样就不能保证其生成的对象是一个单例对象。

@Transactional失效的原因

@Transactional可以JDK或Cglib动态代理实现的事务(默认JDK),在Bean创建时如果检测到类中有@Transactional就会对其进行动态代理,如果类内部没有被@Transactional修饰的方法中调用了其它被@Transactional修饰的内部方法,那么此时事务注解是不会生效的,原因在于只有外部调用才会走代理增强逻辑而内部类的互相调用只是原对象的方法调用,没有经过代理类。

其实上面可以看出出Spring在使用两种代理方式时的不同处理:@Configuration修饰的类被Cglib动态代理后,类内部方法调用也可以走增强逻辑,而含有@Transactional注解的类无论是Cglib还是JDK动态代理都不能进行方法内部的相互调用。
两种代理方式的调用逻辑
JDK动态代理
标准的用法
public interface TestInterface {

void sayHello();

}

public static class Test implements TestInterface {

@Override
public void sayHello() {
    System.out.println("hello");
}

}

//需要定义一个实现了InvocationHandler接口的对象,目的是获取被代理类的实例和自定义增强方法
public static class MyInvocationHandler implements InvocationHandler{

//被代理类的实例对象
protected Object target;
public MyInvocationHandler(Object target) {
    this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("增强方法");
    //调用被代理类的实例对象通过反射执行目标方法
    Object result = method.invoke(target, args);
    return result;
}

}

public static void main(String[] args) {

Test test = new Test();
TestInterface testInterface = (TestInterface) Proxy.newProxyInstance(test.getClass().getClassLoader(), Test.class.getInterfaces(), new MyInvocationHandler(test));
testInterface.sayHello();

}
复制代码
下面是代理类的逻辑代码,这个代理类并不是用反编译内存中的代理类来获取得,是作者自己整了一个类似的,如果要获取真正的代理类代码网上方法很多
//代理类的父类,里面有生成代理类的主要逻辑
public static class Proxy{

//被代理对象实例的调用对象
protected InvocationHandler h;

}
//生成的代理类继承Proxy主要是为了使用父类中的InvocationHandler对象来调用被代理类对象的目标方法
//实现共同接口是为了获取需要增强的目标方法
public static class TestProxy extends Proxy implements TestInterface{

protected TestProxy(InvocationHandler h) {
    super(h);
}
@Override
public void sayHello() {
    try {
        //这里对获取接口方法做了简化处理
        //调用父类中存储的被代理对象的handler执行代理逻辑
        super.h.invoke(this, this.getClass().getInterfaces()[0].getMethods()[0],null);
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
}

}
复制代码
逻辑图

Cglib动态代理
标准的用法
public static class Test implements TestInterface {

@Override
public void sayHello() {
    System.out.println("hello");
}

}

//实现MethodInterceptor接口注册回调函数对代理类中所有方法进行拦截增强
public static class MyInvocationHandler implements MethodInterceptor {

//o为继承了被代理类的代理类对象,method为执行方法,objects为方法参数
//methodProxy为代理对象方法,其中有被代理方法和代理方法的映射关系
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("增强方法");
    //invokeSuper方法传入的对象必须是代理类的实例对象
    //invoke方法则可以穿入被代理类的实例对象,通过被代理类实例调用方法
    return methodProxy.invokeSuper(o,objects);
}

}

public static void main(String[] args) {

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Test.class);
enhancer.setCallback(new MyInvocationHandler());
TestInterface testInterface = (TestInterface)enhancer.create();
testInterface.sayHello();

}
复制代码
动态生成代理类的伪代码,省略了很多很多细节
//Cglib中都是通过代理类中的方法来替换被代理类,然后直接调用代理类对象的方法即可
public static class TestProxy extends Test{

public final void sayHello() {
    System.out.println("增强方法");
    super.sayHello();
}

}
复制代码
逻辑图

==============================================================
从上面可以看出Cglib和JDK最大的区别在于Cglib实现的动态代理并没有被代理类的实例对象,所有的方法调用都是通过代理类来实现的(子类方法 -> 增强逻辑 -> 子类代理方法 -> 父类方法),而JDK则同时生成了被代理类和代理类的实例对象,然后在代理类中保存有被代理类的引用,目标方法的调用还是被代理对象执行的。Cglib方法调用时是使用代理类对象内部方法的相互调用实现的,由于代理类的所有方法都进行了改写,所以内部调用也会被增强,JDK方法调用时是代理类对象和被代理类对象间方法的相互调用实现的,只有通过调用代理类对象的代理方法时才会走增强逻辑,而如果是被代理对象自己的内部调用,被代理对象方法没有改变,所以无法增强。 理解了这一点再看Spring动态代理的使用就好理解了
Spring源码验证
调用@Configuration注解的类时会用到的代理类拦截器
//Spring中Enhancer对象注册的三种拦截器
//回调数组,根据CALLBACK_FILTER中accept方法返回的索引值去从该数组中选择对应的Callback
private static final Callback[] CALLBACKS = new Callback[] {

  new BeanMethodInterceptor(),
  new BeanFactoryAwareMethodInterceptor(),
  NoOp.INSTANCE

};

//BeanMethodInterceptor的intercept方法,对父类中所有带有@Bean注解的方法都进行拦截增强
//无论是Spring通过反射实例化Bean还是配置类中方法的内部调用,都会通过BeanFactory来生成和获取Bean实例
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,

     MethodProxy cglibMethodProxy) throws Throwable {

//是否为Spring通过反射调用
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {

  //调用父类方法生成新的Bean对象,并将其注册到Spring上下文中
  return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);

}

//类方法的内部调用,从BeanFactory中获取bean
//即使通过内部方法直接调用为能保证获取的对象为同一实例
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
}
复制代码
Cglib对于@Transactional注解采用的代理类拦截器DynamicAdvisedInterceptor
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {

  if (this.advised.exposeProxy) {
     oldProxy = AopContext.setCurrentProxy(proxy);
     setProxyContext = true;
  }
  //Spring缓存了被代理类的实例
  //获取被代理类实例
  target = getTarget();
  if (target != null) {
     targetClass = target.getClass();
  }
  //获取目标方法的拦截器链,被@Transactional修饰的方法会有缓存方法和调用链关系
  List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
  Object retVal;
  if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
     Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
     //没有被@Transactional修饰的方法会直接调用被代理类本身来执行
     //此处和Cglib通用的处理不一样,Spring缓存和被代理实例,用被代理类实例来执行方法
     //所以未被注解修饰的方法调用注解修饰的方法不能触发拦截器
     retVal = methodProxy.invoke(target, argsToUse);
  }
  else {
     //@Transactional修饰的方法会通过代理类对象来执行,进入拦截器执行增强逻辑
     retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
  }
  retVal = processReturnType(proxy, target, method, retVal);
  return retVal;

}
finally {

  if (target != null) {
     releaseTarget(target);
  }
  if (setProxyContext) {
     AopContext.setCurrentProxy(oldProxy);
  }

}
}
复制代码
JDK的处理逻辑同样是调用被代理类来执行未加@Transactional注解的方法,就不多写了。
小结
Cglib动态代理与JDK动态代理的区别本质上应该对于代理对象的调用方式有差别,Cglib是直接将代理类对象作为目标对象使用,增强逻辑直接写入代理类的子类方法中,调用方法时只需一个代理类对象即可,而JDK则是将被代理类对象引用存放在代理类对象中,增强逻辑在代理对象中存放而实际执行方法还需要调用被代理对象。当然Cglib通过缓存被代理类的实例对象也可以做到JDK的效果。
两种代理方式一个通过继承获取类方法信息,一种通过接口获取类方法信息,在理解其原理后,如何选型使用还是看业务场景和两种方式的执行效率来决定。

相关文章
|
2月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
2月前
|
Java API 开发者
Jdk动态代理为啥不能代理Class?
该文章主要介绍了JDK动态代理的原理以及为何JDK动态代理不能代理Class。
Jdk动态代理为啥不能代理Class?
|
2月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
82 0
|
2月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
89 0
|
4月前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
24 0
|
4月前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
35 0
|
4月前
|
缓存 Java Maven
JDK 动态代理
JDK 动态代理
23 0
|
4月前
|
Java Spring
深入解析Spring源码,揭示JDK动态代理的工作原理。
深入解析Spring源码,揭示JDK动态代理的工作原理。
50 0
|
18天前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
|
2月前
|
Java 关系型数据库 MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【8月更文挑战第19天】在Linux上搭建Java Web应用环境,需安装JDK 1.8、Tomcat及MariaDB。本指南详述了使用apt-get安装OpenJDK 1.8的方法,并验证其版本。接着下载与解压Tomcat至`/usr/local/`目录,并启动服务。最后,通过apt-get安装MariaDB,设置基本安全配置。完成这些步骤后,即可验证各组件的状态,为部署Java Web应用打下基础。
42 1