博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式之动态代理
阅读量:6501 次
发布时间:2019-06-24

本文共 6054 字,大约阅读时间需要 20 分钟。

既然有动态代理,那么肯定有静态代理,静态代理请参考

作用: 与静态代理一样,但动态代理解决了 静态代理中 1个静态代理,只代理1种类型的目标对象 的问题

在 JDK 动态代理中涉及如下角色:

  • 目标类的接口 Interface
  • 目标类 target
  • 处理模版 Handler
  • 在内存中生成的动态代理类
  • java.lang.reflect.Proxy

废话少说先上代码

实例

目标类的接口

要有jdk动态代理,你的类必须实现接口

/** * UserService 是 目标类的接口 * login() 就是要被代理的方法 */public interface UserService {    boolean login(String userName, String password);}复制代码

目标类

注意,要代理的方法,必须是从接口中实现的

/** * UserServiceImpl 是目标类,或者叫被代理的类 * login() 就是要被代理的方法 */public class UserServiceImpl implements UserService {    @Override    public boolean login(String userName, String password) {        System.out.println("校验账号");        return false;    }}复制代码

处理模版类

这是一个InvocationHandler的实现类

作用: 定义一种对目标方法做某种操作的模版(这里运用了面向抽象编程的思想) 如下,定义了一个在目标方法前后输出log的模版。

说明: LogHandler 有一个 属性 —— 目标对象,可以看到我特意把它定义成 Object 类型,而不是 UserService 类型,也就是说 LogHandler 的目标对象可以是任意类型,而不局限在 UserService。这样一来 LogHandler 就可以在 任意类型的任意方法 前后输出log,所以称它为处理模版

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * 这是一个自定义的处理模版,它将在 目标方法 的前后输出log */public class LogHandler implements InvocationHandler {    // 目标对象    //private UserService target;    private Object target;    // 初始化时,将目标对象传进来    public LogHandler(Object target) {        this.target = target;    }    /**     * 定义一个在目标方法执行前后输出log的处理模版     *     * @param proxy  动态代理对象     * @param method 目标方法     * @param args   目标的参数     */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        before();        //调用目标对象的目标方法,target 是初始化时传人的目标对象        Object result = method.invoke(target, args);        after();        return result;    }    private void before() {        System.out.println("调用目标方法之前输出的log");    }    private void after() {        System.out.println("调用目标方法之后输出的log");    }}复制代码

这里留一个疑问,谁去调用invoke()方法,方法中的三个参数是什么意思(虽然我已经写上去了)?

测试类

上面三个类构成了jdk动态代理的最小组成单位,接下来,编写测试类使用jdk动态代理

import java.lang.reflect.Proxy;public static void main(String[] args) {    //创建一个目标对象    UserService userServiceImpl = new UserServiceImpl();    // 创建 目标对象的 处理模版    LogHandler userServiceLogHandler = new LogHandler(userServiceImpl);    /**     * Proxy.newProxyInstance 会根据 目标对象的 类加载器、接口的Class、目标对象的处理模版,动态生成一个代理类,并返回代理对象     * userServiceProxy 就是 动态创建出的代理对象,它实现了 UserService 接口 的login方法     */    UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userServiceImpl.getClass().getClassLoader(),            new Class[]{UserService.class}, userServiceLogHandler);    userServiceProxy.login("wqlm", "123");    System.out.println("\n代理类的名称为" + userServiceProxy.getClass().getName());}复制代码

输出结果

/Library/Java/jdk1.8.0_201/Contents/Home/bin/java...调用目标方法之前输出的log校验账号调用目标方法之后输出的log代理类的名称为com.sun.proxy.$Proxy0Process finished with exit code 0复制代码

java.lang.reflect.Proxy

测试方法中就是使用该类的方法来生成代理对象的!

这是JDK动态代理中最为重要的一个类,通过该类的 newProxyInstance 方法,我们可以生成一个代理类

public static Object newProxyInstance(ClassLoader loader, Class
[] interfaces, InvocationHandler h) throws IllegalArgumentException复制代码

如上,newProxyInstance 需要三个参数

  • ClassLoader ,目标类target 的类加载器
  • Class<?>[] ,目标类的接口 Interface 的Class
  • InvocationHandler , 处理模版类 Handler

生成代理类的过程如下

  1. Proxy.newProxyInstance 方法, 通过传递给它的参数 Class<?>[] interfaces, InvocationHandler h 生成代理类的字节码
  2. Proxy 通过传递给它的参数(ClassLoader)来加载生成的代理类的字节码
  3. 返回加载完成的代理类的对象

动态生成的代理类

由于代理类是在运行时动态生成的,没有.java文件,也没有.class文件。但是根据上面的运行结果我们知道,生成的动态代理类叫 com.sun.proxy.$Proxy0,可以通过

byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class
[]{userServiceProxy.getClass()});String pathDir = "/Users/wqlm/Desktop";String path = "/$Proxy0.class";File f = new File(pathDir);path = f.getAbsolutePath() + path;f = new File(path);if (f.exists()) { f.delete();}try { f.createNewFile();} catch (IOException e) { e.printStackTrace();}try (FileOutputStream fos = new FileOutputStream(path)) { fos.write(bytes, 0, bytes.length);} catch (Exception e) { e.printStackTrace();} 复制代码

获取字节码流,在经过反编译,就可以得到大概的.java文件。以下是处理过的 (去掉了不需要关注掉内容)代理类的.java文件

import com.example.demo.service.UserService;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;/** * 代理类 继承了 Proxy,并实现了 目标接口 UserService */public final class $Proxy0 extends Proxy implements UserService {    private static Method m3;    static {        try {            m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("login",                    new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});        } catch (NoSuchMethodException var2) {            throw new NoSuchMethodError(var2.getMessage());        } catch (ClassNotFoundException var3) {            throw new NoClassDefFoundError(var3.getMessage());        }    }    // 初始化时,将处理模版 传递给父类    public $Proxy0(InvocationHandler var1) {        super(var1);    }    /**     * 这就是代理方法,可以看到,它是同过调用父类的 处理模版 的invoke 方法,然后把自己,以及目标方法和参数传递进去     */    public final boolean login(String var1, String var2) {        try {            return ((Boolean) super.h.invoke(this, m3, new Object[]{var1, var2})).booleanValue();        } catch (RuntimeException | Error var4) {            throw var4;        } catch (Throwable var5) {            throw new UndeclaredThrowableException(var5);        }    }}复制代码
  • 动态代理类实现了目标类的接口,并实现了接口中的方法,该方法的实现逻辑是, 调用父类 —— Proxy 的 h 的 invoke()方法
  • 其中h 是 在创建动态代理实例时 newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) 传入的第3个参数InvocationHandler对象
  • 在 InvocationHandler.invoke()中通过反射,调用目标对象

