Java动态代理
2019-04-25 | 分类 Programing-language | 标签 Java Jdk Cglib Dynamic-proxy

代理

代理(Proxy),在我们现实生活中经常会遇到,比如一些商品的销售代理商,公司授权给代理商,让其帮忙销售商品。还有,我们经常听到的代理人–负责帮别人处理事务。而对应到计算机中,代理是一种隐藏和替代对象的手段。将对象隐藏在代理对象之后,所有对真实对象的访问,都由代理对象代为处理。由代理对象负责和客户端交互,并负责将请求转发给实际处理的对象。

通过代理的手段,我们可以在代理对象中决定什么时候访问实际对象,以及在被代理对象的前后添加一些代码。如果你熟悉Java的Spring框架的话,应该接触过AOP的概念,其实Spring的AOP就是通过我们后面要结束的动态代理(Dynamic Proxy)实现的。

静态代理

在开始介绍动态代理之前,我们先看下静态代理。静态代理和动态代理相比,唯一的区别就是代理对象的创建,是我们以静态的方式编写、编译和连接到代码中的,是一种编译时的代理创建方式。下面,我们看一段最简单的创建静态代理的代码:

// Foo.java
public class Foo {
    private String name;

    public Foo(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

// ProxyFoo.java
public class FooProxy extends Foo {
    Foo object;

    public FooProxy(Foo object) {
        this.object = object;
    }

    @Override
    public String getName() {
        System.out.println("get name through proxy");
        return super.getName();
    }

    @Override
    public void setName(String name) {
        System.out.println("set name through proxy");
        super.setName(name);
    }

    public static void main(String ...args) {
        Foo foo = new Foo();
        Foo proxy = new FooProxy(foo);
        proxy.setName("name");
        System.out.println(proxy.getName());
    }
}

输出结果:

set name through proxy
get name through proxy
name

我们为 Foo 这个类创建了一个 FooProxy 类,对Foo这个类的getName()调用,我们代理给了FooProxy类,由FooProxy负责处理请求。 静态代理类图 当我们需要为每个类创建代理类的时候,如果采用静态代理的方式,那么我们就需要为项目中的所有要创建代理类的那些类编写代理类,这种方式既耗时又费力。如果能提供一种方式,可以在运行时动态地对某个类创建代理类,那么就可以在必要的时候方便地创建代理类。

动态代理

借助于JVM的字节码动态生成的技术和JVM运行时链接的特性,Java提供了动态代理技术。动态代理(Dynamic Proxy)是一种可以在运行时创建代理类的技术,实现代理类的动态创建和链接。

Java实现动态代理有两种方式,一种是在JDK 1.3引入的Proxy动态代理机制;还有一种是由CGLIB提供的动态代理机制。两种动态代理的实现原理都是基于字节码动态生成技术。接下来,我们来看下如何利用Java提供的动态代理机制创建代理类。

JDK 动态代理

实现

在JDK 1.3开始,Java引入了基于接口的动态代理实现 Proxy。通过Proxy提供的 InvocationHandler 机制,可以在运行时在被代理类的方法执行前后添加代码。

下面,我们用 Proxy 生成动态代理类来实现文章开头给的静态代理的例子:

// FooInterface.class
public interface FooInterface {
     String setName();
     String getName();
}

// Foo.java
public class Foo implements FooInterface {
    private String name;

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    public static void main(String ...args) {
        InvocationHandler handler = new InvocationHandler() {
            private FooInterface foo = new Foo();

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getDeclaringClass().equals(FooInterface.class)) {
                    if ("getName".equals(method.getName())) {
                        System.out.println("get name through proxy");
                    }
                    else if ("setName".equals(method.getName())) {
                        System.out.println("set name through proxy");
                    }
                    return method.invoke(foo, args);
                }
                return method.invoke(foo, args);
            }
        };

        Class<?> fooProxy = Proxy.getProxyClass(Foo.class.getClassLoader(), FooInterface.class);
        try {
            FooInterface foo = (FooInterface) fooProxy.getConstructor(InvocationHandler.class).newInstance(handler);
            foo.setName("name");
            System.out.println(foo.getName());
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
            ex.printStackTrace();
        }
    }
}

输出结果:

set name through proxy
get name through proxy
name

JDK的动态代理是基于接口来生成的,为了创建一个代理类,我们首先需要为被代理的对象创建一个接口 FooInterface。然后让需要被代理的类 Foo 实现这个接口。通过 ProxygetProxyClass方法创建一个代理类对象 fooProxy。这个类对象类似于我们文章开头创建的 FooProxy 的类对象,不同的是这次我们是在运行时动态生成,而不是在编译时创建的。 Proxy动态代理类图 对实现动态代理的逻辑部分, Proxy 是通过 InvocationHandler 这个接口来封装的。所有对代理对象 fooProxy 的方法调用,都会转发到 InvocationHandler 类的public Object invoke(Object proxy, Method method, Object[] args) throws Throwable方法中。这是一种简单的回调机制,由 InvocationHandler 类的invoke方法负责代理类具体的代理逻辑。

下图描述了Proxy的代理过程: Proxy动态代理过程

JDK的 Proxy 类提供了一个newProxyInstance静态方法来简化创建动态代理类的过程。静态方法内部将上述创建代理类的过程封装起来,对外提供了一个简化的接口。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

创建代理类的过程可以简化为:

InvocationHandler handler = new InvocationHandler() {
    private FooInterface foo = new Foo();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getDeclaringClass().equals(FooInterface.class)) {
            if ("getName".equals(method.getName())) {
                System.out.println("get name through proxy");
            }
            else if ("setName".equals(method.getName())) {
                System.out.println("set name through proxy");
            }
            return method.invoke(foo, args);
        }
        return method.invoke(foo, args);
    }
};

