设计模式之代理模式(含JDK动态代理源码分析)

举报
长路 发表于 2022/11/28 18:13:43 2022/11/28
【摘要】 文章目录一、认识代理模式二、静态代理2.1、静态代理模式(通过接口)2.2、实际应用(模拟AOP)三、动态代理3.1、JDK动态代理①实现接口的动态代理类②通过动态代理增强实现类方法源码分析参考资料 一、认识代理模式 代理模式:Java开发中使用较多的一种设计模式,代理设计就是为其他对象提供一种代理以控制对这个对象的访问。 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,程序运行

@[toc]

前言

本篇文章主要介绍静态代理、动态代理的主要实现方式且包含JDK动态代理的源码分析,后序还会更新其他动态代理实现方式。

对应代码地址:Gitee(demo-exer/ java-Lear23designpatterns)Github(java-demo/ java-Lear23designpatterns)

所有博客文章目录索引:博客目录索引(持续更新)


一、认识代理模式

代理模式:Java开发中使用较多的一种设计模式,代理设计就是为其他对象提供一种代理以控制对这个对象的访问。

  • 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,程序运行前代理类的class文件存在。
  • 动态代理:在程序运行期间代理类才通过运用反射机制动态创建而成。
    • 如:JDK动态代理、cglib动态代理、Javassist

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

优缺点

  • 优点:扩展目标对象的功能,将客户端与目标对象分离,降低系统耦合度,增加可扩展性。可以使真实角色的操作更加纯粹,不用去关心一些公共的业务。公共业务交给代理类来进行处理。
  • 缺点:操作系统设计中类数量增加,在客户端与目标对象之间增加一个代理对象,会造成请求处理变慢,增加系统复杂度。

应用场景

  • 安全代理:屏蔽对真实角色的直接访问
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟加载:先加载轻量级的代理对象,真正需要时再加载真实对象
    • 例如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,可以使用代理模式当需要查看图片时,用proxy来进行大图片的打开。4
  • Spring的AOP就使用了动态代理。


二、静态代理

2.1、静态代理模式(通过接口)

  • 代理人与被代理人都实现同一个接口,此时都会实现同一个方法,将被代理类传入到代理类中,在代理类的实现方法中使用被代理类实现的方法,并且其中还可以做一些其他操作。

示例代码见proxystatic包中代码:

image-20210317231717723

抽象代理:NetWork

//抽象接口
public interface NetWork {
    void browser();
}

具体被代理类(真实服务器):NetWork

//真实服务器
public class Server implements NetWork{
    @Override
    public void browser() {
        System.out.println("真实的服务器访问网络...");
    }
}

具体代理类(代理服务器):ProxyServer

//代理服务器
public class ProxyServer implements NetWork{

    private NetWork server;

    //传入被代理人(同样实现了NetWork接口)
    public ProxyServer(NetWork server){
        this.server = server;
    }

    @Override
    public void browser() {
        //代理服务器来进行检查操作
        check();

        //被代理类开始真正联网操作
        server.browser();;
    }

    public void check(){
        System.out.println("联网之前被代理类正在进行检查操作...");
    }
}

测试程序:测试类Customer

public class Customer {
    public static void main(String[] args) {
        //通过传入被代理实例让代理类进行联网操作
        new ProxyServer(new Server()).browser();
    }
}

image-20210317231912523

说明:被代理类只需要负责核心的事情,其他公共的事情可交由代理类去处理,上面的程序代码是静态代理,其代理类是在编译期间就确定了的。



2.2、实际应用(模拟AOP)

通过静态代理的方式来模拟实现Spring中的AOP

demoProxy下的static2目录:

image-20210318140353179

公共接口:Service

public interface Service {

    void query();
    void update();
    void delete();
}

被代理类:ServiceImpl

public class ServiceImpl implements Service{

    @Override
    public void query() {
        System.out.println("查询操作");
    }

    @Override
    public void update() {
        System.out.println("修改操作");
    }

    @Override
    public void delete() {
        System.out.println("删除操作");
    }
}

代理类:ServiceProxy

public class ServiceProxy implements Service{

    private Service service;

    public ServiceProxy(Service service){
        this.service = service;
    }

    @Override
    public void query() {
        System.err.println("执行日志记录...");
        service.query();
    }

    @Override
    public void update() {
        System.err.println("执行日志记录...");
        service.update();
    }

    @Override
    public void delete() {
        System.err.println("执行日志记录...");
        service.delete();
    }
}
  • 通过代理类,我们将原本serviceImpl被代理类的所有方法进行了增强。

测试类:Customer

