JDK 和 CGLib 实现动态代理和区别

在日常的开发中,Spring AOP 是一个非常常用的功能。谈到 AOP,自然离不开动态代理。

那么,基于 JDK 和 CGLib 如何实现动态代理,他们之间的区别和适用场景是什么呢?接下来,我们一起来探讨一下这个问题。

JDK 如何实现动态代理?

话不多说,我们直接对照着代码来查看。

代码示例

Hello 接口

public interface HelloInterface {

    /**
* 代理的目标方法
*/
void sayHello(); /**
* 未被代理处理的方法
*/
void noProxyMethod();
}

Hello 实现类

public class HelloImpl implements HelloInterface {

    @Override
public void sayHello() {
System.out.println("proxyMethod:sayHello");
} @Override
public void noProxyMethod() {
System.out.println("noProxyMethod");
}
}

MyInvocationHandler 实现 InvocationHandler 接口类

public class MyInvocationHandler implements InvocationHandler {

    /**
* 目标对象
*/
private Object target; /**
* 构造方法
*
* @param target
*/
public MyInvocationHandler(Object target) {
this.target = target;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("sayHello".equals(methodName)) {
// 比方说,mybaitis 中的 PooledConnection 利用 jdk 动态代理重新实现了 close 方法
System.out.println("change method");
return null;
}
System.out.println("invoke method");
Object result = method.invoke(target, args);
return result;
} }

动态代理神奇的地方就是:

  1. 代理对象是在程序运行时产生的,而不是编译期;
  2. 对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等。

️注意:从 Object 中继承的方法,JDK Proxy 会把hashCode()、equals()、toString()这三个非接口方法转发给 InvocationHandler,其余的 Object 方法则不会转发。详见 JDK Proxy官方文档

代码测试

public class MyDynamicProxyTest {

    public static void main(String[] args) {
HelloInterface hello = new HelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
// 构造代码实例
HelloInterface proxyInstance = (HelloInterface) Proxy.newProxyInstance(
HelloImpl.class.getClassLoader(),
HelloImpl.class.getInterfaces(),
handler);
// 代理调用方法
proxyInstance.sayHello();
proxyInstance.noProxyMethod();
}
}

打印的日志信息如下:

关键要点

结合上面的演示,我们小结一下 JDK 动态代理的实现,包括三个步骤:

  • 1.定义一个接口

    比如上面的 HelloInterface,Jdk 的动态代理是基于接口,这就是代理接口。

  • 2.编写接口实现类

    比如上面的 HelloImpl,这个就是目标对象,也就是被代理的对象类。

  • 3.编写一个实现 InvocationHandler 接口的类,代理类的方法调用会被转发到该类的 invoke() 方法。

    比如上面的 MyInvocationHandler。

CGLib 如何实现动态代理?

代码示例

Hello 类

无需定义和实现接口。

public class Hello {

    public String sayHello(String name) {
System.out.println("Hello," + name);
return "Hello," + name;
} }

CglibMethodInterceptor 实现 MethodInterceptor

/**
* 实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
*/
public class CglibMethodInterceptor implements MethodInterceptor { @Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("intercept param is " + Arrays.toString(args));
System.out.println("before===============" + method);
// 这里可以实现增强的逻辑处理s
Object result = methodProxy.invokeSuper(obj, args);
// 这里可以实现增强的逻辑处理
System.out.println("after===============" + method);
return result;
} }

️注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()equals()toString()等,但是getClass()wait()等方法不会(因为其他方法是 final,无法被代理),CGLIB 无法代理她们。

pom 依赖

	<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>

代码测试

public class CglibTest {

    /**
* 在需要使用 Hello 的时候,通过CGLIB动态代理获取代理对象
*/
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Hello.class);
enhancer.setCallback(new CglibMethodInterceptor());
// 给目标对象创建一个代理对象
Hello hello = (Hello) enhancer.create();
hello.sayHello("Alan");
}

打印的日志如下:

关键要点

结合上面的演示,我们小结一下 CGLIB 动态代理的实现:

  • 1.实现一个MethodInterceptor,方法调用被转发到该类的 intercept() 方法。
  • 2.使用 Enhancer 获取代理对象,并调用对应的方法。

