Java 动态代理初探

2019-12-30 16:57:04 浏览数 (1)

前言

对于使用过 Spring 的朋友, 应该都使用过 AOP, 那么今天我们来对 AOP 的原理: 动态代理 来一探究竟.

静态代理

在动态代理之前, 我们先来看看静态代理, 看下其实现方式及其优缺点.

静态代理的原理是委托类和代理类都实现同一个接口, 代理类中会调用委托类的方法, 同时代理类中可以加一些自己的逻辑.

公共接口

代码语言:javascript复制
public interface IService {
    public void sayHello();
}

委托类

代码语言:javascript复制
public class RealService implements IService {
    @Override
    public void sayHello() {
        System.out.println("hello");
    }
}

代理类

代码语言:javascript复制
public class ProxyService implements IService {

    private IService iService;

    public ProxyService(IService iService) {
        this.iService = iService;
    }

    @Override
    public void sayHello() {
        System.out.println("before...");
        iService.sayHello();
        System.out.println("after...");
    }
}

测试

代码语言:javascript复制
public class Main {

    public static void main(String[] args) {
        IService realService = new RealService();
        IService proxyService = new ProxyService(realService);
        proxyService.sayHello();
    }
}

输出:

代码语言:javascript复制
before...
hello
after...

可以看到委托类和代理类都实现了同一个接口, 然后代理类在初始化时, 传入委托类对象, 然后在代理类自己的 sayHello() 方法中, 即调用了委托类的 sayHello() 方法, 还加了自己的逻辑, 输出了 beforeafter. 但这种方式有着明显的缺点:

  • 必须要继承一个接口
  • 必须要手工创建对应的代理类
  • 硬编码, 当接口需要改动或代理类较多时不宜维护.

动态代理

JDK 动态代理

公共接口
代码语言:javascript复制
public interface IService {
    public void sayHello();
}
委托类
代码语言:javascript复制
public class RealService implements IService {
    @Override
    public void sayHello() {
        System.out.println("hello");
    }
}
代理类
代码语言:javascript复制
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class SimpleInvocationHandler implements InvocationHandler {

    private Object realObject;

    public SimpleInvocationHandler(Object realObject) {
        this.realObject = realObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before "   method.getName());
        Object result = method.invoke(realObject, args);
        System.out.println("leaving "   method.getName());
        return result;
    }
}
测试
代码语言:javascript复制
import java.lang.reflect.Proxy;

public class Main {
    
    public static void main(String[] args) {

        // 保存生成的代理类的字节码文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        IService realService = new RealService();
        IService proxyService = (IService) Proxy.newProxyInstance(
                IService.class.getClassLoader(),
                new Class<?>[]{IService.class},
                new SimpleInvocationHandler(realService));
        proxyService.sayHello();
    }
}

代码看起来好像更复杂了一些, 我们可以看到 IServiceRealService 的定义不变, 但创建代理类的方式变化了, 它使用 java.lang.reflect.Proxy 的静态方法 newProxyInstance 来创建代理类.

代码语言:javascript复制
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

它有三个参数:

  • loader 表示类加载器.
  • interfaces 表示代理类要实现的接口列表, 元素的类型只能是接口.
  • h 就是上面我们定义的 SimpleInvocationHandler 它实现了 InvocationHandler 接口, 并复写了 invoke 方法, 对代理接口的所有方法调用都会转到此方法上.

newProxyInstance 的返回值是 Object, 可以强制转化为 interfaces 数组中的任意接口类型, 但不能转化为某个普通类型, 如 RealService. 即使它代理的实际对象是 RealService.

接着我们在来看看 SimpleInvocationHandler, 它实现了 InvocationHandler 接口, 它通过构造方法传入被代理对象 realObject. 复写的 invoke 方法有三个参数:

  • proxy 表示代理对象本身, 需要注意, 它不是被代理的对象.
  • method 表示正在被调用的方法
  • args 表示方法的参数

需要注意这句代码:

代码语言:javascript复制
Object result = method.invoke(realObject, args);
``

千万不能把 proxy 当成 method.invoke 的第一个参数. 这样会造成死循环, 因为这样表示代理类代理了它自身.


#### 原理解析

刚才的测试类中, 我们有一段代码, 它用来保存生成后的代理类的 class 文件: 

