教你清楚了解JAVA动态代理
- 代理在生活中很常见,比如说婚介网站,其实就是找对象的代理;还有社保代理、人事代理;还有找黄牛抢票,其实也是一种代理;而这些代理,在JAVA中也是有对应实现的。
1、为什么要动态代理
动态代理的作用其实就是在不修改原代码的前提下,对已有的方法进行增强。
关键点:
- 不修改原来已有的代码(满足设计模式的要求)
- 对已有方法进行增强
2、举个栗子
我们用一个很简单的例子来说明: Hello 类,有一个 introduction 方法。
现在我们的需求就是不修改 Hello 类的 introduction 方法,在 introduction 之前先 sayHello ,在 introduction 之后再 sayGoodBye
3、实现方式
JAVA中,实现动态代理有两种方式,一种是JDK提供的,一种是第三方库 CgLib 提供的。特点如下:
CgLib
3.1、JDK动态代理
JDK动态代理需要实现接口,然后通过对接口方法的增强来实现动态代理
所以要使用JDK动态代理的话,我们首先要创建一个接口,并且被代理的方法要在这个接口里面
3.1.1、创建一个接口
我们创建一个接口如下:
Personal.java
public interface Personal { /** * 被代理的方法 */ void introduction(); }
3.1.2、实现接口
创建接口实现类,并且完成 introduction 方法
PersonalImpl.java
public class PersonalImpl implements Personal { @Override public void introduction() { System.out.println("我是程序员!"); } }
3.1.3、创建代理类
JDK代理的关键就是这个代理类了,需要实现 InvocationHandler
在代理类中,所有方法的调用都好分发到 invoke 方法中。我们在 invoke 方法完成对方法的增强即可
JDKProxyFactory.java
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JDKProxyFactory<T> implements InvocationHandler { /** * 目标对象 */ private T target; /** * 构造函数传入目标对象 * * @param target 目标对象 */ public JDKProxyFactory(T target) { this.target = target; } /** * 获取代理对象 * * @return 获取代理 */ public T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 对方法增强 System.out.println("大家好!"); // 调用原方法 Object result = method.invoke(target, args); // 方法增强 System.out.println("再见!"); return result; } }
就这样,JDK动态代理的代码就完成了,接下来写一份测试代码
3.1.4、编写测试代码
为了方便测试,我们编写一个 test 方法
同时为了查看class文件,还添加了一个 generatorClass 方法,这个方法可以将动态代理生成的 .class 输出到文件
ProxyTest.java
import org.junit.Test; import sun.misc.ProxyGenerator; import java.io.FileOutputStream; import java.io.IOException; public class ProxyTest { @Test public void testJdkProxy() { // 生成目标对象 Personal personal = new PersonalImpl(); // 获取代理对象 JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal); Personal proxy = proxyFactory.getProxy(); // 将proxy的class字节码输出到文件 generatorClass(proxy); // 调用代理对象 proxy.introduction(); } /** * 将对象的class字节码输出到文件 * * @param proxy 代理类 */ private void generatorClass(Object proxy) { FileOutputStream out = null; try { byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()}); out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class"); out.write(generateProxyClass); } catch (Exception e) { e.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (IOException e) { // TODO Auto-generated catch block } } } } }
3.1.5、查看运行结果
可以看到,运行 test 方法之后,控制台打印出如下:
大家好! 我是程序员! 再见!
我们在 introduction 方法前和后都成功增加了功能,让这个程序员的自我介绍瞬间变得更加有礼貌了。
3.1.6、探探动态代理的秘密
动态代理的代码并不多,那么JDK底层是怎么帮我们实现的呢?
在测试的时候我们将动态生成的代理类的 class 字节码输出到了文件,我们可以反编译看看。
结果有点长,就不全部贴出来了,不过我们可以看到,里面有一个 introduction 方法如下:
/** * the invocation handler for this proxy instance. * @serial */ protected InvocationHandler h; protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } public final void introduction() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
原来,生成的代理对象里面,引用了我们的 InvocationHandler ,然后在将 introduction 方法里面调用了 InvocationHandler 的 introduction ,而 InvocationHandler 是由我们编写的代理类,在这里我们增加了 sayHello 和 sayGoodBye 操作,然后还调用了原对象的 introduction 方法,就这样完成了动态代理。
3.2、CgLib动态代理
CgLib 动态
3.2.1、创建被代理对象
由于 CgLib 不需要实现接口,所以我们不需要创建接口文件了(当然,你要有接口也没有问题)
直接创建目标类,实现 introduction 方法
PersonalImpl.java
public class PersonalImpl { public void introduction() { System.out.println("我是程序员!"); } }
3.2.2、创建代理类
同样,我们也需要创建代理类,并且在这里实现增强的逻辑,这次我们不是实现 InvocationHandler 接口了,而是实现 CgLib 提供的接口 MethodInterceptor ,都是类似的, MethodInterceptor 中,全部方法调用都会交给 intercept 处理,我们在 intercept 添加处理逻辑即可。
CgLibProxyFactory.java
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CgLibProxyFactory<T> implements MethodInterceptor { /** * 获取代理对象 * * @param tClass 被代理的目标对象 * @return 代理对象 */ public T getProxyByCgLib(Class<T> tClass) { // 创建增强器 Enhancer enhancer = new Enhancer(); // 设置需要增强的类的类对象 enhancer.setSuperclass(tClass); // 设置回调函数 enhancer.setCallback(this); // 获取增强之后的代理对象 return (T) enhancer.create(); } /** * 代理类方法调用回调 * * @param obj 这是代理对象,也就是[目标对象]的子类 * @param method [目标对象]的方法 * @param args 参数 * @param proxy 代理对象的方法 * @return 返回结果,返回给调用者 * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("大家好!"); Object result = proxy.invokeSuper(obj, args); System.out.println("再见!"); return result; } }
3.2.3、编写测试代码
在刚才的测试方法中,我们添加一个 cglib 的测试方法:
@Test public void testCgLibProxy() { // 生成被代理的目标对象 PersonalImpl personal = new PersonalImpl(); // 获取代理类 CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>(); PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass()); // 将proxy的class字节码输出到文件 generatorClass(proxy); // 调用代理对象 proxy.introduction(); }
3.2.4、查看运行结果
运行测试用例,可以看到跟JDK的实现一样的效果
大家好! 我是程序员! 再见!
3.2.5、探探动态代理的秘密
跟JDK的测试一样,我们也来看看生成的 class 文件
public final void introduction() throws { try { super.h.invoke(this, m7, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
可以发现,与JDK的动态代理并没有区别。
4、如何选择
既然有两种实现方式,那么到底应该怎么选择呢?
就两个原则:
- 目标类有接口实现的, JDK 和 CgLib 都可以选择,你开心就好
- 目标类没有实现任何接口,那只能用 CgLib 了
分享一些知识点给大家希望能帮助到大家,或者从中启发。
加入Q君羊:821169538 都是java爱好泽,大家可以一起讨论交流学习
原文
http://fengqiangboy.com/15377761043880.html
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
因 php 默认的 url encode 编码标准引发的一个问题
先看常用的校验请求合法性的一个方式 function createToken($params) { $secretKey = 'secretKey'; ksort($params); $query = http_build_query($params); $token = md5($query . $secretKey); return $token; } function createQuery($params) { $params['token'] = createToken($params); $query = http_build_query($params); return $query; } function checkQuery($params) { $token = $params['token']; unset($params['token']); return $token == createToken($params); } $params = [ 'k1' => 'v1', 'k2' => 'v2', 'time' => time() ]; $...
- 下一篇
Go map实现原理
1. map数据结构 Golang的map使用哈希表作为底层实现,一个哈希表里可以有多个哈希表节点,也即bucket,而每个bucket就保存了map中的一个或一组键值对。 map数据结构由runtime/map.go/hmap定义: type hmap struct { count int // 当前保存的元素个数 ... B uint8 // 指示bucket数组的大小 ... buckets unsafe.Pointer // bucket数组指针,数组的大小为2^B ... } 下图展示一个拥有4个bucket的map: 本例中, hmap.B=2, 而hmap.buckets长度是2^B为4. 元素经过哈希运算后会落到某个bucket中进行存储。查找过程类似。 bucket很多时候被翻译为桶,所谓的哈希桶实际上就是bucket。 2. bucket数据结构 bucket数据结构由runtime/map.go/bmap定义: type bmap struct { tophash [8]uint8 //存储哈希值的高8位 data byte[1] //key value数据:ke...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- 设置Eclipse缩进为4个空格,增强代码规范
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- 2048小游戏-低调大师作品