代理模式
定义
为其它对象提供一种代理以控制这个对这个对象的访问。
不管是静态代理还是动态代理,目的都是要拿到目标对象的引用,并且能够调用到目标类的业务方法。
静态代理
- 人的抽象接口
package com.faith.net.proxy.staticed; /** * 人抽象接口 */ public interface Person { public void drive(); }
- Boss作为被代理对象
package com.faith.net.proxy.staticed; /** * 老板, 雇佣者 */ public class Boss implements Person { @Override public void drive() { System.out.println("drive.."); } }
- Employee作为代理对象
package com.faith.net.proxy.staticed; /** * 雇员,可以被任何人雇佣。 * 这个类的作用就是保持被代理类对象的引用,并保证能* 够调用其方法即可。不需要实现Person类 */ public class Employee { private Person person; public Employee(Person person){ this.person = person; } public void drive(){ System.out.println("被雇佣,开始工作:"); this.person.drive(); System.out.println("工作结束。"); } }
- 测试类
package com.faith.net.proxy.staticed; public class StaticProxyTest { public static void main(String[] args) { Employee employee = new Employee(new Boss()); employee.drive(); } }
静态代理的缺点,当Person添加新的方法,例如work,被代理类Boss需要实现work方法,并且代理类需要提前知道被代理的引用及其需要被代理的方法。
动态代理可以避免这些麻烦。
动态代理
动态代理中,代理类是运行期自动生成的,无需提前了解被代理类的详细情况。
静态代理在代理之前,所有东西都是已知的;动态代理在代理之前,所有东西都是未知的。
动态代理最终都会生成一个新的代理类。
jdk动态代理
jdk动态代理中,代理类必须实现InvocationHandler接口,详细请见代码注释。而代理类通过字节码重组方式实现。
Person及Boss类沿用上例即可。
- 代理类
package com.faith.net.proxy.jdk; import com.faith.net.proxy.staticed.Person; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * jdk代理类, 本质上是作为调用处理器的实现,所以必须要实现调用处理器接口 */ public class JDKEmployee implements InvocationHandler { // 被代理对象的引用 private Person target; public JDKEmployee(Person target) { this.target = target; } // 获取动态代理对象 public Object getInstance() throws Exception{ /** * 三个参数: * * 1、类加载器将加载进入内存中 * * 2、创建出的动态代理对象需要实现哪几个接口 * * 3、调用处理器,这里可直接指定为this,替换掉new JDKEmployee(target)则为return Proxy.newProxyInstance(JDKEmployee.class.getClassLoader(), new Class[] { Person.class }, new JDKEmployee(target)); */ return Proxy.newProxyInstance(JDKEmployee.class.getClassLoader(), new Class[] { Person.class }, new JDKEmployee(target)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * InvocationHandler的invoke()方法的参数有三个: * * Object proxy:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常不用; * * Method method:表示当前被调用方法的反射对象; * * Object[] args:表示当前被调用方法的参数,没有参数的args是一个零长数组。 * * invoke()方法的返回值为Object类型,它表示当前被调用的方法的返回值 */ System.out.println("被雇佣,开始工作:"); Object invoke = method.invoke(this.target, args); System.out.println("工作结束。"); return invoke; } }
- 测试类
package com.faith.net.proxy.jdk; import com.faith.net.proxy.staticed.Person; import sun.misc.ProxyGenerator; import java.io.FileOutputStream; /** * 测试类 */ public class JDKProxyTest { public static void main(String[] args) { try { Person obj = (Person)new JDKEmployee(new Boss()).getInstance(); System.out.println(obj.getClass()); obj.drive(); //打印出$Proxy0类文件,稍后通过反编译工具可以查看源代码 byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class}); FileOutputStream os = new FileOutputStream("D://$Proxy0.class"); os.write(bytes); os.close(); } catch (Exception e) { e.printStackTrace(); } } }
- 字节码重组过程
1、拿到被代理对象的引用,并且获取到它的所有的接口;
2、JDK Proxy类重新生成一个新的类、同时新的类要实现被代理类所实现的所有接口;
3、动态生成新类的Java代码,把新加的业务逻辑方法由一定的逻辑代码去调用;
4、编译新生成的Java代码,生成.class字节码文件
5、将字节码文件加载到JVM中运行.
这个过程就叫字节码重组。
- 分析代理类
上面的
System.out.println(obj.getClass());
会输出如下结果:
class com.sun.proxy.$Proxy0
按照JDK规范,$开头的类都是运行时动态生成的,例如内部类。
将$Proxy0类文件输出并拖入idea中,可以得到反编译结果如下:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // import com.faith.net.proxy.staticed.Person; 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 Person { 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); } } public final void drive() 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"); m3 = Class.forName("com.faith.net.proxy.staticed.Person").getMethod("drive"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
可以看到该类继承了Proxy类并实现了Person接口:
public final class $Proxy0 extends Proxy implements Person
主要看代理类中的代理方法drive:
public final void drive() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
其中h是父类的,进入其父类Proxy可得:
/** * the invocation handler for this proxy instance. * @serial */ protected InvocationHandler h;
在此场景h就是实现了InvocationHandler接口的代理类JDKEmployee。所以$Proxy0类中drive方法调用的即是JDKEmployee的drive方法。
这就是jdk动态代理的原理。
- 内部类替代代理类
JDKEmployee类还可以使用内部类来替代,例如:
/** * 测试类 */ public class JDKProxyTest { public static void main(String[] args) { Person obj = (Person) Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class[]{Person.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //调用方法 if ("drive".equals(method.getName())) { System.out.println("被雇佣,开始工作:"); Object invoke = method.invoke(new Boss(), args); System.out.println("工作结束。"); return invoke; } else { return null; } } }); } }
因为JDKEmployee存在的意义就是作为调用处理器的实现,那么这个实现当然可以使用内部类来替代。
CGLIB动态代理
jdk动态代理是基于接口实现的,而CGLIB动态代理是通过继承实现的。
同样,CGLIB方式需要代理类实现MethodInterceptor接口,其意义也是作为方法的处理器。示例如下:
- Boss类
package com.faith.net.proxy.cglib; /** * Boss */ public class Boss { public void drive(){ System.out.println("drive.."); } }
- 测试类
package com.faith.net.proxy.cglib; /** * 测试 */ public class CglibTest { public static void main(String[] args) { try { Boss obj = (Boss)new CglibEmployee().getInstance(Boss.class); obj.drive(); System.out.println(obj.getClass()); } catch (Exception e) { e.printStackTrace(); } } }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
.NET redis cluster
原文: .NET redis cluster 一、下载Windows版本Redis 下载链接:https://github.com/MSOpenTech/redis/releases(根据系统选择对应版本) 二、修改默认的配置文件 如上图两个配置文件,redis.windows.conf(应用程序配置文件);redis.windows-service.conf(Redis windows 服务使用的配置文件)。 主要配置: 1. bind #IP 2.port #端口 3.loglevel #日志级别 4.logfile #日志保存位置 5.dir #数据保存地址 6.cluster-enabled yes #启用集群 7.cluster-config-file #nodes.conf (redis记录文件,自动生成) 8.cluster-node-timeout #失效时间(毫秒) 注意:以上配置节点行头不要留有空格,否则会报错。 三、准备集群配置文件 将修改好的配置文件复制如下图 四、准备集群环境 安装rubay(由于 Redis 的集群使用 ruby脚本编写,所以系统需...
- 下一篇
p神 代码审计知识星球二周年wp[2]
参考文献:https://m3lon.github.io/2018/05/29/RCTF-r-cursive-wp/http://f1sh.site/2018/11/25/code-breaking-puzzles%e5%81%9a%e9%a2%98%e8%ae%b0%e5%bd%95/ 递归匹配:http://www.laruence.com/2011/09/30/2179.html easy - phpmagic 源码 <?php if(isset($_GET['read-source'])) { exit(show_source(__FILE__)); } define('DATA_DIR', dirname(__FILE__) . '/data/' . md5($_SERVER['REMOTE_ADDR'])); if(!is_dir(DATA_DIR)) { mkdir(DATA_DIR, 0755, true); } chdir(DATA_DIR); $domain = isset($_POST['domain']) ? $_POST['domain'] : ''; ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Red5直播服务器,属于Java语言的直播服务器
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS6,CentOS7官方镜像安装Oracle11G
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装