```java
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")

我们来反编译看下, 它为我们动态生成的代理类:

代码语言:javascript复制
package com.sun.proxy;

import im.zhaojun.jdk_proxy.IService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements IService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1)  {
        super(var1);
    }

    public final boolean equals(Object var1)  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sayHello()  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString()  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode()  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("im.zhaojun.jdk_proxy.IService").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到, 其实也就是生成了一个类, 实现了我们传入的接口, 它所有方法都是调用的 SimpleInvocationHandler 的 invoke 方法.

相比于静态代理, 这里的动态代理看起来麻烦了不少, 但它却更加通用. 我们不用为每个被代理的类都创建一个静态代理类, 而是当代理类要做的功能不变时, 只需要有这一个代理类即可. 说起来可能有些不好理解, 看代码吧:

代码语言:javascript复制
public class GeneralProxyDemo {
    static interface IServiceA {
        public void sayHello();
    }

    static class ServiceAImpl implements IServiceA {

        @Override
        public void sayHello() {
            System.out.println("hello");
        }
    }

    static interface IServiceB {
        public void fly();
    }

    static class ServiceBImpl implements IServiceB {

        @Override
        public void fly() {
            System.out.println("flying");
        }
    }

    static class SimpleInvocationHandler implements InvocationHandler {
        private Object realObj;

        public SimpleInvocationHandler(Object realObj) {
            this.realObj = realObj;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("entering "   realObj.getClass().getSimpleName()   "::"   method.getName());
            Object result = method.invoke(realObj, args);
            System.out.println("leaving "   realObj.getClass().getSimpleName()   "::"   method.getName());
            return result;
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> T getProxy(Class<T> intf, T realObj) {
        return (T) Proxy.newProxyInstance(intf.getClassLoader(), new Class<?>[] { intf },
                new SimpleInvocationHandler(realObj));
    }

    public static void main(String[] args) throws Exception {
        IServiceA a = new ServiceAImpl();
        IServiceA aProxy = getProxy(IServiceA.class, a);
        aProxy.sayHello();

        IServiceB b = new ServiceBImpl();
        IServiceB bProxy = getProxy(IServiceB.class, b);
        bProxy.fly();
    }
}

在这里有两个接口 ServiceAServiceB, 他们对应的实现类为 ServiceAImplServiceBImpl. 虽然他们的接口和实现类完全不同, 但通过动态代理. 他们都可以使用 SimpleInvocationHandlerinvoke 中的代理逻辑.

CGLIB 动态代理

上面讲到的 JDK 动态代理, 有一定的局限性, 那就是只能为接口创建代理, 返回的对象也只能是接口类型的, 如果一个类没有接口, 或者想代理非接口中定义的方法, JDK 动态代理就无法实现了. 这里就要用到 CGLIB 动态代理了.

代码语言:javascript复制
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 SimpleCGLibDemo {

    /**
     * 被代理类.
     */
    static class RealService {
        public void sayHello() {
            System.out.println("hello");
        }
    }

    /**
     * 方法拦截器.
     */
    static class SimpleInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object object, Method method,
                Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("entering "   method.getName());
            Object result = proxy.invokeSuper(object, args);
            System.out.println("leaving "   method.getName());
            return result;
        }
    }

    private static <T> T getProxy(Class<T> cls) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(cls);                    // 设置被代理类
        enhancer.setCallback(new SimpleInterceptor());  // 设置方法拦截器
        return (T) enhancer.create();
    }

    public static void main(String[] args) {
        RealService proxyService = getProxy(RealService.class);
        proxyService.sayHello();
    }
}

RealService 表示被代理的类, 它没有实现任何接口. getProxy() 方法为一个类生成代理对象, 这个代理对象可以转换为被代理类的类型, 它使用了 cglibEnhancer 类, Enhancer 类的 setSuperclass 设置被代理的类, setCallback 设置被代理类的方法被调用时的处理类, Enhancer 支持多种类型, 这里使用的类实现了 MethodInterceptor 接口, 它与 JDK 动态代理中的 InvocationHandler 有点类似, 方法名称变成了intercept, 多了一个MethodProxy类型的参数.

与前面的 InvocationHandler 不同,SimpleInterceptor 中没有被代理的对象,它通过 MethodProxyinvokeSuper 方法调用被代理类的方法:

代码语言:javascript复制
Object result = proxy.invokeSuper(object, args);

注意,它不能这样调用被代理类的方法:

代码语言:javascript复制
Object result = method.invoke(object, args);

object 是代理对象,不能自己代理自己,会造成死循环。

基本的使用就这些, 先消化下, 自己动手实现, 后续我会更新一些 JDK 更细节上的内容.

0 人点赞