【设计模式】代理模式 - 静态代理、JDK动态代理、Cglib动态代理

159 阅读7分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

什么是代理模式

graph LR
访问者 --> 代理类--> 被代理类

代理模式是一种行为型模式,可以理解为创建一个代理类,将被代理类进行包装增强,使得可以不改变原来被代理类的情况下额外对其进行扩展。例:我(被代理类)喜欢一个女生,但是我不自信,没有勇气表白,所以我找了一个舍友(代理类),这个舍友很自信,让他代替我进行我向女生表白这个行为,最终都是执行了我向女生表白这个行为,但是舍友对我进行了增强。

代理模式的好处

正如上面所说,代理模式可以在不改变被代理类的情况下进行增强,可以使代码的业务解耦,例如Spring中的拦截器,我们可以使用代理模式在不改变原来业务代码的情况下,在其前后加入日志或校验操作,实现业务和基础操作的分离,使代码更加优雅。

Java中代理对象的创建方式

创建代理类的方式有两种,一种是静态代理和动态代理,其中Java中动态代理比较常用的有两种:基于接口的JDK动态代理和基于子类的Cglib动态代理,当然还有其他创建方式,但在日常开发中接触的比较少,此文不再展开。

静态代理

代码演示

静态代理是最简单的代理方式,首先创建如下结构:

image.png

Person.java:

// Person接口,有一个eat方法
interface Person {
    void eat();
}

Developer.java:

// Developer实现类,也就是被代理类,实现Person接口和eat方法
public class Developer implements Person {
    @Override
    public void eat() {
        System.out.println("吃饭了");
    }
}

PersonProxy.java:

// 代理类,实现对Developer的代理
public class PersonProxy implements Person {
    private Person person;

    public PersonProxy(Person person) {
        this.person = person;
    }

    @Override
    public void eat() {
        System.out.println("吃饭前要洗手");
        person.eat();
    }
}

启动测试类:ProxyTest.java:

public class ProxyTest {
    public static void main(String[] args) {
        Developer developer = new Developer();
        PersonProxy personProxy = new PersonProxy(developer);
        personProxy.eat();
    }
}

输出如下: image.png 我们看到了PersonProxy对Developer进行了增强,在eat()之前洗手。

缺点

缺点其实很明显,就是针对每一种需要被代理的类都需要创建代理类,如果都使用静态代理,将 会出现大量的代理类,这对于今后的维护是灾难性的。于是我们就想到用反射技术,将传入的需要被代理的类进行处理,自动生成代理类,这样就避免了上面的问题,这也就是动态代理。

动态代理

动态代理的本质其实就是在程序的执行过程中,使用反射机制创建代理对象。动态代理在Java中常见的技术分为JDK动态代理和Cglib动态代理,还有其他代理技术如javassist动态代理、ByteBuddy动态代理等。

JDK动态代理

JDK从1.3版本开始支持JDK动态代理。主要核心类为:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

代码演示

先看一下如何使用JDK动态代理: Developer.java和Person.java代码保持不变,我们主要修改PersonProxy.java,为了区分,我们新建JDK动态代理类JdkProxyHandler.java

public class JdkProxyHandler implements InvocationHandler {
    // 不再静态声明被代理类,用Object接收所有被代理类
    private Object proxyObject;

    public JdkProxyHandler(Object proxyObject) {
        this.proxyObject = proxyObject;
    }

