Java 动态代理机制解析

通过这篇文章,你将了解Java的静态代理以及实现动态代理的两种方式,你还将能看到动态代理类结构信息。

既然有动态代理,那么肯定有其对立面静态代理。两者最明显的区别就是字节码class文件生成的时间。静态代理的class文件是在编译期生成,而动态代理的class文件信息是在运行期生成。

介绍动态代理之前,我们先看我们核心业务类UserService,现在有个需求要为这个类增加统一的日志打印功能。

// UserService.java 
interface UserService {
    public String getLoginNameById(String id);
}

//UserServiceImpl.java
public class UserServiceImpl implements UserService {
    public String getLoginNameById(String id){
        return "admin";
    }
}

1. 静态代理

静态代理是编译器生成class文件,具体实现方式如下:

//StaticLogProxy.java
public class StaticLogProxy implements UserService {

    public UserService userService;

    @Override
    public String getLoginNameById(String id) {

        System.out.println("getLoginNameById invoked param: "+id);

        return userService.getLoginNameById(id);
    }
}

静态代理通过新建一个代理类StaticLogProxy(该类实现UserService接口)从而完成代理操作。这种实现方式需要为每个被代理的类增加对应的代理类(如果被代理类数量较多,工作量较大),代码耦合性较强(每个代理类只能对应某个类及其子类),且不易扩展。

2. jdk动态代理类

动态代理主要分为两种:jdk原生的动态代理以及cglib动态代理。我们先来看一下jdk原生的动态代理。

笔者认为jdk原生动态代理是基于静态代理,解决了静态代理中代码耦合性强,不易扩展等问题。

jdk动态代理包括两个重要的类:InvocationHandler和Proxy。
InvocationHandler:被代理类公共行为的引入的定义,在本文指的是为UserService增加日志打印的功能。
Proxy:根据被代理类及InvocationHandler,生成动态代理的字节码,并加载到JVM中。

//LogInvocationHandler.java 
public class LogInvocationHandler implements InvocationHandler {
    public Object target;

    public LogInvocationHandler(Object target){
        this.target = target;
    }
    /**
     * invoke方法
     * @param proxy  代理类
     * @param method 被代理的方法
     * @param args  被代理方法的参数
     * @return  返回值
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("method:"+method.getName()+ " invoked"+","+"params: "+ Arrays.toString(args));
        return method.invoke(target, args);
    }
}

LogInvocationHandler是InvocationHandler的实现类,被代理的类必须为LogInvocationHandler的属性。LogInvocationHandler的invoke方法定义了日志打印的功能。
有了Invocation的实现类,接下来我们就需要使用Proxy的静态方法为UserService创建代理,Proxy创建代理方法如下:

 /**
   * 
   * @param loader  加载代理类字节的类加载器
   * @param interfaces  代理类需要实现的接口
   * @param h  方法调用的实际处理者
   * @return  返回值
   * @throws Throwable
*/
public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)

具体实现代码如下:

// Main.java
public class Main {
    public static void main(String[] args){
        //该设置是为了将为UserService生成代理类的字节码保存在磁盘上,便于查看
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 实例化一个被代理的UserSerivce
        UserService target = new UserServiceImpl();
        // 实例化InvocationHandler实现类,并将target作为构造函数参数传入。
        LogInvocationHandler logInvocationHandler = new LogInvocationHandler(target);
        // 创建动态代理类
        UserService userService = (UserService) Proxy.newProxyInstance(
                Main.class.getClassLoader(), 
                new Class[] {UserService.class}, 
                logInvocationHandler);
        //调用方法,验证日志打印功能
        userService.getLoginNameById("1");
    }
}

通过对比jdk动态代理和静态代理的实现方式,我们发现jdk动态代理的实际业务处理类LogInvocationHandler中没有与UserService相关的代码,从代码层面上进行解耦。通过LogInvocationHandler实现动态代理,我们可以为任意的接口实现类增加日志打印功能。
注意我们特别说明了是可以为任意的接口实现类增加日志打印功能,这是我们使用动态代理的一个限制。

