JAVA | 第3期 - 你知道动态代理的几种实现吗?

JAVA | 第3期 - 你知道动态代理的几种实现吗?

编程文章jaq1232025-02-08 10:03:5625A+A-

在程序中,我们要实例化代码有两种方式:

一是直接 new 出来,这种方式称为静态实例化,也是我们代码中用的最多的一种;

另一种则是通过动态代理来创建对象,这种方式在运行期间动态的创建它的实例对象,并且我们可以对目标方法做一些特殊处理。

1、静态代理

在了解动态代理之前,我们先来看一下什么是静态代理。

首先,创建一个接口:

 public interface Session {
   void send(String message);
 }

然后去实现这个接口:

 public class DefaultSession implements Session {
   @Override
   public void send(String message) {
     System.out.printf("Send message: %s\n", message);
   }
 }

手动创建一个代理类:

 public class SessionProxy implements Session {
   private final Session session;
   public SessionProxy(Session session) {
     this.session = session;
   }
   @Override
   public void send(String message) {
     System.out.println("前置通知");
     try {
       session.send(message);
     } catch (Exception e) {
       System.out.println("异常处理");
     }
     System.out.println("后置通知");
   }
 }

最后,创建实例并调用:

 // 创建实例并调用方法
 Session session = new SessionProxy(new DefaultSession());
 session.send("Hello World");
 
 // "前置通知"
 // "Hello World"
 // "异常处理"(如果抛出异常的话)
 // "后置通知。"

所谓的静态代理,说白了就是由我们手动创建的实现类,在编译之前就已经存在了,可以直接被编译成对应的.class字节码。

2、动态代理

在 JDK 标准库中提了一个动态代理机制(Dynamic Proxy),可以让我们在运行期间动态创建对应接口的实例,为什么我这里要强调接口呢?因为 JDK 库的动态代理只适用于接口的代理,如果要实现普通类的代理,像是Spring中的AOP一样,则需要使用cglib这个库,后面我们会专门说一下这个库的简单使用方式,这里我们先看一下动态代理的基本原理。

在动态代理中有几个比较重要的类:

  • InvocationHandler - 代理的最终实现类(凡是对象的实例化,无论静态还是动态,都会存在一个目标实例对象),也是我们需要着重关注的一个类;
  • Proxy - JDK 提供的标准代理对象,通过它可以为我们创建代理后的对象;
  • ProxyGenerator - 我们不需要直接操作它,但是我们需要知道动态代理就是是通过它来创建运行时的class字节码的;

使用姿势:

 Object instance = Proxy.newProxyInstance(
   ,       // 类加载器
   []>,        // 需要被代理的接口
     // 调用实现类
 );

还是先整一个默认实现:

 public class DefaultSessionImpl implements Session {
   @Override
   public void send(String message) {
     System.out.printf("Send message: %s\n", message);
   }
 }

然后创建一个类去实现InvocationHandler,我们的拦截逻辑主要就在这里面去做实现:

 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 
 public class ProxyInvocationHandler implements InvocationHandler {
   private final Session session;
   public SessionInvocationHandler(Session session) {
     this.session = session;
   }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     Object result = null;
     Exception exception = null;
         
     System.out.println("前置通知");
     try {
       if (Object.class.equals(method.getDeclaringClass())) {
         result = method.invoke(this, args); // Object 内部的方法
       } else {
         result = method.invoke(session, args); // 交给默认实现类处理
       }
     } catch (Exception e) {
        exception = e;
        System.out.println("统一异常处理");
     }
      
     System.out.println("后置通知");
     if (exception != null) throw exception;
     return result;
   }
 } 

创建代理对象并调用:

 // 获取动态代理的实例对象
 Session session = (Session) Proxy.newProxyInstance(
   Session.class.getClassLoader(),  // ClassLoader
   new Class[ Session.class ],   // 代理的接口
   new ProxyInvocationHandler(new DefaultSessionImpl()) // 代理执行类
 );
 
 // 调用实例方法
 session.send("Hello"); // 走默认实现类中的方法
 session.toString(); // 走代理的对象中的方法
 session.hashCode(); // 走代理的对象中的方法

上面就是 JDK 动态代理的基本实现,使用起来也很简单,只需要着重注意InvocationHandler的一个逻辑就好了。如果我们要做像SpringAop那样的前置、后置、环绕处理,则只需要在InvocationHandlerinvoke方法中做相应的逻辑处理即可。代码其实不多,但是要理解其中地逻辑和使用方法,并且灵活运用,你也可以合理的运用到项目中去。

3、CGLIB 代理

对于简单的接口代理,我们可以使用 JDK 自带的动态代理,但是如果我们要对普通类做一些代理实现,那我们应该怎么做呢?这时候就需要用到cglib这个库了。

CGLIB 是一个强大的、高性能的代码生成库。采用非常底层的字节码技术,对指定目标类生成一个子类,并对子类进行增强,其被广泛应用于AOP框架(像是Spring)中,用以提供方法拦截操作。CGLIB 是一个托管在 github 的开源项目,其地址为:
https://github.com/cglib/cglib。

在 CGLIB 中有两个比较重要的类:

  • Enhancer - CGLIB 用于创建代理类的主要实现;
  • MethodInterceptor - 方法拦截回调接口,我们需要在代理实现中去实现此接口(拦截逻辑就主要在这里面实现);

首先,我们来创建一个普通类:

注意:这个类不能是final的,因为 cglib 是通过创建子类的方式去生成对应字节码的,使用了 final 的类无法被继承!

public class Handler {
  public void handle() {
     System.out.println("handle()");
  }
}

然后创建一个代理实现类:

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibDynamicProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Object result = null;
        Exception exception = null;
      
        System.out.println("前置通知");
        try {
            result = proxy.invokeSuper(target, args);
        } catch (Exception e) {
            exception = e;
            System.out.println("统一异常处理");
        }
        System.out.println("后置通知");
      
        if (exception != null) throw exception;
        return result;
    }
}

最后来通过Enhancer来创建它的代理类:

// 调试 CGLIB 保存 class 文件到指定目录
// System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D://cglib");
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

// 构建 CGLIB 的对象实例
Enhancer enhancer = new Enhancer();
enhancer.setUseCache(true);
enhancer.setSuperclass(Handler.class); // 被代理的类的 class(也可以是接口)
enhancer.setCallback(new CglibDynamicProxy()); // 代理实现类

// 创建代理类
Handler handler = (Handler) enhancer.create();
handler.handle();

最后别忘了添加 Maven 坐标:


  cglib
  cglib
  3.3.0

4、那我们应该如何选择?

相对来说 JDK 提供的动态代理使用上比较方便,不需要导入第三方包,且可以随着 JDK 版本的更新而及时调整,但是仅针对接口产生代理类,无法对普通类生效,并且InvocationHandler中的invoke方法使用了反射进行目标方法的调用,在一定程度上会有一些效率上的问题;

而 CGLIB 属于第三包,在使用的时候需要进行导入,它不仅可以对接口进行代理,也可以对普通类进行代理,生成对应的代理类,在调用上可以直接调用代理类中的方法,而无需进行反射调用,效率上相对较高,但生成代理类的过程相对较慢,所以使用CGLIB的时候最好将动态代理放在程序启动过程中,这样不会影响执行的效率。

点击这里复制本文地址 以上内容由jaq123整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

苍茫编程网 © All Rights Reserved.  蜀ICP备2024111239号-21