FooInterface fooProxy = (FooInterface) Proxy.newProxyInstance(
    Foo.class.getClassLoader(), new Class[] {FooInterface.class}, handler);

InvocationHandler

InvocationHandler 这个接口只有一个invoke方法,用来实现具体的调用逻辑。invoke方法签名的定义如下:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

其中入参proxy表示代理类对象本身,method表示代理类当前调用的方法的方法对象,方法对象的类型和通过Java反射回去的方法对象的类型一致。最后一个参数args是方法调用时传递的参数。需要注意的是,如果在调用method.invoke()的使用,传递的是代理对象object,会导致无限循环,最终导致栈溢出。

不足

JDK的动态代理是基于接口来实现的,所以如果要使用JDK的Proxy创建代理类,需要被代理的对象实现接口。如果需要对没有实现任何接口的普通类创建待类,JDK的Proxy机制就没有办法了。为了解决对不实现任何接口的类创建代理类,我们需要用到CGLIB这个工具包。接下来,我们来看下如何用CGLIB动态创建代理类。

CGLIB动态代理

简介

CGLIB是一个强大的JVM字节码生成库,在运行时通过字节码生成的方式创建子类和生成接口的实现类。CGLIB底层是基于ASM这个字节码操作框架实现的,但是它封装了晦涩的字节码操作细节,提供了一套更加简单和易于使用的API。

CGLIB

CGLIB通过在运行时生成字节码的方式,基于继承和接口实现来创建代理类。所以可以认为CGLIB比JDK提供的Proxy动态代理实现机制更加强大,CGLIB除了支持基于接口的动态代理创建,还支持基于类继承的动态代理实现。但是,和Proxy相比,使用CGLIB需要引入额外的三方库,而Proxy是JDK自带的一套动态代理生成框架。除了动态代理,CGLIB这个字节码生成库还可以做很多其他的事情,比如Java Bean的动态生成、Bean拷贝等。由于本文主要关注Java的动态代理生成技术,所以这里不展开讨论。

接下来,我们来看下如何用CGLIB实现动态代理。

实现

由于CGLIB是一个外部库,首先我们需要引入依赖。CGLIB的引入可以参考官方文档。我们这里使用Maven来做依赖管理:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>${cglib.version}</version>
</dependency>

CGLIB提供了一个 Enhancer 类来实现动态代理。Enhancer 类提供了一系列API来配置增强类,我们先看一个例子:

public class Foo {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public static void main(String ...args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Foo.class);
        enhancer.setCallback(new CallbackImpl());
        Foo fooProxy = (Foo) enhancer.create();
        fooProxy.setName("name");
        System.out.println(fooProxy.getName());

    }

    private static class CallbackImpl implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            if ("getName".equals(method.getName())) {
                System.out.println("get name through proxy");
            }
            else if ("setName".equals(method.getName())) {
                System.out.println("set name through proxy");
            }
            return methodProxy.invokeSuper(o, objects);
        }
    }
}

输出结果:

set name through proxy
get name through proxy
name

我们首先实例化一个 Enhancer 对象,然后调用enhancer.setSuperclass()设置需要被代理的类的类对象。我们知道CGLIB是通过继承的方式来实现动态代理的,所以这里需要设置一个父类。然后,我们需要通过enhancer.setCallback()设置一个回调对象,这里的回调对象必须是一个 Callback 接口的实现。这个接口的作用类似于JDK动态代理中的 InvocationHandler 。CGLIB提供了多个 Callback 类型,用于不同的代理场景,这里我们使用 MethodInterceptorCGLIB动态代理 MethodInterceptor 接口继承于 Callback

public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

这里我们使用intercept方法中的 MethodProxy 类型的参数。MethodProxy 提供了一个invokeSuper()方法,可以很方便得用来调用被代理的类的方法。MethodInterceptor 接口中的intercept方法的入参,第一个参数表示代理类对象;第二个参数表示被代理的方法的方法对象;第三个参数表示方法的入参;第四个参数是对被代理对象的方法对象的代理。

类似于JDK中的 InvocationHandler 接口,CGLIB也提供了一个 InvocationHandler 类型的Callback:

public interface InvocationHandler extends Callback {
    Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
}

入参的定义和JDK中的 InvocationHandler 的定义相同,我们可以将上面的 CallbackImpl 的实现修改成 InvocationHandler 类型的实现,以达到相同的目的:

private static class CallbackImpl implements InvocationHandler {
    private Foo foo = new Foo();
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        if (method.getDeclaringClass().equals(Foo.class)) {
            if ("getName".equals(method.getName())) {
                System.out.println("get name through proxy");
            } else if ("setName".equals(method.getName())) {
                System.out.println("set name through proxy");
            }
        }
        return method.invoke(foo, objects);
    }
}

CGLIB动态代理过程如下: CGLIB动态代理过程

不足

相比于JDK提供的动态代理机制,CGLIB提供的动态代理更加强大和通用。但是CGLIB的使用,也不是万能的。由于CGLIB是基于继承来解决JDK的动态代理存在的不足的,所以继承机制存在的局限性也适用于CGLIB的动态代理。我们知道被 final 关键字修饰的方法或者类是不能被继承的,所以如果一个类或者方法被修饰 final 关键字修饰,那么CGLIB也是不能对这个类创建动态代理的。

对比

下面是JDK的动态代理和CGLIB动态代理的一个简单的对比。

对比点 JDK Proxy CGLIB Proxy
外部依赖 JDK自带 需要引入CGLIB包
实现原理 字节码生成 字节码生成
代理方式 基于接口 同时支持接口实现和类继承
不足 被代理的类必须实现一个可访问的接口 被代理的类和被代理的方法不能进行final修饰
TOP