代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)
代理模式
代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问。
那么为什么要使用代理模式呢?
1、隔离,客户端类不能或者不想直接访问目标对象,代理类可以在远程客户端类和目标类之间充当中介。
2.代理类可以对业务或者一些消息进行预处理,做一些过滤,然后再将消息转给目标类,主要处理逻辑还是在目标类,符合开闭原则。
在我们生活中有很多体现代理模式的例子,如中介、媒婆、经纪人等等,比如说某个球队要签约一个球星,就需要和经纪人进行沟通,在一些编程框架中,也有很多地方使用代理模式,如Spring的AOP,java的RMI远程调用框架等。
代理模式分为静态代理和动态代理,动态代理又分为jdk动态代理和cglib动态代理,下面分别来阐述下这几种代理模式的区别。
静态代理
静态代理在使用的时候,需要定义一个接口或者父类,代理类和目标类(被代理类)都需要实现这个接口,代理类持有目标类的引用。我们以球员签约为例,湖人想要签下安东尼戴维斯,安东尼说,先和我的经纪人商讨签约情况,商谈成功之后再来找我签约。我们定义一个会谈的接口,这个接口提供一个签约的方法,再定义一个经纪人类和球员类,分别实现会谈接口的签约方法。经纪人和湖人说,想要签下戴维斯也可以,不过我们需要交易否决权,且最后一年是球员选项。湖人的魔术师想了想,詹姆斯巅峰期的尾巴也没几年了,反正这几年垃圾合同也签了不少,不在乎这一个,而且戴维斯正值巅峰,联盟前十球员,不算太亏,就同意了,毕竟还是总冠军重要。所以,经纪人在正式签约前,谈妥了戴维斯的球员选项和薪水,剩下的就需要戴维斯自己亲自签约了。然后经纪人就拉着戴维斯来签约了。(写博客的期间,魔术师辞职了,我........,算了,懒得改了)
/** * 会谈接口,有一个签约的方法 */ public interface Talk { public void sign(); } /** * 球员安东尼戴维斯,实现了签约的方法,需要本人亲自签约 */ public class Davis implements Talk { @Override public void sign() { System.out.println("签约了,5年2.25亿美元"); } } /** * 经纪人,也实现了签约的方法,持有球员的引用,但是具体签约流程还是必须由球员完成 */ public class Broker implements Talk { private Davis davis; public Broker(Davis davis){ this.davis = davis; } @Override public void sign() { System.out.println("我们拥有最后一年的球员选项"); davis.sign(); System.out.println("签约成功,交易否决权开始生效"); } } //** * 测试类,只需要调用经纪人的签约方法就可以了 */ public class StaticProxyTest { public static void main(String[] args) { Davis davis = new Davis(); Broker broker = new Broker(davis); broker.sign(); } } 控制台打印: 我们拥有最后一年的球员选项 签约了,5年2.25亿美元 签约成功,交易否决权开始生效
通过以上代码,我们发现,代理对象需要与目标对象实现一样的接口,当需要代理的对象很多的时候,就需要增加很多的类,假如代理接口需要新增一个方法,那么代理类和目标类都需要修改维护,那么有没有更好的解决方式呢?
动态代理
一、jdk动态代理
静态代理的代理类是我们在编译期就已经创建好了,而动态代理是是指代理类是程序在运行过程中创建的。jdk的动态代理是是基于接口的代理。
第一步:还是定义一个会谈接口:
/** * 会谈接口,有一个签约的方法 */ public interface Talk { public void sign(); }
第二步:定义一个球员,需要实现真正的签约方法
/** * 球员安东尼戴维斯,实现了签约的方法,需要本人亲自签约 */ public class Davis implements Talk { @Override public void sign() { System.out.println("签约了,5年2.25亿美元"); } }
第三步:定义一个实现了InvocationHandler接口的实现类,该类需要绑定目标类。
public class MyInvocationHandler implements InvocationHandler { //目标类(被代理的类) private Object target; public MyInvocationHandler(Object target){ this.target = target; } /** * * @param proxy 生成的代理类的实例 * @param method 被调用的方法对象 * @param args 调用method方法时传的参数 * @return method方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object object = null; System.out.println("我们拥有最后一年的球员选项"); //执行方法,相当于执行了Davis中的sign()方法, //当代理对象调用其方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 object = method.invoke(target,args); System.out.println("签约成功,交易否决权开始生效"); return object; } }
第四步:编写测试类:
public class DynamicProxyTest { public static void main(String[] args) { //被代理的对象 Talk talk = new Davis(); MyInvocationHandler myInvocationHandler = new MyInvocationHandler(talk); //传入代理对象的字节码文件和接口类型,让Proxy来生成代理类 Talk davisProxy = (Talk)Proxy.newProxyInstance(talk.getClass().getClassLoader(), talk.getClass().getInterfaces(),myInvocationHandler); //调用签约方法 davisProxy.sign(); } }
控制台打印:
我们拥有最后一年的球员选项
签约了,5年2.25亿美元
签约成功,交易否决权开始生效
可以看到控制台和静态代理打印的一模一样。下面来分析一下jdk动态代理的原理:
我们看到,客户端通过Proxy的静态方法newProxyInstance生成了代理类,该方法有三个参数,分别是代理类的类加载器,这个代理类需要实现的接口以及一个处理器InvocationHandler,当我们使用代理类调用sign()方法的时候,是怎么执行这个方法的前置操作和后置操作,从代码来看,我们并没有显示的调用MyInvocationHandler的invoke方法,但是这个方法确实被执行了,究竟是哪里调用的呢?我们来看一下newProxyInstance的源码:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
源码有一些检验判断,我们暂且忽略,重点是看怎么创建代理类以及代理类调用方法的时候如何调用的invoke方法的
重点剖析这行代码:
Class<?> cl = getProxyClass0(loader, intfs);
这个方法根据传入的类加载器和接口类型生成了一个类,这个类即是代理类。点进去:
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
英文注释写到:如果缓存中有代理类了直接返回,否则将由ProxyClassFactory创建代理类。我们再来看一下ProxyClassFactory:
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // 所有代理类的前缀名都以$Proxy开头 private static final String proxyClassNamePrefix = "$Proxy"; // 下一个用于生成唯一代理类名的数字 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* */ Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* *验证该Class对象是不是接口 */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* *验证此接口不是重复的 */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * 记录非公共代理接口的包,以便代理类将在同一个包中定义 * 验证所有非公共代理接口都在同一个包中 */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * 为代理类选择一个全限定类名 */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * 生成代理类的字节码文件 */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }
我们看到,最终调用了ProxyGenerator的generateProxyClass方法生成字节码文件。回到newProxyInstance方法中,我们看看这几行代码:
//代理类构造函数的参数类型 1、private static final Class<?>[] constructorParams = { InvocationHandler.class }; 2、final Constructor<?> cons = cl.getConstructor(constructorParams); 3、 cons.newInstance(new Object[]{h});
代理类实例化的代码在第三行,通过反射调用代理类对象的构造方法,选择了这个InvocationHandler为参数的构造方法,这个h就是我们传递过来的实现了InvocationHandler的实例。所以,我们猜测是生成的代理类持有我们前文定义的MyInvocationHandler实例,并调用里面的invoke方法。所以,我们通过反编译来看下生成的代理类的源码。
使用IDEA,在VM options一栏中输入:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,就会在项目中生成一个代理类:
package com.sun.proxy; import chenhuan.designpattern.proxy.staticproxy.Talk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; //该类继承了Proxy类,实现了Talk接口 public final class $Proxy0 extends Proxy implements Talk { //声明了一些Method变量,后面会用到 private static Method m1; private static Method m2; private static Method m3; private static Method m0; //代理类的构造方法调用父类的构造方法 public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } //实现sign()方法,注意传入的是m3, public final void sign() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); //加载Talk接口,并获取其sign方法 m3 = Class.forName("chenhuan.designpattern.proxy.staticproxy.Talk").getMethod("sign"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
至此,我们发现,代理类通过调super.h.invoke(this, m2, (Object[])null);执行我们实现的InvocationHandler接口的invoke方法。复盘下jdk动态代理的实现原理的几大步骤:
1、新建一个接口
2、为接口实现一个代理类
3、创建一个实现了InvocationHandler接口的处理器
4、通过Proxy的静态方法,根据类加载器,实现的接口,以及InvocationHandler处理器生成一个代理类
①为接口创建代理类的字节码文件
②使用ClassLoader将字节码文件加载到JVM
③创建代理类实例对象,执行对象的目标方法
我们看到,使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象,那么,有没有不需要目标类实现接口的动态代理呢?让我们来看下cglib动态代理。
二、cglib动态代理
使用cglib需要引入cglib jar包,本篇案例使用的是cglib2.2.jar
直接先来看下代码实现:
1、先定义一个目标类
package chenhuan.designpattern.proxy; public class Davis { public void sign() { System.out.println("签约了,5年2.25亿美元"); } }
2、定义一个拦截器,
public class MyMethodInterceptor implements MethodInterceptor { /** * * @param o 表示增强的对象,即实现这个接口类的一个对象 * @param method 表示要被拦截的方法 * @param objects 表示要被拦截方法的参数 * @param methodProxy * @return 表示要触发父类的方法对象 * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("我们拥有最后一年的球员选项");
//调用代理类实例上的proxy方法的父类方法 Object object = methodProxy.invokeSuper(o,objects); System.out.println("签约成功,交易否决权开始生效"); return object; } }
3、使用字节码增强器来生成代理类:
public class CglibProxyTest { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); //设置enhancer的父类对象 enhancer.setSuperclass(Davis.class); //设置enhancer的回调对象,就是我们定义的拦截器 enhancer.setCallback(new MyMethodInterceptor()); //生成代理类 Davis davis = (Davis)enhancer.create(); davis.sign(); } }
cglib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。需要注意的是,cglib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
本文感觉篇幅过长,就不分析cglib动态代理的源码了 。
总结:静态代理由程序员创建代理类,在程序运行前代理类就已经存在了,并且代理类和目标类都要实现相同的接口。当需要代理的对象很多的时候,就需要增加很多的类,假如代理接口需要新增一个方法,那么代理类和目标类都需要修改维护,不易维护。jdk动态代理需要目标类实现接口,也就是说jdk动态代理只能对该类中实现了目标接口的方法进行代理,这个在实际编程中可能存在局限性,cglib动态代理完全不受代理类必须实现接口的限制,其生成的代理类是目标类的子类。
代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)的更多相关文章
- Java-JDK动态代理(AOP)使用及实现原理分析
Java-JDK动态代理(AOP)使用及实现原理分析 第一章:代理的介绍 介绍:我们需要掌握的程度 动态代理(理解) 基于反射机制 掌握的程度: 1.什么是动态代理? 2.动态代理能够做什么? 后面我 ...
- Java中的代理模式--静态代理和动态代理本质理解
代理模式定义:为其他对象提供了一种代理以控制对这个对象的访问. 代理模式的三种角色: Subject抽象主题角色:抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求. Real ...
- java 代理模式-静态代理与动态代理
最近在研究SpringAOP,当然要学习AOP就要知道这么健硕.强大的功能的背后究竟隐藏着怎样不可告人的“秘密”?? 接下来就是查阅了许多资料详细的研究了一下Java的代理模式,感觉还是非常非常重要的 ...
- 代理模式:利用JDK原生动态实现AOP
代理模式:利用JDK原生动态实现AOP http://www.cnblogs.com/qiuyong/p/6412870.html 1.概述 含义:控制对对象的访问. 作用:详细控制某个(某类)某对象 ...
- java 设计模式之单利模式以及代理模式(静态)
1:单利模式: public class Singleton { private static Singleton uniqueInstance = null; private Singleton() ...
- 代理模式 静态代理、JDK动态代理、Cglib动态代理
1 代理模式 使用代理模式时必须让代理类和被代理类实现相同的接口: 客户端通过代理类对象来调用被代理对象方法时,代理类对象会将所有方法的调用分派到被代理对象上进行反射执行: 在分派的过程中还可以添加前 ...
- Java代理模式/静态代理/动态代理
代理模式:即Proxy Pattern,常用的设计模式之一.代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问. 代理概念 :为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委 ...
- Java JDK 动态代理(AOP)使用及实现原理分析
一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 代理模式U ...
- 【Java】代处理?代理模式 - 静态代理,动态代理
>不用代理 有时候,我希望在一些方法前后都打印一些日志,于是有了如下代码. 这是一个处理float类型加法的方法,我想在调用它前打印一下参数,调用后打印下计算结果.(至于为什么不直接用+号运算, ...
随机推荐
- 死锁问题------------------------INSERT ... ON DUPLICATE KEY UPDATE*(转)
前言 我们在实际业务场景中,经常会有一个这样的需求,插入某条记录,如果已经存在了则更新它如果更新日期或者某些列上的累加操作等,我们肯定会想到使用INSERT ... ON DUPLICATE K ...
- HTTP Status 404 – Not Found
一般都是配置中的问题,这次发现扫描controller时,自己的包是com.aaa.conlller,而springmvc.xml中扫描的是com.aaa.controller,,多写了一个l
- usb2.0、usb3.0、usb3.1、type-c 接口含义与区别
简单说: usb3.0 比2.0的传输速率快,充电快,能向下兼容2.0 usb3.1 通常是指 usb3.1 gen2,比3.0的传输速率更快.充电更快,同兼容 type-c 通常是指 usb3.1的 ...
- HTML5的Rang对象
基本概念 Range对象代表页面上的一段连续的区域.通过Range对象,可以获取或修改网页上的任何区域. Selection与Range对象的使用 <body> <script> ...
- eclipse和sublime3打开freemarker(.ftl)文件
1.eclipse如何打开freemarker? https://jingyan.baidu.com/article/49ad8bce5ea95d5834d8fa9e.html 2.sublime3如 ...
- HDU 2516 斐波那契博弈
点这里去看题 n为斐波那契数时,先手败,推断方法见算法讲堂 #include<bits/stdc++.h> using namespace std; int main() { ],i,n, ...
- C#关于xml文件和TreeView之间的转换解析
主窗体: using System; using System.Collections; using System.Collections.Generic; using System.Componen ...
- JS获取form表单数据
以下代码可放在一个js文件中,以便通用: //获取指定表单中指定标签对象 function getElements(formId, label) { var form = document.getEl ...
- 运维工具pssh和pdsh安装和使用
1. pssh安装与使用 1.1 pssh安装 [root@server]# wget http://peak.telecommunity.com/dist/ez_setup.py [root@ser ...
- linux redis 多实例安装
前言: Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件. 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表( ...