在Main.java中,我们通过增加下面这行代码,将代理类的字节保存的在磁盘上

//生成字节码的路径在本工程下
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

紧接着,我们来看一下生成字节码反编译后的代码:

//我们可以看出jdk动态代理通过实现接口的方式生成了代理类
//所有被代理的方法都作为代理$Proxy0的属性,由静态代码块进行初始化
//在调用具体的方法时,又通过invocationHandler定义的增强逻辑来进行统一处理
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

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

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

    public final String getLoginNameById(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        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() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m3 = Class.forName("org.weking.proxy.jdk.UserService").getMethod("getLoginNameById", new Class[]{Class.forName("java.lang.String")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

3.CGLIB动态代理

之前我们提到jdk动态代理只能为接口实现类创建代理,那么我们如何为一般的类创建动态代理呢?这时就需要用到CGLIB动态代理机制。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,也正因为如此,CGLIB的性能优于jdk动态代理。CGLIB通过继承方式实现代理。
CGLIB动态代理主要包括两个重要的类:MethodInterceptor和Enhancer。
MethodInterceptor:该类与InvocationHandler类似,是包含增强方法的类。
EnHancer:该类与Proxy类似,完成生成代理类字节码的功能。

注意:CGLIB不是jdk自带的功能,需要引用相关的jar包才能使用

首先我们来看一下没有实现接口的UserSerivce的代码

//UserService.java
public class UserService {

    public final String finalMethod(String name){
        System.out.println("finalMethod running!!!");
        return "final";
    }

    public String getLoginNameById(String id){
        return "admin";
    }
}

接下来,我们看一下MethodInterceptor的实现类LogMethodInterceptor

//LogMethodInterceptor .java
public class LogMethodInterceptor implements MethodInterceptor {

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("method exexcute,name: "+method.getName()+",params: "+ Arrays.toString(args));
        return proxy.invokeSuper(obj, args);
    }
}

最后,我们看一下如何通过Enhancer创建代理的代码:

// Main.java
public class Main {
    public static void main(String[] args){
        //将创建代理的字节保存到D盘下的class文件夹下
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");
        //实例化增强器类
        Enhancer enhancer = new Enhancer();
        //设置代理类需要继承的类
        enhancer.setSuperclass(UserService.class);
        //设置代理类的方法拦截器
        enhancer.setCallback(new LogMethodInterceptor());
        //创建UserService的代理类
        UserService userService = (UserService)enhancer.create();
        //执行代理类方法
        userService.getLoginNameById("123");

    }
}

相比于jdk动态代理需要实例化UserService的实现类,CGLIB动态代理更加简单,易用。关键是CGLIB可以为非接口实现类创建代理。
当然,CGLIB也有限制:CGLIB无法代理类的final方法,比如UserService中的finalMethod方法。
注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不会,因为它是final方法,CGLIB无法代理。
在Main.java中,我们通过增加如下的代码,将代理类的字节码保存在磁盘上

     System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");

同样,我们看一下字节码反编译后的结果

//不同于jdk动态代理,CGLIB通过继承的方式生成UserService的代理类
public class UserService$$EnhancerByCGLIB$$d603014e extends UserService implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$getLoginNameById$0$Method;
    private static final MethodProxy CGLIB$getLoginNameById$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("org.weking.proxy.cglib.UserService$$EnhancerByCGLIB$$d603014e");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        CGLIB$getLoginNameById$0$Method = ReflectUtils.findMethods(new String[]{"getLoginNameById", "(Ljava/lang/String;)Ljava/lang/String;"}, (var1 = Class.forName("org.weking.proxy.cglib.UserService")).getDeclaredMethods())[0];
        CGLIB$getLoginNameById$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)Ljava/lang/String;", "getLoginNameById", "CGLIB$getLoginNameById$0");
    }

    final String CGLIB$getLoginNameById$0(String var1) {
        return super.getLoginNameById(var1);
    }

    public final String getLoginNameById(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null?(String)var10000.intercept(this, CGLIB$getLoginNameById$0$Method, new Object[]{var1}, CGLIB$getLoginNameById$0$Proxy):super.getLoginNameById(var1);
    }

    final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }

    public final boolean equals(Object var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if(var10000 != null) {
            Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
            return var2 == null?false:((Boolean)var2).booleanValue();
        } else {
            return super.equals(var1);
        }
    }

    final String CGLIB$toString$2() {
        return super.toString();
    }

    public final String toString() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null?(String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy):super.toString();
    }

    final int CGLIB$hashCode$3() {
        return super.hashCode();
    }

    public final int hashCode() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if(var10000 != null) {
            Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
            return var1 == null?0:((Number)var1).intValue();
        } else {
            return super.hashCode();
        }
    }

    final Object CGLIB$clone$4() throws CloneNotSupportedException {
        return super.clone();
    }

    protected final Object clone() throws CloneNotSupportedException {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null?var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy):super.clone();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case -508378822:
            if(var10000.equals("clone()Ljava/lang/Object;")) {
                return CGLIB$clone$4$Proxy;
            }
            break;
        case 69527633:
            if(var10000.equals("getLoginNameById(Ljava/lang/String;)Ljava/lang/String;")) {
                return CGLIB$getLoginNameById$0$Proxy;
            }
            break;
        case 1826985398:
            if(var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return CGLIB$equals$1$Proxy;
            }
            break;
        case 1913648695:
            if(var10000.equals("toString()Ljava/lang/String;")) {
                return CGLIB$toString$2$Proxy;
            }
            break;
        case 1984935277:
            if(var10000.equals("hashCode()I")) {
                return CGLIB$hashCode$3$Proxy;
            }
        }

        return null;
    }

    public UserService$$EnhancerByCGLIB$$d603014e() {
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        UserService$$EnhancerByCGLIB$$d603014e var1 = (UserService$$EnhancerByCGLIB$$d603014e)var0;
        if(!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if(var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if(CGLIB$STATIC_CALLBACKS == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        UserService$$EnhancerByCGLIB$$d603014e var10000 = new UserService$$EnhancerByCGLIB$$d603014e();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        UserService$$EnhancerByCGLIB$$d603014e var10000 = new UserService$$EnhancerByCGLIB$$d603014e();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        UserService$$EnhancerByCGLIB$$d603014e var10000 = new UserService$$EnhancerByCGLIB$$d603014e;
        switch(var1.length) {
        case 0:
            var10000.();
            CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
            return var10000;
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        MethodInterceptor var10000;
        switch(var1) {
        case 0:
            var10000 = this.CGLIB$CALLBACK_0;
            break;
        default:
            var10000 = null;
        }

        return var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch(var1) {
        case 0:
            this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
        default:
        }
    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    static {
        CGLIB$STATICHOOK1();
    }
}

4.小结

前文详细介绍了静态代理、jdk动态代理、CGLIB动态代理三种实现方式,分析了三种实现方式的优缺点。最后我们总结一下:

  • 静态代理:编译器生成字节码;硬编码,代码耦合性较强,某个代理类能代理的范围有限,不易扩展。

  • jdk动态代理: 运行期动态生成字节码;基于接口实现创建代理;代理相关类代码没有与具体被代理类耦合,能代理范围较大;不能为非接口实现类创建代理;

  • CGLIB动态代理:运行期动态生成字节码;基于继承实现创建代理;代理相关类代码没有与具体被代理类耦合,能代理范围较大,可以为非接口实现类创建代理;无法为final修饰的方法创建代理,使用时需要引用CGLIB相关jar包,性能优于jdk动态代理。

动态代理在Java中应用中较多,比较常见的如Spring AOP的实现就是利用动态代理的方式。学习动态代理对于我们日后使用以及学习其他相关技术的源码有非常大的帮助。

你可能感兴趣的:(Java 动态代理机制解析)