JDK Vs CgLib

Java 1.3 后,提供了动态代理技术,允许我们开发者在运行期创建接口的代理实例,后来这项技术被用到了很多地方(比如 Spring AOP)。

JDK 动态代理主要对应到 java.lang.reflect 包下边的两个类:ProxyInvocationHandler

其中 InvocationHandler 是一个接口,可以通过实现该接口定义横切逻辑。

举个例子,在方法执行前后打印的日志(这里只是为了说明,实际应用一般不会只是简单的打印日志,一般用于日志、安全、事务等场景),并通过「反射机制」调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。

  • JDK 动态代理有一个限制:它只能为接口创建代理实例

    对于没有通过接口定义业务方法的类,如何创建动态代理实例呢?答案就是 CGLib。

  • CGLIB(Code Generation Library))是一个底层基于 ASM 的字节码生成库,它允许我们在「运行时」修改和动态生成字节码。

    CGLIB 通过继承方式实现代理,在子类中采用方法拦截的方式拦截所有父类方法的调用并顺势织入横切逻辑

JDK 和 CGLib 动态代理区别

1. JDK 动态代理实现原理

  • 通过实现 InvocationHandler 接口创建自己的调用处理器
  • 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 创建动态代理
  • 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数传入

JDK 动态代理是面向接口的代理模式,如果被代理目标没有接口则无能为力。

例如,Spring 通过 Java 的反射机制生产被代理接口的新的匿名实现类,重写了 AOP 的增强方法。

2. CGLib 动态代理原理

利用 ASM 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。

3. 两者对比

  • JDK 动态代理是面向接口的;
  • CGLib 动态代理是通过字节码底层继承代理类来实现,如果被代理类被 final 关键字所修饰,则无法被代理。

4.适用场景

  • 如果被代理的对象是个实现了接口的实现类,那么可以使用 JDK 动态代理。

    例如,Spring 会使用 JDK 动态代理来完成操作(Spirng 默认采用)

  • 如果被代理的对象没有实现接口,只有实现类,那么只能使用 CGLib 实现动态代理(JDK 不支持)。

    例如,被代理对象是没有接口的实现类,Spring 强制使用 CGLib 实现的动态代理。

性能对比

网上有人对于不通版本的 jdk 进行了测试,经过多次试验,测试结果大致如下:

  • 在 JDK 1.6 和 1.7 时,JDK 动态代理的速度要比 CGLib 要慢,但是并没有某些书上写的10倍差距那么夸张。
  • 在 JDK 1.8 时,JDK 动态代理的速度比 CGLib 快很多。

[idea] 很多时候,性能差异不一定是我们选择某种方式的绝对因素,我们更应该去考虑该技术适用的场景

例如,我们应用中绝大多数的性能差异可能主要在集中在磁盘 I/O,网络带宽等因素,动态代理这点性能差异可能只是占了非常小的比例。

Reference


END