public class Customer {
    @Test
    public void test01(){
        //被代理类测试
        Service service = new ServiceImpl();
        service.query();
        service.update();
        service.query();
    }

    @Test
    public void test02(){
        //使用代理类来对被代理类进行方法增强
        Service service = new ServiceProxy(new ServiceImpl());
        service.query();
        service.update();
        service.query();
    }
}

image-20210318142016382

image-20210318142033104

  • 可以看到通过代理类实现了被代理类方法的增强。

说明:想要不修改原有的代码就能增强类的方法,我们就可以使用静态代理来实现,首次必要条件是代理类与被代理类都实现同一个接口,当想要增强方法时,只需要将被代理类传入到代理类中即可!不过一般在实际应用中并不使用静态代理,因为静态代理需要你在编译期前自己创建一个代理类,若是有多个类想要被静态代理岂不是要写多个代理类,所以我们一般会去使用动态代理,在Spring中的AOP就使用到了动态代理默认是使用的JDK动态代理。



三、动态代理

3.1、JDK动态代理

①实现接口的动态代理类

ProxyJDKDynamic目录下的内容:

image-20210318205602741

接口:Service

public interface Service {
    void query();
    void update();
    void delete();
}

测试类:Customer(包含自定义的InvocationHandler

//自定义的InvocationHandler
class MyInvocationHandler implements InvocationHandler{

    //代理类将会调用的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("query".equals(method.getName())){
            System.out.println("调用了query()方法");
        }else if("update".equals(method.getName())){
            System.out.println("调用了update()方法");
        }else if("delete".equals(method.getName())){
            System.out.println("调用了delete()方法");
        }
        return null;
    }
}

//使用JDK静态代理(加强接口)
public class Customer {
    public static void main(String[] args) {
        //依次是类加载器,对应包含接口class类的数组,以及自定义的InvocationHandler
        Service service = (Service) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Service.class}, new MyInvocationHandler());
        service.query();
        service.update();
        service.delete();
    }
}

image-20210318210612647



②通过动态代理增强实现类方法

目标描述:在调用ServiceImpl(Service实现类)中的方法时都进行日志记录操作!

本部分代码见xyz.com.changlu.Proxy包下的JDKDynamic2目录里代码:

image-20210318211830534

接口类:Service

public interface Service {

    void query();
    void update();
    void delete();
}

接口实现类:ServiceImpl

public class ServiceImpl implements Service {

    @Override
    public void query() {
        System.out.println("查询操作");
    }

    @Override
    public void update() {
        System.out.println("修改操作");
    }

    @Override
    public void delete() {
        System.out.println("删除操作");
    }
}

接下来我们对Service实现类ServiceImpl的所有方法进行增强,见测试类:Customer

//自定义InvocationHandler
class MyInvocationHandler implements InvocationHandler {

    private Service service;

    //通过构造器传入接口实现类,方便下面通过反射来执行实现类中的方法
    public MyInvocationHandler(Service service){
        this.service = service;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行日志记录......");
        //执行ServiceImpl实现类中的方法
        method.invoke(service, args);
        return null;
    }
}

//测试类
public class Customer {
    public static void main(String[] args) {
        //将要增强的实现类传入到自定义的InvocationHandler中
        MyInvocationHandler invocationHandler = new MyInvocationHandler(new ServiceImpl());
        //传入参数:类加载器、指定接口的class类数组(可指定多个)、自定义InvocationHandler
        Service service = (Service) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Service.class}, invocationHandler);
        service.query();
        service.delete();
        service.update();
    }
}

image-20210318212124828

说明:我们可以通过动态代理来增强某一实现类中的所有方法,该代理类是在运行期间创建的(根据你在newProxyInstance()方法中传入的接口来实现的代理类),当你调用代理类(及上面的26行)的方法时会默认执行自定义InvocationHandler类中的invoke()方法。

注意:仅仅是通过上面例子还是不能够深入理解JDK的动态代理的,我在起初学习时也有许多的疑惑,例如为什么要自定义InvocationHandler接口,其中的invoke()方法是怎样被调用的何时被调用的,其中的几个参数都指代什么?这些都通过查看源码得到解答!看下面的源码分析。



源码分析

前提说明:要懂一些反射的知识,若是对反射不太清楚的可以先去看下反射的知识点再来看下面的源码。

Proxy.newProxyInstance()源码初探

首先要说明的是使用Proxy.newProxyInstance()方法你会得到一个代理类(实现你传入的接口类参数)!我们看源码:

