Java动态代理

Java动态代理

在介绍动态代理之前,我们先来说说静态代理。

静态代理

假设,现在有这么一个需求场景:项目依赖了一个三方库,现在想要在项目调用三方库时记录调用日志。那么我们如何能够在无法修改三方库代码的前提下,完成这个需求呢?

相信大家能够想到很多种方法来实现,其中最简单粗暴的就是静态代理了。大概的做法是:

  1. 为每一个被调用的三方类(委托类)都编写一个对应的代理类,并实现相同的接口,通过代理类创建出代理对象。
  2. 在创建代理对象时,通过构造器塞入原委托对象,在代理对象的方法内部调用原委托对象的同名方法,并在调用前后打印日志。

也就是说,代理对象=原委托对象+增强代码,有了代理对象后,就不用原委托对象了。

大概的代码是这样的:

/**
 * 代理类与被代理类(委托类)都实现了UserService接口
 */
public interface UserService {
    public void select();
}

/**
 * 实现了UserService接口的委托类
 */
public class UserServiceImpl implements UserService {  
    public void select() {  
        System.out.println("invoke UserServiceImpl::select");
    }
}

/**
 * 实现了UserService接口的代理类
 */
public class UserServiceProxy implements UserService {
  // 被代理的原委托对象
  private UserService target;
  
  public UserServiceProxy(UserService target) {
    this.target = target;
  }
  
  public void select() {
    before();
    // 调用原委托对象的原方法,也是真正调用三方接口的代码
    target.select();
    after();
  }
  
  private void before() {
    System.out.println("invoe UserServiceProxy::before");
  }
  
  private void after() {
    System.out.println("invoe UserServiceProxy::after");
  }
}

/**
 * 在其他的对象中使用代理类
 */
public class Client {
  public void selectUser() {
    UserService proxy = new UserServiceProxy(new UserServiceImpl());
    proxy.select();
  }
}

像这样的通过对应的代理对象来代理原委托对象的方法,我们称之为静态代理。

静态代理的优点就是简单粗暴,分别为原委托对象实现各自的代理对象即可;但缺点也很明显,静态代理需要编写大量的代理代码,实现起来非常的繁琐,此外一旦原委托对象需要增加新的方法或修改已有的方法时,代理对象都需要进行相应的修改,在维护性上较差。

动态代理

静态代理在代码冗余、维护性上都存在问题,回到文章开篇的场景下,如果项目中大量调用了三方库,那静态代理就不是最优解了。所以,我们的解决方向应该是如何少写或者不写代理类但也能够完成代理功能

现在的方向是少写或不写代理类也能完成代理,那么我们可不可以想办法自动生成代理类不需要手动编写呢,让我们从基础的对象创建开始。

我们知道JVM创建对象的过程如下:

BCznZd.png

其中的Class对象,是Class类的实例,而Class类是Java中用来描述所有类的类,所以要创建一个对象,首先是得到对应的Class对象,既然如此,那么是不是可以想办法得到委托对象的Class对象,然后根据Class对象创建代理实例。

在Java中,JDK已经为我们提供了java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler,这两个API相互配合,Proxy是整体的入口,而InvocateionHandler是中介类,连接委托类和代理类,主要用来增强代码的实现。

Proxy有个静态方法:getProxyClass(ClassLoader loader, Class<?>... interfaces),这个方法会根据传入的类加载器Class接口列表返回代理对象的Class类对象,同时这个Class类对象会继承实现所传入的接口Class。

简单点的理解就是这样:

Bp5ay8.png BCMjI0.png

到这里我们已经能够通过委托类的Class创建出代理类的Class,那么接下来就是生成代理实例,同时插入增强代码。

这里的增强代码就需要借助中介类java.lang.reflect.InvocationHandler了,中介类主要是作为调用处理器拦截对委托类方法的调用。

一个简单的中介类大概是这样的:

public class LogHandler implements InvocationHandler { 
    // obj为委托对象; 
    private Object obj; 
 
    public LogHandler(Object obj) {
        this.obj = obj;
    } 
 
    @Override 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        System.out.println("invoke before"); 
        // 调用委托对象的方法
        Object result = method.invoke(obj, args); 
        System.out.println("invoke after"); 
        return result;
    }
} 

其内部的调用逻辑是这样的:

BC31eJ.png

也可以理解成这样的:

BC7XvR.png

这样的话我们能够得到全部的代码是这样的:

public class ProxyTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        // 拿到委托类的代理对象的Class对象
        Class userServiceClass = Proxy.getProxyClass(UserService.class.getClassLoader(), UserService.class);
        // 得到Class对象的构造器$Proxy0(java.lang.reflect.InvocationHandler)
        Constructor constructor = userServiceClass.getConstructor(InvocationHandler.class);

        // 增强代码中介类对象
        InvocationHandler handler = new LogHandler(new UserServiceImpl());
        // 反射创建代理对象
        UserService impl = (UserService) constructor.newInstance(handler);
        impl.select();
    }

}

class LogHandler implements InvocationHandler {
    // obj为委托对象
    private Object obj;

    public LogHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke before");
        Object result = method.invoke(obj, args);
        System.out.println("invoke after");
        return result;
    }
}

最终的输出结果:

invoke before
invoke UserServiceImpl::select
invoke after

到了这里动态代理的基本内容是差不多了,但回头看看我们的代码,会发现在代码存在多处的硬编码,如Proxy.getProxyClass(UserService.class.getClassLoader(), UserService.class);等等,这种写法并不优雅,如果后面想代理其他委托对象就很是麻烦,所以接下来,对代码进行优化下:

public class ProxyTest {
    public static void main(String[] args) throws Exception {
        // 根据委托对象获取代理对象
        UserService impl = (UserService) getProxy(new UserServiceImpl());
        impl.select();
    }

    private static Object getProxy(Object obj) throws Exception {
        // 拿到委托类的代理对象的Class对象
        // 参数1:委托对象的类加载器 参数2:委托对象的接口列表,这样代理对象能够继承实现相同的接口
        Class objClass = Proxy.getProxyClass(obj.getClass().getClassLoader(), obj.getClass().getInterfaces());
        // 得到Class对象的构造器$Proxy0(java.lang.reflect.InvocationHandler)
        Constructor constructor = objClass.getConstructor(InvocationHandler.class);

        // 反射创建代理对象,传入中介类对象
        return constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("invoke before");
                Object result = method.invoke(obj, args);
                System.out.println("invoke after");
                return result;
            }
        });
    }
}

改进后的代码就好多了,无论现有系统有多少类,只需要将委托对象传进去就可以了。

实际上,Proxy还提供了一步到位的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h),方法可以直接返回代理对象,省去了获取代理对象的Class对象的过程:

public class ProxyTest {

    public static void main(String[] args) throws Exception {
        // 根据委托对象获取代理对象
        UserService impl = (UserService) getProxy(new UserServiceImpl());
        impl.select();
    }

    private static Object getProxy(Object obj) throws Exception {
        // 直接一步到位
        return Proxy.newProxyInstance(
                obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                // lambda改进
                (proxy, method, args) -> {
                    System.out.println("invoke before");
                    Object result = method.invoke(obj, args);
                    System.out.println("invoke after");
                    return result;
                });
    }
}

动态代理实际上有很多应用,比如Spring AOP的实现,RPC框架的实现,一些第三方工具库的内部使用等等。这里就不扯了这些了。

原文地址:https://www.cnblogs.com/tingyugetc/p/13854068.html