JDK 和 CGLib 实现动态代理和区别的更多相关文章

  1. JDK和Cglib实现动态代理实例及优缺点分析

    Spring AOP使用的核心技术是动态代理,说到动态代理就不得不和设计模式中的代理模式联系起来,通过代理模式我们可以对目标类进行功能增强,在某个方法的执行前后增加一些操作,例如计算方法执行效率.打印 ...

  2. JDK和CGLIB生成动态代理类的区别

     关于动态代理和静态代理 当一个对象(客户端)不能或者不想直接引用另一个对象(目标对象),这时可以应用代理模式在这两者之间构建一个桥梁--代理对象. 按照代理对象的创建时期不同,可以分为两种: 静态代 ...

  3. 【4】JDK和CGLIB生成动态代理类的区别

    当一个对象(客户端)不能或者不想直接引用另一个对象(目标对象),这时可以应用代理模式在这两者之间构建一个桥梁--代理对象. 按照代理对象的创建时期不同,可以分为两种: 静态代理:事先写好代理对象类,在 ...

  4. JDK和CGLIB生成动态代理类的区别(转)

     关于动态代理和静态代理 当一个对象(客户端)不能或者不想直接引用另一个对象(目标对象),这时可以应用代理模式在这两者之间构建一个桥梁--代理对象. 按照代理对象的创建时期不同,可以分为两种: 静态代 ...

  5. jdk与cglib的动态代理

    JDK动态代理中包含一个类和一个接口: InvocationHandler接口: public interface InvocationHandler { public Object invoke(O ...

  6. java面试题之spring aop中jdk和cglib哪个动态代理的性能更好?

    在jdk6和jdk7的时候,jdk比cglib要慢: 在jdk8的时候,jdk性能得到提升比cglib要快很多: 结论出自:https://www.cnblogs.com/xuliugen/p/104 ...

  7. 学习CGLIB与JDK动态代理的区别

    动态代理 代理模式是Java中常见的一种模式.代理又分为静态代理和动态代理.静态代理就是显式指定的代理,静态代理的优点是由程序员自行指定代理类并进行编译和运行,缺点是一个代理类只能对一个接口的实现类进 ...

  8. JDK和CGLIB动态代理原理区别

    JDK和CGLIB动态代理原理区别 https://blog.csdn.net/yhl_jxy/article/details/80635012 2018年06月09日 18:34:17 阅读数:65 ...

  9. cglib实现动态代理简单使用

    Boss: package proxy.cglib; public class Boss{ public void findPerson() { System.out.println("我要 ...

随机推荐

  1. 【题解】codeforces 1B Spreadsheets

    题意翻译 人们常用的电子表格软件(比如: Excel)采用如下所述的坐标系统:第一列被标为A,第二列为B,以此类推,第26列为Z.接下来为由两个字母构成的列号: 第27列为AA,第28列为AB-在标为 ...

  2. 高速数字逻辑电平(8)之LVDS差分信号深度详解

    原文地址点击这里: LVDS(Low-Voltage Differential Signaling ,低电压差分信号)是美国国家半导体(National Semiconductor, NS,现TI)于 ...

  3. noip模拟10[入阵曲·将军令·星空](luogu)

    对于这次考试来说,总体考得还是不错的 就是有一个小问题,特判一定要判对,要不然和不判一样,甚至错了还会挂掉30分 还有一个就是时间分配问题,总是在前几个题上浪费太多时间,导致最后一个题完全没有时间思考 ...

  4. react的三大属性

    react的三大属性 state props  refs props 来自外部属性 states 来自内部状态 refs 用于表示组件内某个元素 state基础(最重要的属性) state是组件对象最 ...

  5. Apache Hudi在Hopworks机器学习的应用

    Hopsworks特征存储库统一了在线和批处理应用程序的特征访问而屏蔽了双数据库系统的复杂性.我们构建了一个可靠且高性能的服务,以将特征物化到在线特征存储库,不仅仅保证低延迟访问,而且还保证在服务时间 ...

  6. explicit 关键字 禁止隐式转换

    explicit可以抑制内置类型隐式转换,所以在类的构造函数中,使用explicit关键字,防止不必要的隐式转换

  7. 基于Yarp实现内网http穿透

    Yarp介绍 YARP是微软开源的用来代理服务器的反向代理组件,可实现的功能类似于nginx. 基于YARP,开发者可以非常快速的开发一个性能不错的小nginx,用于代理http(s)请求到上游的ht ...

  8. C#使用FtpWebRequest 基础连接已经关闭:连接被意外关闭(The underlying connection was closed:The connection was closed unexpectedly)

    公司内部开发的winform程序使用了FtpWebRequest下载FTP服务器的文件到本地. 大多数人运行良好,由于我们是试运行逐步有人加入到平台的使用,前两天突然有个别机器无法连接FTP服务器报出 ...

  9. 一分钟了解JDBC的构成和原理

    JDBC(一组接口组成) : 形式如下: 1:JDBC-ODBC桥接技术(100%不用) 在Windows中有ODBC技术,ODBC指的是开放数据库链接 是由微软提供的数据库连接应用,而Java可以利 ...

  10. bugku SKCTF管理系统

    这题hint是sql约束攻击...sql约束攻击其实我没了解过,当时就各种百度,现在总结一下 0x01: sql约束攻击:通常是sql查询语句select * from username= 'lin' ...