public class Proxy implements java.io.Serializable {
    
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) throws IllegalArgumentException
    {
        //检查传入的参数h是否为null,若为null直接抛出异常!
        Objects.requireNonNull(h);

        //12-17上是相关检查代码,暂且跳过
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        //这个方法是通过作为参数的类加载器以及接口来创建代理类!!!(重要)
        Class<?> cl = getProxyClass0(loader, intfs);
        ...
    }
}
  • 重点看20行调用的方法,该方法创建了代理类的字节码文件,该代理类是动态生成的类文件,暂时缓存在jvm中,我们无法直接在源码中查看到,不过我们可以通过一些方法将该代理类保存到本地来进行查看,见下。

运行下面代码,即可在当前目录中生成Proxy0.java文件:

//生成代理类文件
@Test
public void test() throws Exception {
    //主要是第二个参数:其为要实现的接口(数组形式传递,这里传入Service接口)
    byte[]classFile = ProxyGenerator.generateProxyClass("Proxy0",new Class[]{Service.class});
    FileOutputStream fos =new FileOutputStream(new File("Proxy0.class"));
    fos.write(classFile);
    fos.flush();
    fos.close();
}

运行之后,我们即可在目录中获得了一个Proxy0.class文件,我们来看看这个代理类的真面目:

//默认继承Proxy类以及实现Service接口
public final class Proxy0 extends Proxy implements Service {
    private static Method m1;
    private static Method m5;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    //有参构造中的参数好眼熟噢,不就是我们之前实现的InvocationHandler接口嘛
    public Proxy0(InvocationHandler var1) throws  {
        //父类Proxy的有参构造器
        super(var1);
    }

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

