设计模式学习---第三节:代理模式
来源:互联网 发布:网易电台录制软件 编辑:程序博客网 时间:2024/06/01 08:34
代理模式有几种,虚拟代理,计数代理,远程代理,动态代理。主要分为两类,静态代理和动态代理。
静态代理
--在程序运行前就编译好的,而不是由程序动态产生代理类
静态代理模式其实很常见,比如买火车票这件小事:黄牛相当于是火车站的代理,我们可以通过黄牛买票,但只能去火车站进行改签和退票。在代码实现中相当于为一个委托对象realSubject提供一个代理对象proxy,通过proxy可以调用realSubject的部分功能,并添加一些额外的业务处理,同时可以屏蔽realSubject中未开放的接口。
1、RealSubject 真实主题角色,是实现抽象主题接口的类;
2、Proxy 是代理类,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。
3、Subject 抽象主题角色,是一个接口。该接口是对象和它的代理共用的接口;
4、request() 是委托类和代理类的共同方法;
具体代码实现如下:
interface Subject { void request();}class RealSubject implements Subject { public void request(){ System.out.println("RealSubject"); }}class Proxy implements Subject { private Subject subject; public Proxy(Subject subject){ this.subject = subject; } public void request(){ System.out.println("begin"); subject.request(); System.out.println("end"); }}public class ProxyTest { public static void main(String args[]) { RealSubject subject = new RealSubject(); Proxy p = new Proxy(subject); p.request(); }}
静态代理实现中,一个委托类对应一个代理类,代理类在编译期间就已经确定。
动态代理
动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为jdk动态代理和cglib动态代理,下面通过一个例子看看如何实现jdk动态代理。
JDK动态代理
jdk动态代理使用的局限性通过反射类Proxy
和InvocationHandler
回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方方式实现动态代理。
1、定义业务逻辑
public interface Service { //目标方法 public abstract void add(); } public class UserServiceImpl implements Service { public void add() { System.out.println("This is add service"); } }
2、利用java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口定义代理类的实现。
class MyInvocatioHandler implements InvocationHandler { private Object target; public MyInvocatioHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("-----before-----"); Object result = method.invoke(target, args); System.out.println("-----end-----"); return result; } // 生成代理对象 public Object getProxy() { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); return Proxy.newProxyInstance(loader, interfaces, this); }}
3、使用动态代理
public class ProxyTest { public static void main(String[] args) { Service service = new UserServiceImpl(); MyInvocatioHandler handler = new MyInvocatioHandler(service); Service serviceProxy = (Service)handler.getProxy(); serviceProxy.add(); }}
执行结果:
-----before-----This is add service-----end-----
代理对象的生成过程由Proxy类的newProxyInstance方法实现,分为3个步骤:
1、ProxyGenerator.generateProxyClass
方法负责生成代理类的字节码,生成逻辑比较复杂,有兴趣的同学可以继续分析源码 sun.misc.ProxyGenerator;
// proxyName:格式如 "com.sun.proxy.$Proxy.1";// interfaces:代理类需要实现的接口数组;// accessFlags:代理类的访问标识;byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
2、native方法Proxy.defineClass0
负责字节码加载的实现,并返回对应的Class对象。
Class clazz = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
3、利用clazz.newInstance
反射机制生成代理类的对象;
反编译代理类
为了更清楚的理解动态代理,通过以下方式把代理类字节码生成class文件。
byte[] classFile = ProxyGenerator.generateProxyClass("com.sun.proxy.$Proxy.1", service.getClass().getInterfaces());FileOutputStream out = new FileOutputStream("com.sun.proxy.$Proxy.1.class");out.write(classFile);out.flush();
使用 反编译工具 jad jad com.sun.proxy.$Proxy.1
看看代理类如何实现,反编译出来的java代码如下:
public final class $proxy1 extends Proxy implements Service { public $proxy1(InvocationHandler invocationhandler) { super(invocationhandler); } public final boolean equals(Object obj) { try { return ((Boolean)super.h.invoke(this, m1, new Object[] { obj })).booleanValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)super.h.invoke(this, m2, null); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void add() { try { super.h.invoke(this, m3, null); return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return ((Integer)super.h.invoke(this, m0, null)).intValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } private static Method m1; private static Method m2; private static Method m3; private static Method m0; static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("zzzzzz.Service").getMethod("add", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch(NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch(ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } }}
从上述代码可以发现:
1、生成的$proxy1继承自Proxy类,并实现了Service接口。
2、执行代理对象的方法,其实就是执行InvocationHandle对象的invoke方法,传入的参数分别是当前代理对象,当前执行的方法和参数。
super.h.invoke(this, m3, null);
JDK动态代理步骤
1. 创建一个实现InvocationHandler接口的类,它必须实现invoke()方法
2. 创建被代理的类及接口
3. 调用Proxy的静态方法,创建一个代理类
4. 通过代理调用方法
而为什么要进行如此操作,可以从Proxy和InvocationHandler的源码中找打答案。对源码不感兴趣的可以将下面的源码部分小节略过。
JDK动态代理原理与源码
newProxyInstance()方法的源码:
可以看到,获得代理类的代码是Class<?>cl = getProxyClass0(loader,intfs);
并由此获得代理类的构造函数,生成代理类的实例返回给该方法的调用者。
继续跟进getProxyClass0()方法:
还是没有看到代理类是怎么生成的,只知道代理类是从proxyClassCache中取得的,这个变量是与缓存相关的一个对象,查看该变量的声明与初始化:可以发现proxyClassCache是个用来缓存代理类的类变量,大家知道类变量的特点是与类一一对应,在一个虚拟机中类只有一个,对应着在一个虚拟机中类变量也只有一个,且在此处,在Proxy类被加载的时候就赋值了。在赋值操作的参数中有ProxyClassFactory()这么一个构造函数,这个是动态代理中的关键:生成代理类的类文件字节码。继续跟进去,找到代理类的生成之处了:在ProxyClassFactory中,可以看到产生代理类的具体逻辑,大致上是,根据传递的被代理类及其实现的接口生成代理类的字节码加载到缓存中,但是加载到缓存中只是一个.java文件也不能用,所以底层还有编译等操作。到这里,可以大致的看清JDK中动态代理的面孔了,实现的步骤为:1. 创建代理类的源码;
2. 对源码进行编译成字节码;
3. 将字节码加载到内存;
4. 实例化代理类对象并返回给调用者;
底层的代码我们看不到,但是我们可以查看其生成的字节码:
生成的字节码比较长,但是在字节码中最关键的信息是代理类的声明:public final class $Proxy1 extends Proxy
可以看到生成的代理类是继承了Proxy类的,这就是说明了为什么使用JDK动态代理不能实现继承式动态代理,原因是Java不允许多继承,而生成的代理类本身就已经继承了Proxy类。
至此,JDK的动态代理的使用及底层原理分析完毕,揭下动态代理的神秘面纱,果然是枚美女。
至于最底层的native方法是怎么动态生成代理类的字节码我们也可以简单的模拟一下,先分析下模拟的步骤:首先要生成一段代理类的源码,然后将源码编译后生成代理类的实例返回给调用者。依据此步骤开始编写我们的模拟代码:
运行测试代码,结果和手工编写的结果一致,完成了JDK中动态代理的实现模拟。
cglib动态代理
前面分析到,因为Java只允许单继承,而JDK生成的代理类本身就继承了Proxy类,因此,使用JDK实现的动态代理不能完成继承式的动态代理,但是我们可以使用cglib来实现继承式的动态代理。
大名鼎鼎的Spring中就含有cglib动态代理,在此也以Spring中自带的cglib完成动态代理的实现:
- 设计模式学习---第三节:代理模式
- 设计模式学习----代理模式
- 设计模式学习-----代理模式
- 设计模式学习--代理模式
- 设计模式学习--代理模式
- 设计模式学习--代理模式
- 设计模式学习-代理模式
- 学习设计模式-代理模式
- 设计模式学习-代理模式
- 设计模式第三节:Abstract Factory(抽象工厂)
- 设计模式学习--------12.代理模式学习
- 设计模式学习--------12.代理模式学习
- 设计模式学习7 -- Proxy:代理模式
- 设计模式学习-Proxy(代理模式)
- 学习设计模式——代理模式
- 设计模式之代理模式学习
- 设计模式学习笔记之代理模式
- 设计模式学习4--代理模式
- 研究方向的一点思考
- CentOS 一键搭建 L2TP VPN 服务器
- 九度OJ题目1001:A+B for Matrices
- Win10 环境下安装配置 zsh
- Cento系统下docker的安装与卸载
- 设计模式学习---第三节:代理模式
- L2TP/IPSec一键安装脚本
- hdu 1542 矩形交(线段树)
- CentOS7服务器安装mysql
- Mac shell使用技巧总结
- 3个跨域请求 解释异步执行
- 线段树之扫描线之周长并
- JNI源码分析 (并实现JNI动态注册)
- Python知识点