动态代理由浅入深
来源:互联网 发布:python string length 编辑:程序博客网 时间:2024/05/18 01:00
实际生活中,我们经常听说中介、代理商、加盟等关键名词。试想一下,为什么需要这些?
就说说中介:就我本人身在上海来说,如果我要卖房子,我想找中介。原因:我每天工作很忙,卖房子也不是一天两天能找到买家,除了我的工作外,我没有时间浪费在上面,给我减轻负担。
再说一些品牌商,他们的核心就是打响了品牌,使用技术早就了这些好的商品,但是这些商品需要卖出去,自己去卖?人力、精力都不够用。所以他们就需要找代理商帮他去卖。
基于上述的问题,其实体现了专业分工的思想。实际开发中,一个类中,方法除了当前需要的核心操作,其他次要的可以丢给代理类去做。这样分工明确,业务分明。这就是我们接下来要说的动态代理。
动态代理的作用:可以在不改变原有类的基础上对方法进行增强。
什么是动态代理?这里2个关键字:动态和代理。
首先说下代理:这里涉及到一种设计模式–代理模式。我先不解释代理模式,我先说个词语:代理商。那么这个词语相信大家都听过,这个代理商和我们这里的代理是一样的意思。
所以动态代理,就是java虚拟就动态的生成了这个代理,而不是我们开发者手写java代码生成的。
一:快速入门-动态代理
需求:卖的奶茶和制造的奶茶需要加上品牌名“一点点”
首先我们直接先看动态代理的实现:只要记住动态代理的实现就是一种格式,固定步骤。
首先需要一个接口:奶茶
public interface Milk { //奶茶接口,有卖奶茶的方法 public void sell(String milkName); //制造奶茶 public void make();}
然后一个品牌商:一点点
public class YiDianDian implements Milk { @Override public void sell(String milkName) { System.out.println("卖奶茶"); } @Override public void make() { System.out.println("制造奶茶"); }}
动态代理生成 一点点 的代理商
public class Test { public static void main(String[] args) { System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //这是一点点 奶茶 final Milk yiDianDian = new YiDianDian(); //获取一点点的类加载器 ClassLoader classLoader = yiDianDian.getClass().getClassLoader(); //获取一点点实现的接口 Class<?>[] interfaces = yiDianDian.getClass().getInterfaces(); /* * 动态代理创建一点点 的代理商 * 我们需要记住这种创建代理对象的格式: * 第一个参数:类加载器,一般写被代理对象的类加载器 * 第二个参数:被代理对象实现的接口 * 第三个参数:处理器 InvocationHandler接口的实现类。实现invoke方法,用来指定代理做的事情。 * */ Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() { /* * * 代理商做的事情就在这个invoke方法里书写, * 这个方法3个参数: * 第一个参数:代理商本身,invoke方法中不能使用,使用就报错。原因后面介绍。 * 第二个参数:正在执行的方法。这里可以理解成一点点这个品牌需要执行的方法。 * 第三个参数:正在执行的方法需要的参数。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //输出执行的方法的名称 System.out.println("一点点的代理商代替 一点点品牌执行:"+method.getName()+"方法"); //输出执行的方法下需要的参数 System.out.println("一点点品牌传递过来的的参数:"+Arrays.toString(args)); //执行被代理对象本身方法原有的功能。 System.out.println("**********一点点品牌**********"); method.invoke(yiDianDian, args); return null; } }); proxyYiDianDian.sell("红茶"); proxyYiDianDian.make(); }}
结果:
二:代理模式-静态代理
代理模式:创建一个对象的代理,以控制对这个对象的访问。通俗来讲,就是创建了一个中介(代理)。
首先需要一个接口:奶茶
public interface Milk { //奶茶接口,有卖奶茶的方法 public void sell(String milkName); //制造奶茶 public void make();}
然后一个品牌商:一点点
public class YiDianDian implements Milk { @Override public void sell(String milkName) { System.out.println("卖奶茶"); } @Override public void make() { System.out.println("制造奶茶"); }}
代理类
public class ProxyYiDianDian implements Milk { //代理商 拥有 被代理商的引用 private Milk yiDianDain; public ProxyYiDianDian(Milk milk) { this.yiDianDain = milk; } @Override public void sell(String milkName) { System.out.println("***********一点点品牌*****************"); yiDianDain.sell(milkName); } @Override public void make() { System.out.println("***********一点点品牌*****************"); yiDianDain.make(); }}
测试:
public class Test2 { public static void main(String[] args) { //被代理对象 一点点品牌 Milk yiDianDian = new YiDianDian(); //代理对象 代理商 Milk proxyYiDianDian = new ProxyYiDianDian(yiDianDian); //执行sell方法 proxyYiDianDian.sell("红茶"); //执行make方法 proxyYiDianDian.make(); }}
结果:
为什么需要动态代理:
就需求而言,制造和售出方法都添加打印品牌的名字,代理的原因就是因为我们所关注的售卖和制造的主要代码保持不变,创建代理类来在原有的功能上添加核心操作之外的步骤:打印一点点的品牌名。这样实现了每个方法的核心操作和打印品牌的业务分离。代码业务清晰。但是这个方法存在很明显缺点,当我们需要添加打印品牌的方法很多的时候,我们需要每一个方法都要添加这个输出语句,很是麻烦。而且后期一点点品牌发生变化,比如将一点点改成两点点,这是候每个方法都要修改,作为一个程序员真是心中十万只羊驼飘过。所以为了维护方便,我们需要使用动态代理。
其实这也就是说静态代理的缺点:
1.如果需要代理的方法很多的话,那么我们需要对每一个方法做实现,非常的麻烦。
2.后期如果接口增加新的方法,我们需要对java文件做改写。维护麻烦。
而动态代理完全避免了这些。因为这是java虚拟机做的事情。
动态代理和静态代理的区别:
通过上面的案例,我们发现,我们静态代理是需要我们手动的书写一个java文件,而动态代理是不需要手动书写一个java文件的。
我们学习java的都知道,我们创建一个对象,首先需要书写一个java文件,然后将java文件编译成class文件,类加载器将class文件加载到内存中。然后我们可以创建此对象的实例。而上述的代理商proxyYiDianDain这个对象是没有java文件的,也没有class文件,是java虚拟机直接在内存中生成了class对象.从而创建这个class对象的实例,当然这个实例可以按照我们开发者的需求做任何功能的改写,代替原有的对象。这就是动态代理含义的本质。
动态代理引用场景介绍:
通过代理模式,我们发现我们使用代理模式的目的是为了实现专业分工。所以在开发中当我们除了原始核心类以外的其他功能,比如说事务的管理,权限等,业务逻辑方法每个都要添加事务的管控和权限的判断是非常麻烦的,而这些东西,和我们方法的核心操作关系不大,所以我们可以将这些操作用专门的动态代理来处理。
三:动态代理深入理解-源码解析
1.Proxy.newProxyInstance静态方法解析
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{ if (h == null) { throw new NullPointerException(); } /* * 1.核心操作,java虚拟机根据接口和类加载器,在内存中生成代理类的class对象。 */ Class<?> cl = getProxyClass0(loader, interfaces); try { //2.获取代理类的构造方法 final Constructor<?> cons = cl.getConstructor(constructorParams); //3.获取我们调用这个方法传递过来的处理器。 final InvocationHandler ih = h; SecurityManager sm = System.getSecurityManager(); if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { //4.这里可以发现,通过代理类的构造方法和处理器来创建这个代理类的实例。也就是创建动态代理对象 return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } }
通过上面的4步我们可以发现,其实动态代理,就是java虚拟机动态生成了一个class对象,然后创建了一个对象。
所以接下来我们看看java虚拟机动态生成的这个class对象的字节码文件
2.动态代理类的字节码文件
在测试类的最上面加上如下的一句代码,让java虚拟机生成的字节码文件输出到硬盘。
System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
然后再项目下添加如下目录:com/sun/proxy
执行代码:
public static void main(String[] args) { System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //这是一点点 奶茶 final Milk yiDianDian = new YiDianDian(); //获取一点点的类加载器 ClassLoader classLoader = yiDianDian.getClass().getClassLoader(); //获取一点点实现的接口 Class<?>[] interfaces = yiDianDian.getClass().getInterfaces(); /* * 动态代理创建一点点 的代理商 * 我们需要记住这种创建代理对象的格式: * 第一个参数:类加载器,一般写被代理对象的类加载器 * 第二个参数:被代理对象实现的接口 * 第三个参数:处理器 InvocationHandler接口的实现类。实现invoke方法,用来指定代理做的事情。 * */ Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() { /* * * 代理商做的事情就在这个invoke方法里书写, * 这个方法3个参数: * 第一个参数:代理商本身,invoke方法中不能使用,使用就报错。原因后面介绍。 * 第二个参数:被代理对象本身所执行的方法。这里可以理解成一点点这个品牌需要执行的方法。 * 第三个参数:被代理对象本身所执行的方法需要的参数。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //输出执行的方法的名称 System.out.println("一点点的代理商代替 一点点品牌执行:"+method.getName()+"方法"); //输出执行的方法下需要的参数 System.out.println("一点点品牌传递过来的的参数:"+Arrays.toString(args)); //执行被代理对象本身方法原有的功能。 System.out.println("**********一点点品牌**********"); method.invoke(yiDianDian, args); return null; } }); proxyYiDianDian.sell("红茶"); }
可以发现,com/sun/proxy目录下生成了一个class文件,然后使用反编译工具打开,生成的代理类源码如下:
package com.sun.proxy;import com.itheima.proxy.Milk;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements Milk{ private static Method m1; private static Method m3; private static Method m0; private static Method m2; public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final boolean equals(Object paramObject) throws { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } /** * 可以发现,代理类的所有的方法都执行了this.h.invoke方法 */ public final void sell(String paramString) throws { try { this.h.invoke(this, m3, new Object[] { paramString }); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("com.xu.proxy.Milk").getMethod("sell", new Class[] { Class.forName("java.lang.String") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } }}
通过上述的源码我们可以发现,代理类的所有的方法都执行了this.h.invoke()方法
我们将sell方法抽出来理解一下:
public final void sell(String paramString) throws { try { /* * sell方法的核心操作就是这个 * this代表当前对象, * h 代表我们传递过来的 invacationHandler的实现类对象 * invoke就是处理器 的invoke方法 * * 也就是说,最终的实现全部丢给了invacationHandler的invoke()方法处理了。 * 再来看看调用invoke()方法传递的参数, * this:代表当前对象,当前类就是代理类,所以是代理对象本身 * m3: m3 = Class.forName("com.itheima.proxy.Milk").getMethod("sell", new Class[] { Class.forName("java.lang.String") }); * 最下面有如上代码,可以发现m3就是sell方法,执行的方法 * new Object[] { paramString }:就是调用sell方法时传递的参数。 */ this.h.invoke(this, m3, new Object[] { paramString }); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } }
结合上面生成的动态代理类的sell方法的源码,结合我们一开始实现动态代理类的代码来看一下
Milk proxyYiDianDian =(Milk)Proxy.newProxyInstance(classLoader, interfaces,new InvocationHandler() { /* 第一个参数:代理商本身,invoke方法中不能使用,使用就报错。原因后面介绍。 * 第二个参数:当前正在执行的方法。这里可以理解成一点点这个品牌需要执行的方法。 * 第三个参数:当前正在执行的方法需要的参数。 */@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //输出执行的方法的名称 System.out.println("一点点的代理商代替 一点点品牌执行:"+method.getName()+"方法"); //输出执行的方法下需要的参数 System.out.println("一点点品牌传递过来的的参数:"+Arrays.toString(args)); //执行被代理对象本身方法原有的功能。 method.invoke(yiDianDian, args); return null; } });
所以,执行代理对象的任何方法,就需要执行invocationHandler的invoke方法。所以在invoke方法中调用proxy对象的方法,就会调用代理对象本身的方法,而又回到了invoke()方法中。所以陷入死循环。
- 动态代理由浅入深
- 由浅入深写java分布式(1)动态代理
- 由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理
- 由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理
- 由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理
- 由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理
- mybatis -- 由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理
- 由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理
- 【转】由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理
- 设计模式_代理模式_由浅入深
- 设计模式之代理模式(由浅入深)
- 代理-->静态代理&动态代理
- 动态代理
- 动态代理
- 动态代理
- 动态代理
- 动态代理
- 动态代理
- java语言程序设计 第七章(7.21、7.35)
- HDU4778
- Linux 静态库、动态库的创建和使用,环境变量初识。
- 洛谷 1542 快递包裹
- 一个项目中hibernate框架下配置多个数据库
- 动态代理由浅入深
- recovery display
- 【51Nod1122】机器人走方格 V4
- 开启mysql服务
- 第七单元笔记整理
- 【对象数组+静态数据成员+静态成员函数+...】面向对象程序设计(B)——第二次作业
- 贝叶斯决策 实例
- 学习堆的配置参数(最大堆和初始堆的设置)
- Blockchain学习1_区块链-以太坊开发环境搭建介绍