还记得在 处理模版那节留的疑问吗—— “留一个疑问,谁去调用invoke()方法,方法中的三个参数是什么意思” 现在我们知道了,是代理类去调用invoke(),它会传3个参数,自己(代理对象自己)、目标方法、参数列表

总结

JDK的动态代理

特点: 不需要显式实现与目标类相同的接口,而是将这种实现推迟到程序调用时由 JVM来实现,

  • 即:在使用时再创建动态代理类 和 实例
  • 静态代理则是在代理类实现时就指定与目标相同的接口

优点:一个代理类就可以代理多个目标类,避免重复、多余代码

缺点

  • 相比与静态代理,效率低。 静态代理是直接调用目标对象方法,而动态代理则需要先生成类和对象,在通过Java反射机制间接调用目标对象的方法
  • 应用场景局限, 由于每个代理类都继承了 java.lang.reflect.Proxy 类,而Java又只能支持单继承,导致不能针对类 创建代理类,只能针对接口 创建 代理类。即,动态代理只能代理实现了接口的类。

应用场景

  • 需要代理的对象数量较多的情况下。数量较少时,推荐直接使用静态代理
  • 面向切面编程时

与静态代理的区别

设计模式 代理类创建时机 原理 效率 能代理的目标类的数量
动态代理 运行时动态创建 反射 能代理多个目标类
静态代理 运行前,先显示创建好 / 只能代理一个目标类

转载于:https://juejin.im/post/5cca561b51882540884723e6

你可能感兴趣的文章
简述 clearfix 的原理
查看>>
【Project Euler】530 GCD of Divisors 莫比乌斯反演
查看>>
luogu P1280 尼克的任务 序列DP
查看>>
获取文件最后修改时间的VC代码
查看>>
ThinkPHP子类继承Controller类的注意事项
查看>>
iphone UIView的一些基本方法理解
查看>>
sys.check_constraints
查看>>
vue问题
查看>>
Linux常用命令大全
查看>>
ThinkPHP 框架学习
查看>>
yii1框架,事务使用方法
查看>>
css3箭头效果
查看>>
Python学习笔记【第一篇】:认识python和基础知识
查看>>
MathType在手,公式不求人!
查看>>
测试用例设计
查看>>
三层架构
查看>>
Python变量类型(l整型,长整形,浮点型,复数,列表,元组,字典)学习
查看>>
解决方案(.sln)文件
查看>>
理解cookie和session机制
查看>>
【Treap】bzoj1588-HNOI2002营业额统计
查看>>