Java代理:静态代理、JDK动态代理和CGLIB动态代理
代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式。所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。
代理模式分为静态代理、动态代理(JDK、CGLIB)。
静态代理
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
上面的静态代理UML图的一个例子,代理类和目标类都实现相同的接口。
创建一个Animal接口。
1 package com.proxy.example;
2
3 /**
4 * an animation interface
5 */
6 public interface Animal {
7 void call();
8 void hobby();
9 }
创建目标类Cat并实现Animal接口。
1 package com.proxy.example;
2
3 public class Cat implements Animal {
4 @Override
5 public void call() {
6 System.out.println("meow meow meow ~");
7 }
8
9 @Override
10 public void hobby(){
11 System.out.println("eating fish ~");
12 }
13 }
所以我们需要在目标对象Cat调用方法之前指出它已经饿了,该方法是使用静态代理来实现Cat的叫声,然后再进行调用。
1 package com.proxy.example.staticproxy;
2
3 import com.proxy.example.Animal;
4
5 /**
6 * 静态代理类, 实现了Animal接口
7 */
8 public class StaticProxyAnimal implements Animal {
9 private Animal impl; // 在代理对象中保存目标对象, 使代理类中持有目标对象
10
11 /**
12 * 传入目标对象
13 *
14 * @param impl 目标对象
15 */
16 public StaticProxyAnimal(Animal impl) {
17 this.impl = impl;
18 }
19
20 /**
21 * 调用代理对象中Cat#call的实现
22 */
23 @Override
24 public void call() {
25 System.out.println("Cat is hungry");
26 impl.call();
27 }
28
29 @Override
30 public void hobby() {
31 // 接口每添加新方法,旧的在这里实现这个方法,灵活性不够,在大型的项目不易于维护
32 }
33 }
猫的饥饿和喵喵叫的行为是通过调用静态代理来实现的。
1 package com.proxy.example.staticproxy;
2
3 import com.proxy.example.Animal;
4 import com.proxy.example.Cat;
5 import org.junit.Test;
6
7 public class Main {
8 @Test
9 public void staticProxy(){
10 Animal staticProxy = new StaticProxyAnimal(new Cat());
11 staticProxy.call(); // 猫的饥饿和喵喵叫行为是通过调用静态代理来实现的。
12 }
13 }
可以看到,通过保留目标类对象然后调用目标类的方法来实现静态代理。
但在某些情况下它还有明显的缺点:
- 当我们将新方法添加到Animal接口时,不仅需要添加此方法的实现来实现Cat类,而且因为代理类实现了Animal接口,所以代理类还必须实现Animal的新方法,这在项目规模较大时不利于维护。
- Animal调用的代理类实现是为Cat目标类的对象设置的。如果需要添加Dog目标类的代理,则必须为Dog类实现一个相应的代理类,这会使代理类的重用性变得不友好,并且太多的代理类难以维护。
在JDK动态代理中友好地解决了以上问题。
动态代理
JDK动态代理
动态代理类和静态代理类之间的主要区别在于,代理类的字节码不是在程序运行之前生成的,而是在程序运行时在虚拟机中自动创建的。
实现InvocationHandler接口
JDK动态代理类必须在反射包中实现java.lang.reflect.InvocationHandler接口,它负责实现接口的方法调用,它的实现类相当于一个切面。
1 package com.proxy.example.jdkdynamicproxy;
2
3 import java.lang.reflect.InvocationHandler;
4 import java.lang.reflect.Method;
5
6 public class TargetInvoker implements InvocationHandler {
7 // 代理中的目标对象
8 private Object target;
9
10 public TargetInvoker(Object target) {
11 this.target = target;
12 }
13
14 /**
15 *
16 * @param proxy 代理目标对象的代理对象,它是真实的代理对象。
17 * @param method 方法执行目标类的方法
18 * @param args 执行目标类的方法的args参数
19 * @return
20 * @throws Throwable
21 */
22 @Override
23 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
24 System.out.println("jdk agent before invocation"); // 执行切入的逻辑
25 Object result = method.invoke(target, args); // 执行原有的逻辑
26 System.out.println("jdk agent after invocation");
27 return result;
28 }
29 }
创建JDK动态代理类
创建JDK动态代理类的实例还使用反射包中的java.lang.reflect.Proxy类。通过调用代理Proxy#newproxyinstance静态方法创建,它是织入器,织入代码并生成代理类。
1 package com.proxy.example.jdkdynamicproxy;
2
3 import java.lang.reflect.Proxy;
4
5 public class DynamicProxyAnimal {
6 public static Object getProxy(Object target) throws Exception {
7 Object proxy = Proxy.newProxyInstance(
8 target.getClass().getClassLoader(), // 目标类的类加载器, 生成代理类的字节码加载器
9 target.getClass().getInterfaces(), // 需要代理的接口, 被代理类实现的接口可以在这里实现,可以是一个数组
10 new TargetInvoker(target) // 构建AOP的advice, 这里需要传入业务类的实例
11 );
12 return proxy;
13 }
14 }
通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
使用的ClassLoader,通常就是接口类的ClassLoader;
需要实现的接口数组,至少需要传入一个接口进去;
用来处理接口方法调用的实例InvocationHandler 。
最后,实现动态代理。
1 package com.proxy.example.jdkdynamicproxy;
2
3 import com.proxy.example.Animal;
4 import com.proxy.example.Cat;
5 import org.junit.Test;
6
7 public class Main {
8 @Test
9 public void jdkDynamicProxy() throws Exception {
10 Cat cat = new Cat();
11 Animal proxy = (Animal) DynamicProxyAnimal.getProxy(cat); // 将返回的Object强制转型为接口
12 proxy.call();
13 }
14 }
JDK在运行期动态创建class字节码并加载的过程。动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给 InvocationHandler 完成的。保存动态生成的代理的方法:
1 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
动态代理原理:
1 @CallerSensitive
2 public static Object newProxyInstance(ClassLoader loader,
3 Class<?>[] interfaces,
4 InvocationHandler h)
5 throws IllegalArgumentException
6 {
7 Objects.requireNonNull(h);
8
9 final Class<?>[] intfs = interfaces.clone();
10 final SecurityManager sm = System.getSecurityManager();
11 if (sm != null) {
12 checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
13 }
14
15 /*
16 * 获取代理类
17 */
18 Class<?> cl = getProxyClass0(loader, intfs);
19
20 /*
21 * Invoke its constructor with the designated invocation handler.
22 */
23 try {
24 if (sm != null) {
25 checkNewProxyPermission(Reflection.getCallerClass(), cl);
26 }
27
28 final Constructor<?> cons = cl.getConstructor(constructorParams); // 获取带有InvocationHandler参数的构造方法
29 final InvocationHandler ih = h;
30 if (!Modifier.isPublic(cl.getModifiers())) {
31 AccessController.doPrivileged(new PrivilegedAction<Void>() {
32 public Void run() {
33 cons.setAccessible(true);
34 return null;
35 }
36 });
37 }
38 return cons.newInstance(new Object[]{h}); // 把handler传入构造方法生成实例
39 } catch (IllegalAccessException|InstantiationException e) {
40 throw new InternalError(e.toString(), e);
41 } catch (InvocationTargetException e) {
42 Throwable t = e.getCause();
43 if (t instanceof RuntimeException) {
44 throw (RuntimeException) t;
45 } else {
46 throw new InternalError(t.toString(), t);
47 }
48 } catch (NoSuchMethodException e) {
49 throw new InternalError(e.toString(), e);
50 }
51 }
其中 getProxyClass(loader, interfaces) 方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。
1 /**
2 * 生成代理类。在调用此方法之前, 必须调用checkProxyAccess方法执行权限检查。
3 */
4 private static Class<?> getProxyClass0(ClassLoader loader,
5 Class<?>... interfaces) {
6 if (interfaces.length > 65535) {
7 throw new IllegalArgumentException("interface limit exceeded");
8 }
9
10 // 如果由实现了给定接口的给定加载器定义的代理类存在, 则将简单地返回缓存的副本;
11 // 否则, 它将通过ProxyClassFactory创建代理类
12 return proxyClassCache.get(loader, interfaces);
13 }
其中,代理类的生成主要是以下这两行代码。
1 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
2 proxyName, interfaces, accessFlags); // 生成代理类的字节码文件并保存
3
4 defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); // 使用类加载器将字节码加载到内存中
动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,第一代理类必须实现一个接口,如果没实现接口会抛出一个异常。第二性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢。其次,使用反射大量生成类文件可能引起 Full GC 造成性能影响,因为字节码文件加载后会存放在 JVM 运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起 Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少 Full GC 次数。
CGLIB动态代理
CGLIB (Code Generation Library),一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP等。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类,将切面逻辑加入到子类中,所以使用 CGLIB 实现 AOP 不需要基于接口。
CGLIB动态代理的实现机制是生成目标类的子类(Enhancer#create),该目标类是通过调用父类(目标类)的方法来实现的,然后在调用父类的方法时在代理中进行增强。
实现MethodInterceptor接口
与JDK动态代理的实现相比,CGLIB动态代理不需要实现与目标类相同的接口,而是通过方法拦截来实现代理。
1 package com.proxy.example.cglibproxy;
2
3 import org.springframework.cglib.proxy.MethodInterceptor;
4 import org.springframework.cglib.proxy.MethodProxy;
5
6 import java.lang.reflect.Method;
7
8 /**
9 * 方法拦截器——通过方法拦截接口调用目标类的方法, 然后增强被拦截的方法. 方法拦截器接口的拦截方法中有四个参数
10 */
11 public class TargetInterceptor implements MethodInterceptor {
12
13 /**
14 * 拦截方法
15 *
16 * @param obj obj代理类对象
17 * @param method 代理当前拦截的方法
18 * @param args 拦截方法参数
19 * @param methodProxy 与目标类相对应的代理类的代理方法
20 * @return result
21 * @throws Throwable 抛出的异常
22 */
23 @Override
24 public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
25 System.out.println("CGLIB Before Calling"); // 执行织入的操作
26 Object result = methodProxy.invokeSuper(obj, args); // 执行原有逻辑,注意这里是invokeSuper
27 System.out.println("CGLIB After Calling");
28 return result;
29 }
30 }
创建CGLIB动态代理类
使用 Enhancer 类创建CGLIB动态代理类。它是一个织入器,CGLIB动态代理中的核心类。
1 package com.proxy.example.cglibproxy;
2
3 import org.springframework.cglib.proxy.Enhancer;
4
5 /**
6 * 使用Enhancer类创建CGLIB动态代理类. 它是CGLIB动态代理中的核心类.
7 */
8 public class CglibProxy {
9 public static Object getProxy(Class<?> clazz) {
10 Enhancer enhancer = new Enhancer();
11 enhancer.setClassLoader(clazz.getClassLoader()); // 设置类加载器
12 enhancer.setSuperclass(clazz); // 设置父类
13 enhancer.setCallback(new TargetInterceptor()); // 使用织入器创建子类
14 return enhancer.create(); // 使用织入器创建子类
15 }
16 }
可以通过设置代理类的信息以及代理类所拦截的方法的回调执行逻辑来实现代理类。
CGLIB动态代理调用:
1 package com.proxy.example.cglibproxy;
2
3 import com.proxy.example.Animal;
4 import com.proxy.example.Cat;
5 import org.junit.Test;
6 import org.springframework.cglib.core.DebuggingClassWriter;
7
8 public class Main {
9 @Test
10 public void cglibDynamicProxy() throws Exception {
11 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
12 Animal cat = (Animal) CglibProxy.getProxy(Cat.class);
13 cat.call();
14 }
15 }
上面是在简单应用程序中实现CGLIB动态代理的方式。但是, Enhancer是常用的,并且在回调过滤器的使用中具有特色。当它拦截目标对象的方法时,它可以有选择地执行方法拦截,即选择代理方法的增强处理。现在再添加一个方法拦截实现:
1 package com.proxy.example.cglibproxy;
2
3 import org.springframework.cglib.proxy.MethodInterceptor;
4 import org.springframework.cglib.proxy.MethodProxy;
5
6 import java.lang.reflect.Method;
7
8 /**
9 * 方法拦截器2
10 */
11 public class TargetInterceptor2 implements MethodInterceptor {
12 @Override
13 public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
14 System.out.println("CGLIB Before calling TargetInterceptor2");
15 Object result = methodProxy.invokeSuper(obj, args);
16 System.out.println("CGLIB After calling TargetInterceptor2");
17 return result;
18 }
19 }
实现回调过滤器 CallbackFilter。
1 package com.proxy.example.cglibproxy;
2
3 import org.springframework.cglib.proxy.CallbackFilter;
4
5 import java.lang.reflect.Method;
6
7 /**
8 * 回调过滤器
9 */
10 public class TargetCallbackFilter implements CallbackFilter {
11 /**
12 * 使用Enhancer#setcallbacks设置多个方法拦截器的数组中的方法拦截器
13 *
14 * @param method 方法
15 * @return 返回的数字是数组的索引
16 */
17 @Override
18 public int accept(Method method) {
19 if ("hobby".equals(method.getName())) {
20 return 1;
21 } else {
22 return 0;
23 }
24 }
25 }
要演示如何调用不同的方法拦截器,需要在Enhancer设置中,使用 Enhancer#setcallbacks 设置多个方法拦截器。该参数是一个数组。 Targetcallbackfilter#accept返回的数字是数组的索引,它确定要回调哪个方法拦截器。
1 package com.proxy.example.cglibproxy;
2
3 import org.springframework.cglib.proxy.Callback;
4 import org.springframework.cglib.proxy.Enhancer;
5
6 public class CglibProxy2 {
7 public static Object getProxy(Class<?> clazz) {
8 Enhancer enhancer = new Enhancer();
9 enhancer.setClassLoader(clazz.getClassLoader());
10 enhancer.setSuperclass(clazz);
11 enhancer.setCallbacks(new Callback[]{new TargetInterceptor(), new TargetInterceptor2()}); // 设置回调数组
12 enhancer.setCallbackFilter(new TargetCallbackFilter()); // 设置回调过滤器
13 return enhancer.create();
14 }
15 }
根据代码实现逻辑,调用喵喵加的方法将调用 TargetInterceptor 类,而兴趣方法将调用 TargetInterceptor2 类。
1 @Test
2 public void dynamicProxy() throws Exception {
3 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
4 Animal cat = (Animal) CglibProxy2.getProxy(Cat.class);
5 cat.call();
6 cat.hobby();
7 }
执行结果如下:
总结:JDK动态代理实现目标类的接口,然后在构造动态代理时将目标类作为参数传递,因此代理对象保存目标对象,然后通过代理对象的 InvocationHandler 实现动态代理的操作。
CGLIB动态代理将通过配置目标类信息并使用ASM字节码框架来生成目标类的子类。调用代理方法时,代理的操作是通过拦截该方法来实现的。
通常,JDK动态代理使用接口来实现代理,而CGLIB动态代理使用继承来实现代理。
Java代理:静态代理、JDK动态代理和CGLIB动态代理的更多相关文章
- java的静态代理、jdk动态代理和cglib动态代理
Java的代理就是客户端不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理.使用代理有两个好处,一是可以隐藏委托类的实现:二是可以实现客户与委托类之间的解耦,在不修改委托类代码的情况 ...
- 代理模式之静态代理,JDK动态代理和cglib动态代理
代理模式,顾名思义,就是通过代理去完成某些功能.比如,你需要购买火车票,不想跑那么远到火车站售票窗口买,可以去附近的火车票代售点买,或者到携程等第三方网站买.这个时候,我们就把火车站叫做目标对象或者委 ...
- jdk动态代理和cglib动态代理底层实现原理详细解析(cglib动态代理篇)
代理模式是一种很常见的模式,本文主要分析cglib动态代理的过程 1. 举例 使用cglib代理需要引入两个包,maven的话包引入如下 <!-- https://mvnrepository.c ...
- JDK动态代理和CGLib动态代理简单演示
JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...
- 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...
- Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。
借鉴:http://jinnianshilongnian.iteye.com/blog/1508018 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional ...
- Spring <tx:annotation-driven>注解 JDK动态代理和CGLIB动态代理 区别。
基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...
- jdk动态代理和cglib动态代理的区别
一.原理区别: java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. 而cglib动态代理是利用asm开源包,对代理对象类的class文件 ...
- jdk 动态代理和 cglib 动态代理
原理区别: java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. 而cglib动态代理是利用asm开源包,对代理对象类的class文件加载 ...
- 动态代理:jdk动态代理和cglib动态代理
/** * 动态代理类:先参考代理模式随笔,了解代理模式的概念,分为jdk动态代理和cglib,jdk动态代理是通过实现接口的方式创建代理类的,cglib是通过继承类的方式实现的代理类的 * jdk动 ...
随机推荐
- weblogic漏洞初探之CVE-2015-4852
weblogic漏洞初探之CVE-2015-4852 一.环境搭建 1. 搭建docker 这里用了vulhub的环境进行修改:https://vulhub.org/ 新建个文件夹,创建两个文件doc ...
- 1.docker概述及其历史
一. 为什么会出现docker? 不用说, 肯定是时代进步的产物. 那么, 他为什么能火? 一定是解决了痛点问题. docker也不是一下子就火起来了, 他的火也是有一个过程的, 我们先来看看为什么会 ...
- freeswitch 编译安装后的配置
--------------------FreeSWITCH configuration------------Locations: prefix: /usr/local/freeswitchexec ...
- 双非Java的学习之旅以及秋招路程
个人信息: 趁着中秋写个帖子记录一下吧.渣渣本,无实习,无高质量证书,走了很多弯路,最后选择的Java后端.现在算是半躺平了,接了几个中小厂的offer保底,20w多的薪资,后面还有几家公司接着面.不 ...
- PTA——c++函数
1.在C++中,关于下列设置缺省参数值的描述中,()是正确的. 在指定了缺省值的参数右边,不能出现没有指定缺省值的参数: 2.使用地址作为实参传给形参,下列说法正确的是() 实参与形参操作的是同一对象 ...
- Elaticsearch倒排索引
ES倒排索引基本原理 索引(index)可以分为正序索引(Forward Indexes)和倒排索引(Inverted Index)两种.在关系型数据库中使用索引可以避免数据检索走全表扫描,将检索的时 ...
- 一起学习PHP的runkit扩展如何使用
这次又为大家带来一个好玩的扩展.我们知道,在 PHP 运行的时候,也就是部署完成后,我们是不能修改常量的值,也不能修改方法体内部的实现的.也就是说,我们编码完成后,将代码上传到服务器,这时候,我们想在 ...
- 一些PHP选项参数相关的函数
关于 PHP 的配置,我们大多数情况下都是去查看 php.ini 文件或者通过命令行来查询某些信息,其实,PHP 的一些内置函数也可以帮助我们去查看或操作这些配置参数.比如之前我们学习过的 关于php ...
- php 的一些问题
------------------------------------------------------------------------------ * 绝对路径转化为相对路径 <?ph ...
- 基于AM335X,如何搭建优良的Linux开发环境(下)
接着上一篇文章的Linux开发环境搭建,文章中详细讲解了 VMware14.1.1虚拟机安装.基于虚拟机安装Ubuntu14.04.3操作系统.安装Ubuntu14.04.3操作系统.安装虚拟机工具. ...