    //实现了query()方法
    public final void query() throws  {
        try {
            //调用的是父类Proxy中的InvocationHandler里的invoke()方法(即我们调用newProxyInstance中的MyInvocationHandler,我们后面在Proxy类的源码中会再次看到)
         //传入参数依次为:代理类Proxy0实例,通过反射获得的Service接口中的query()的method的实例,query()方法中的参数
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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);
        }
    }

    //同样也实现了delete()方法
    public final void delete() throws  {
        try {
            //与上面query()中的注释大致相同,只不过这里传入了m4实例(也就是通过反射获取到Service接口的delete()的Method实例)
            super.h.invoke(this, m4, (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);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    //同上
    public final void update() throws  {
        try {
            //也就是更改了m3实例,此时你应该能够判断了吧
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    //静态代码块中通过反射获取到所有对应类的Method实例,着重看下面①②③
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            //①获取Service接口中的query()的method实例
            m5 = Class.forName("xyz.changlu.Proxy.JDKDynamic2.Service").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            //②获取Service接口中的delete()的method实例
            m4 = Class.forName("xyz.changlu.Proxy.JDKDynamic2.Service").getMethod("delete");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            //③获取Service接口中的update()的method实例
            m3 = Class.forName("xyz.changlu.Proxy.JDKDynamic2.Service").getMethod("update");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
  • Proxy0代理类默认实现Proxy类,实现Service接口。
  • 通过看Proxy0这个代理类的源代码之后是不是有点感觉了,在该代理类的方法中就使用到了InvocationHandler接口中的invoke()方法。

相信看到这里已经有点感觉了,我们继续看之前Proxy类中的newProxyInstance()的源码:

public class Proxy implements java.io.Serializable {
    
    //下面反射获取代理类Proxy0会用到该参数
    private static final Class<?>[] constructorParams = { InvocationHandler.class };
    
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ....//上面看过的源码就不再展示了
        //该cl就是我们上面代理类的字节码Class类(即相当于Proxy0.class)
        Class<?> cl = getProxyClass0(loader, intfs);

        try {
            //暂且跳过
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            //获取到Proxy0代理类的有参构造器(constructorParams即表示InvocationHandler.class)
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            //将参数中的自定义InvocationHandler传入到局部属性ih中
            final InvocationHandler ih = h;
            //暂且跳过
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //通过使用之前获取到的有参构造器cons来通过反射new实例
            //h即为本方法作为参数的自定义InvocationHandler
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
}

看了newProxyInstance()方法,我们可以知道在方法最后通过反射来创建了代理类Proxy0的实例并返回。

在创建实例过程中,实际上还调用了Proxy的有参构造器(可见之前Proxy源码),如下:

public class Proxy implements java.io.Serializable {
    
    protected InvocationHandler h;
    
    //传入的参数实际上就是我们调用newProxyInstance()方法中作为参数的自定义InvocationHandler
    //这个h很有用,我们之前看到代理类中的各个实现接口方法中调用了h的invoke()方法
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
}

至此我们newProxyInstance()的源码分析已经结束了。

小总结

  1. 此方法在调用期间(即运行期间)创建了Proxy0的代理类文件并暂存在jvm中,在方法最后返回了Proxy0代理类实例(即Service接口的实现类)。
  2. 此方法调用最后创建Proxy0实例时还将此方法中的自定义InvocationHandler(即MyInvocationHandler)传给Proxy类中的变量。

调用代理类的方法时的过程

我们就看之前②中代理类调用query()方法:

image-20210318224056231

首先会执行代理类Proxy0query()方法:

public final class Proxy0 extends Proxy implements Service {

    public final void query() throws  {
        try {
        	//super.h => Proxy.h(即我们自定义的InvocationHandler) 并调用其中的invoke()方法
            //参数:代理类实例,Sercvice接口的query()的method实例,该方法的参数(由于方法参数为空所以是null)
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

我们直接看调用的invoke()方法好吧:

//自定义InvocationHandler
class MyInvocationHandler implements InvocationHandler {

    private Service service;

    public MyInvocationHandler(Service service){
        this.service = service;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行日志记录......");
        //执行ServiceImpl实现类中的方法
        method.invoke(service, args);
        return null;
    }
}
  • 此时invoke()方法中的参数我们就心知肚明了吧,第一个就是Proxy0代理类的实例,第二个就是Service接口中的指定方法Method实例(即我们调用的方法,这里就是指的query()方法),第三个就是方法中的参数(数组形式)。

若我们想要增强ServiceImpl类中的方法,我们就需要在MyInvocationHandler中传入对应的实例,method.invoke(service, args);实际上就是调用ServiceImpl中的核心方法,在其上下可以任意增加公共的业务操作即可。

相信看到这里应该对于JDK动态代理有了比较深入的理解了吧,一定要自己回到源码中进行思考,多进行debug看源码执行过程。我当时学JDK动态代理时也是一脸懵逼,后来通过看源码并结合其他大佬的博客这才搞懂其中的原理。

  • 为啥要实现InvocationHandler?其中invoke()方法调用时机,该方法的参数含义?Proxy.newProxyInstance()做了些什么事情,其中传入的多个参数含义?对于聪明的你来说一定已经心知肚明了吧。

对于invoke()方法中的Object proxy属性有何用途可见文章:14、反射与JDK动态代理 (第七部分的2里的关于InvocationHandler接口中第一个参数proxy)

说明:狂神的设计模式系列讲的特别好特别是其中的单例模式,还是很感谢狂神带我学习设计模式。对于动态代理看到许多小伙伴在弹幕中打出了一脸懵逼…,所以特此码了这篇文章,也正好加深一下我自己对于动态代理的理解吧,希望能够帮助到大家,若是文章中有错误请指出我会进行修正,一起加油!(一定要自己进行debug调试,多实践看源码,千万不要只看不动手)


3.2、Cglib动态代理(基于类)

引入第三方jar包:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.4</version>
</dependency>

测试demo:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @ClassName Main
 * @Author ChangLu
 * @Date 2021/5/17 17:17
 * @Description CGLIB实现
 */

//被代理类
class Person{
    public void walk(){
        System.out.println("Person walk......");
    }
}

//需要自定义实现方法拦截器
class MyCglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz){
        enhancer.setSuperclass(clazz);//设置生成的类将扩展的类,需要具有可访问的构造函数
        enhancer.setCallback(this);//将本实例传入,主要需要使用重写的intercept的方法
        return enhancer.create();//通过字节码技术动态创建子类实例
    }

    /**
     *
     * @param o 代理类对象
     * @param method 代理类对象调用的方法
     * @param objects 调用方法的参数
     * @param methodProxy 被代理对象
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("前置通知");
        Object object = methodProxy.invokeSuper(o, objects);//方法代理着进行反射执行
        System.out.println("后置通知");
        return object;
    }
}

public class Main {
    public static void main(String[] args) {
        MyCglibProxy proxy = new MyCglibProxy();
        Person person = (Person) proxy.getProxy(Person.class);//获取代理类
        person.walk();
    }
}

image-20210517174203972


两种动态代理的比较

JDK动态代理不需要任何外部依赖,但是只能基于接口进行代理;CGLIB通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法代理final对象与final方法。(final类型不能有子类,final方法不能被重载)。

在JDK中就使用到了动态代理来实现AOP,有两种方式一种是基于JDK,另一种是基于CGLIB方式。



参考资料

视频:【狂神说Java】通俗易懂的23种设计模式教学(停更)

[1]. 代理模式(代理设计模式)详解-C语言中文网

[2]. JDK动态代理-ProxyGenerator生成代理类的字节码文件解析 介绍得比较详细

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。