动态代理竟然如此简单! (2)

于是乎 cxuan 上网求助,发现了一个叫做动态代理的概念,通读了一下,发现有点意思,于是乎 cxuan 修改了一下静态代理的代码,新增了一个 UserHandler 的用户代理,并做了一下 test,代码如下

public class UserHandler implements InvocationHandler { private UserDao userDao; public UserHandler(UserDao userDao){ this.userDao = userDao; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { saveUserStart(); Object obj = method.invoke(userDao, args); saveUserDone(); return obj; } public void saveUserStart(){ System.out.println("---- 开始插入 ----"); } public void saveUserDone(){ System.out.println("---- 插入完成 ----"); } }

测试类如下

public static void dynamicProxy(){ UserDao userDao = new UserDaoImpl(); InvocationHandler handler = new UserHandler(userDao); ClassLoader loader = userDao.getClass().getClassLoader(); Class<?>[] interfaces = userDao.getClass().getInterfaces(); UserDao proxy = (UserDao)Proxy.newProxyInstance(loader, interfaces, handler); proxy.saveUser(); }

UserHandler 是用户代理类,构造函数中的 UserDao 是真实对象,通过把 UserDao 隐藏进 UserHandler ,通过 UserHandler 中的 UserDao 执行真正的方法。

类加载器、接口数组你可以把它理解为一个方法树,每棵叶子结点都是一个方法,通过后面的 proxy.saveUser() 来告诉 JVM 执行的是方法树上的哪个方法。

用户代理是通过类加载器、接口数组、代理类来得到的。saveUser 方法就相当于是告诉 proxy 你最终要执行的是哪个方法,这个 proxy.saveUser 方法并不是最终直接执行的 saveUser 方法,最终的 saveUser 方法是由 UserHandler 中的 invoke 方法触发的。

上面这种在编译期无法确定最终的执行方法,而只能通过运行时动态获取方法的代理模式被称为 动态代理。

动态代理的优势是实现无侵入式的代码扩展,也可以对方法进行增强。此外,也可以大大减少代码量,避免代理类泛滥成灾的情况。

所以我们现在总结一下静态代理和动态代理各自的特点。

静态代理

静态代理类:由程序员创建或者由第三方工具生成,再进行编译;在程序运行之前,代理类的 .class 文件已经存在了。

静态代理事先知道要代理的是什么。

静态代理类通常只代理一个类。

动态代理

动态代理通常是在程序运行时,通过反射机制动态生成的。

动态代理类通常代理接口下的所有类。

动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。

动态代理的调用处理程序必须事先继承 InvocationHandler 接口,使用 Proxy 类中的 newProxyInstance 方法动态的创建代理类。

在上面的代码示例中,我们是定义了一个 UserDao 接口,然后有 UserDaoImpl 接口的实现类,我们通过 Proxy.newProxyInstance 方法得到的也是 UserDao 的实现类对象,那么其实这是一种基于接口的动态代理。也叫做 JDK 动态代理。

是不是只有这一种动态代理技术呢?既然都这么问了,那当然不是。

除此之外,还有一些其他代理技术,不过是需要加载额外的 jar 包的,那么我们汇总一下所有的代理技术和它的特征

JDK 的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar 包。

CGLIB 和 Javassist 都是高级的字节码生成库,总体性能比 JDK 自带的动态代理好,而且功能十分强大。

ASM 是低级的字节码生成工具,使用 ASM 已经近乎于在使用字节码编程,对开发人员要求最高。当然,也是性能最好的一种动态代理生成工具。但 ASM 的使用很繁琐,而且性能也没有数量级的提升,与 CGLIB 等高级字节码生成工具相比,ASM 程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 CGLIB 或者 Javassist。

下面我们就来依次介绍一下这些动态代理工具的使用

CGLIB 动态代理

上面我们提到 JDK 动态代理是基于接口的代理,而 CGLIB 动态代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,也就是说 CGLIB 动态代理采用类继承 -> 方法重写的方式进行的,下面我们先来看一下 CGLIB 动态代理的结构。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspjzg.html