    /**
     * @param proxy JDK生成的代理对象
     * @param method 被代理类中的方法
     * @param args 被代理类中的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取方法名,这里是指被调用的方法
        String methodName = method.getName();
        if (methodName.equals("eat")) {
            System.out.println("吃饭前要洗手");
        }
        return method.invoke(proxyObject, args);
    }
}

修改ProxyTest.java

public class ProxyTest {
    public static void main(String[] args) {
        JdkProxyHandler proxy = new JdkProxyHandler(new Developer());
        Person developer = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
        developer.eat();
    }
}

运行结果如下:

image.png

我们可以看到同样实现了对Developer的增强。

代码解析

首先是声明代理类处理器:实现java.lang.reflect.InvocationHandler的invoke()方法,这样可以使用反射获取调用方法的信息,以后不管被代理类里有什么方法,我们都可以动态的获取。

然后就是创建代理对象:

Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);

newProxyInstance传入的参数依次为:ClassLoader(代理类的类加载器),Class[](代理类要实现的接口列表),InvocationHandler(将方法调用分派到的处理程序)。 这三者的用处也很明显,ClassLoader加载代理类,Class[]用于实现接口对应的方法,InvocationHandler用于指定进行怎样的处理。

InvocationHandler和Proxy的源码不是很复杂,大体来说就是使用反射创建指定的类,强烈建议大体看一眼,可以学到很多。

缺点

JDK动态代理的缺点主要有两点,一个是需要被代理类实现接口,因为代理类是基于被代理类的接口生成的,所以要求被代理类必须实现接口。第二个是效率问题,不管怎么说JDK动态代理都是基于反射生成代理类的,一般情况下如果生成的代理类数量不是很多,对效率影响不是很高,但是如果生成的代理类数量很多,比如Spring项目启动时会创建大量的代理类,如果使用JDK动态代理则会导致启动速度变慢,占用系统资源等问题,这也是为什么Spring后来改成Cglib动态代理的原因。

Cglib动态代理

Cglib底层使用ASM框架,ASM框架可以直接操作字节码文件,绕过了反射这一层,游离于Java之外,所以提供了比JDK更为强大的动态代理,比如Cglib是通过生成被代理类的子类来实现代理的,不要求被代理类实现接口,比JDK动态代理更加灵活,速度也比JDK动态代理更快。

Cglib动态代理的核心类也有两个:org.springframework.cglib.proxy.Enhancerorg.springframework.cglib.proxy.MethodInterceptor,这里以spring-core包中的为例,其他包中的使用方法也类似,底层都是ASM框架,只不过自己封装了一层而已。

代码演示

同样保持Person.java和Developer.java不动,我们创建CglibProxyHandler.java来定义生成代理类处理器

public class CglibProxyHandler implements MethodInterceptor {
    // 原被代理对象
    private Object proxyObject;
    // Cglib创建代理类需要的enhancer对象
    private Enhancer enhancer = new Enhancer();
    public CglibProxyHandler(Object proxyObject) {
        this.proxyObject = proxyObject;
    }

    /** 根据被代理类,创建enhancer被代理对象,用于之后的代理类创建 */
    public Object getProxyObject() {
        enhancer.setSuperclass(proxyObject.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     * @param obj    生成的代理对象
     * @param method 被代理类中的方法
     * @param args   被代理类中的参数
     * @param proxy  父类的方法,也就是代理类覆盖了被代理类的那个方法
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 获取方法名,这里是指被调用的方法
        String methodName = method.getName();
        if (methodName.equals("eat")) {
            System.out.println("吃饭前要洗手");
        }
        return method.invoke(proxyObject, args);
    }
}

修改ProxyTest.java:

public class ProxyTest {
    public static void main(String[] args) {
        CglibProxyHandler proxyHandler = new CglibProxyHandler(new Developer());
        Person developer = (Person) proxyHandler.getProxyObject();
        developer.eat();
    }
}

运行输出结果:

image.png

毫无疑问也实现了动态代理功能,并且可以看到【代理类 extends 被代理类】

image.png

代码解析

Cglib的MethodInterceptor也和JDK动态代理一样,用来获取调用方法的信息,同时因为Cglib是通过继承来实现的,也能获取继承的父类的方法信息。

Enhancer.create(),根据指定的被代理类,创建代理类,源码相对于jdk更加复杂,总而言之是通过继承来实现的,有兴趣的可以研究。

两者对比

JDK动态代理Cglib动态代理原因
速度相对慢JDK使用反射,Cglib直接操作字节码
代理类implements被代理类的接口extends被代理类反射操作继承复杂,字节码操作较为简单
被代理类需要实现接口更加灵活,不需要实现接口代理类的生成方式决定
应用场景不依赖外部框架,简单生成代理对象时使用需要生成大量代理对象时使用,如Spring初始化