天天看点

设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

代理模式顾名思义就是进行代理,简单来说就是经纪人,他直接与你沟通,并帮助做更多的事情。

在程序中,代理模式那可谓是非常重要,像Spring的aop就是动态代理,而且很多框架中都是用到了代理模式。

代理模式在我们不改变原有代码的基础上对某一个方法进行增强,这种增强可以是提前编写好的代码,也可以是自动编写的代码。这就分为静态代理和动态代理。

静态代理

静态代理是显式的帮助我们对目标类进行增强。

我们定义一个场景:12306提供最基本的卖票功能,但也有很多软件也卖票,例如美团、携程等。其实在第三方平台买票实际也是要登陆12306账号的,不过第三方会提供一些额外的功能帮你买票,比如说加速包抢票等。而这些额外的服务其实就是在12306买票的基础上增加了额外的功能,简单理解就是进行了加工,这其实就是一种代理。

设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)
/**
 * 1.定义卖票接口
 */
public interface Tickets {
    void sell();
}
           
/**
 * 2、12306类
 */
public class Demo12306 implements Tickets {
    @Override
    public void sell() {
        System.out.println("卖票");
    }
}
           
/**
 * 3.美团类里包含了12306类,并且在12306类的基础上增加了抢票功能
 */
public class MeiTuan implements Tickets {

    private Tickets tickets;

    MeiTuan(Tickets tickets){
        this.tickets = tickets;
    }

    @Override
    public void sell() {
        System.out.println("加速包开始抢票");
        //这里是12306原本的卖票
        tickets.sell();
        System.out.println("加速包抢票成功");

    }
}
           
public class Test {
    public static void main(String[] args) {
        //创建原始的12306类
        Tickets demo12306 = new Demo12306();
        //美团对12306进行代理,增加额外的抢票功能
        Tickets meiTuan = new MeiTuan(demo12306);
        /**
         * 打印结果:
         * 加速包开始抢票
         * 卖票
         * 加速包抢票成功
         */
        meiTuan.sell();
    }
}
           

这种方式虽然在不改变原本类的基础上增加了功能,但是我们如果每有代理需求的时候,就要一直创建代理类,而且很不灵活,并且在jvm运行之前.class文件就已经生成了。

动态代理

动态代理相比较于静态代理区别在于:我们不用设计一个代理类来具体的来代理某一个对象,而是程序运行的时候在jvm中帮我们生成代理类。

而代理和被代理之前有一个包含的关系,代理要从被代理那里获取到所有方法进行代理。这样以来就需要被代理类有一个统一的对外服务标准,这个标准就是接口和抽象类。

对于动态代理我们有两种实现方式,一个是基于接口的实现、另一个是基于抽象类的实现。

jdk实现动态代理

jdk本身就帮我们实现了动态代理,这种代理是基于接口方式进行实现的。

还是之前卖票的例子,但是这一次是用动态代理方式进行实现。

美团并不去实现卖票接口了,而是实现

InvocationHandler

接口,在创建代理类的时候就不是直接调用方法了(当然也调用不了,没有实现卖票接口了),这个时候使用

Proxy.newProxyInstance()

来进行创建。我们先上代码,再来进行解释。

/**
 * 1.定义卖票接口
 */
public interface Tickets {
    void sell();
}
           
/**
 * 2、12306类
 */
public class Demo12306 implements Tickets {
    @Override
    public void sell() {
        System.out.println("卖票");
    }
}
           

前两步其实都是一样的

/**
 * 3.美团类里包含了12306类,并且在12306类的基础上增加了抢票功能
 *  这里实现InvocationHandler接口
 *  本类不是真正的代理类了
 */
public class MeiTuan implements InvocationHandler {

    private Tickets tickets;

    MeiTuan(Tickets tickets){
        this.tickets = tickets;
    }

    /***
     * 该方法是代理类在执行时候调用的方法
     * @param proxy 代理类本身(这里就是美团)
     * @param method 代理类调用的具体方法 (我们调用的卖票所以这里就是卖票方法、通俗点就是你调用什么方法,这里就是什么方法)
     * @param args 方法参数 上面方法对应的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("加速包开始抢票");
        //这里是12306原本的卖票,但是是使用反射进行调用
        Object invoke = method.invoke(tickets, args);
        System.out.println("加速包抢票成功");
        return invoke;
    }
}
           

从这一步开始就一下变的面目全非了,代理类去实现

InvocationHandler

接口,重写

invoke

方法。

public class Test {
    public static void main(String[] args) {
        //创建原始的12306类和代理类
        Tickets demo12306 = new Demo12306();
        MeiTuan proxy = new MeiTuan(demo12306);
        /**
         * 美团对12306进行代理,增加额外的抢票功能
         * 这里通过Proxy.newProxyInstance方法在运行的时候动态创建代理类
         * 这里有三个参数
         * ClassLoader loader,  类加载器,这里主要是选择代理的类加载器
         * lass<?>[] interfaces, 被代理类的所有接口
         * InvocationHandler h  代理类
         */
        Tickets meiTuan = (Tickets) Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
                demo12306.getClass().getInterfaces(),
                proxy);
        /**
         * 打印结果:
         * 加速包开始抢票
         * 卖票
         * 加速包抢票成功
         */
        meiTuan.sell();
    }
}
           

最终代理对象是通过

Proxy.newProxyInstance

创建得出的。

其实到这里你只要跟着案例把代码敲出来,就已经实现了动态代理。但是你肯定会有个疑问以下为什么用到这个方法,并且参数又是什么意思?

首先是代理类重写的invoke方法,这里有三个参数。我们回顾一下静态代理,静态代理中我们显式的实现了所有被代理类实现的接口,所以我们有被代理类的所有方法,可能是多个。那么在增强功能的时候,我们就需要每一个方法进行增强,耗时耗力。但是我们把所有方法抽象成

Method

,在Method前后进行增强不就同时增强了所有方法吗。

设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

这里的第二个和第三个参数就比较明朗了,既然我把所有方法抽象成

Method

,那么自然需要反射调用具体的方法。

那么第一个参数到底是什么呢?这个其实是代理类本身,它在调用不同的方法的时候把代理类本身也就是this传递了进来,为什么要传递代理类呢。

  1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。
  2. 可以将代理对象返回以进行连续调用

当接口中方法返回值是接口类型的时候,可以在invoke方法中返回代理对象,这样在使用代理对象的时候可以进行链式调用,具体实例自行尝试。

Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
                	   demo12306.getClass().getInterfaces(),
                       proxy);
           

这里第一个参数就是类加载器,他的作用是用来加载代理对象的,因为我们的代理类是在内存中生成的,那么如果我们在内存中生成用了额外的加密手段,那么我们就要自定义类加载器来进行加载。但是这里jdk就是正常的生成内存中的.class文件,所以我们用正常的application加载器就可以了。

第二个参数就是被代理类的所有接口,第三个参数就是

InvocationHandler

,其实就是代理类在执行对应方法的时候要调用的方法。

手撕jdk动态代理源码

这里介绍几个关键的方法

设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

首先是Proxy类的719行,查找或生成代理类

设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

然后是Proxy类的639行,这里是具体在内存中生成.class的方法

进入到该方法后第二行

final byte[] classFile = gen.generateClassFile();

点击该方法进入

设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

这里是ProxyGenerator类的第427行,这里详细介绍了实现步骤仔细看注释

设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)
设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)
设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

第三步就开始在jvm中拼接代理类,这时候是.class文件,最后会使用类加载器把.class文件加载到jvm中就获得了Class对象

设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

然后我们在回到Proxy类的719行往下看,这不就是反射生成对象吗。所以我们就拿到了代理对象。

这里.class文件由于在jvm中我们看不到,这里其实可以把内存中的.class输出到文件中然后进行反编译。

public class Test2 {
    public static void main(String[] args) throws IOException {
        byte[] data = ProxyGenerator.generateProxyClass("$Proxy0.class", new Class[]{Tickets.class});
        FileOutputStream out = new FileOutputStream("$Proxy0.class");
        out.write(data);
        out.close();
    }
}
           
设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

这时候idea目录下已经多出了这个文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package $Proxy0;

import com.cstor.设计模式.代理模式.静态.Tickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class class extends Proxy implements Tickets {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public class(InvocationHandler var1) throws  {
        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);
        }
    }

    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 void sell() throws  {
        try {
            super.h.invoke(this, m3, (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);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.cstor.设计模式.代理模式.静态.Tickets").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
           

细心的朋友可以看到这里真实的代理类里面的属性是每个方法,不过在原有接口的方法上多出了hashCode、toString、equals。然后在方法中调用的

InvocationHandler

中的invoke方法,传递对应的

Method

自己实现动态代理

其实到这里我们就发现了,代理类其实就是把方法抽象为属性,然后依旧是实现接口,然后重写接口的方法,只不过方法中调用的是

InvocationHandler

中的invoke方法。

这里我们可以自己实现编写动态代理。

在手写动态代理前,先理清楚思路。

设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

1.重写InvocationHandler接口

2.重写类加载器,可以进行文件的二进制加载

3.重写Proxy.newProxyInstance方法帮助我们创建.class文件,并且使用类加载器加载,用反射创建实例。

只不过我们的Proxy.newProxyInstance方法采用手动拼接方式生成类文件然后编译生成.class文件,所以具有不通用性,所以我们还是按照之前的卖票来进行编写。

/**
 * 1.定义卖票接口
 */
public interface Tickets {
    void sell() throws NoSuchMethodException;
}

           
/**
 * 2、12306类
 */
public class Demo12306 implements Tickets {
    @Override
    public void sell() {
        System.out.println("卖票");
    }
}
           
/**
 * 3、重写InvocationHandler接口
 *   这里不把数组作为参数,方便拼接java文件
 */
public interface MyInvocationHandler {

    Object invoke(Object proxy, Method method, Object args);

}
           
/**
 * 4、编写自定义类加载器
 */
public class MyClassLoader extends ClassLoader {

    private File dir;

    //需要加载的.class文件路径
    public MyClassLoader(String path) {
        dir = new File(path);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        /**
         * 1.获取到该.class文件
         * 2.写成流
         * 3.使用defineClass加载类
         */
        File clazzFile = new File(dir, name + ".class");
        if (clazzFile.exists()) {
            try {
                FileInputStream input = new FileInputStream(clazzFile);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len;
                while ((len = input.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);
                }
                return defineClass("com.demo." + name,
                        baos.toByteArray(),
                        0,
                        baos.size());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return super.findClass(name);
    }
}
           
/**
 * 自定义的动态代理Proxy类
 */
public class MyProxy {


    /**
     * 这里第二个参数没有做成数组是因为偷懒,也就是只能实现一个接口,有兴趣的可以改为数组
     */
    public static Object newProxyInstance(MyClassLoader loader, Class interfaces, MyInvocationHandler handler) {
        try {
            //把java文件写入到本项目com.emo包下
            String proxyClass = getClassString(interfaces);
            String filePathName = "src/com/demo/$Proxy0.java";
            File file = new File(filePathName);
            FileWriter fw = new FileWriter(file);
            fw.write(proxyClass);
            fw.flush();
            fw.close();

            //编译上面生成的java文件生成.class
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,
                    null, null);
            Iterable javaFileObjects = fileManager.getJavaFileObjects(filePathName);
            CompilationTask task = compiler.getTask(null, fileManager, null, null, null, javaFileObjects);
            task.call();
            fileManager.close();

            //通过类加载器加载.class文件,使用反射创建对象
            Class<?> proxy0Clazz = loader.findClass("$Proxy0");
            Constructor<?> constructor = proxy0Clazz.getConstructor(MyInvocationHandler.class);
            Object instance = constructor.newInstance(handler);
            return instance;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }


    static String rt = "\r\n";
    /**
     * 手动拼接代理类
     */
    private static String getClassString(Class interfaces) {
        //01.使用拼凑字符串的方式将内存中的代理类拼出来
        return "package com.demo;" + rt +
                "import java.lang.reflect.Method;" + rt +
                "public class $Proxy0 implements " + interfaces.getName() + "{" + rt +
                "MyInvocationHandler h;" + rt +
                "public $Proxy0(MyInvocationHandler h) {" + rt +
                "this.h = h;" + rt +
                "}"
                + getMethodString(interfaces) + rt + "}";
    }


    private static String getMethodString(Class interfaces) {
        String proxyMe = "";
        for (Method method : interfaces.getMethods()) {
            proxyMe += "public void " + method.getName() + "() throws NoSuchMethodException {" + rt +
                    "Method md = " + interfaces.getName() + ".class.getMethod(\"" + method.getName() + "\",new Class[]{});" + rt +
                    "this.h.invoke(this,md,null);" + rt +
                    "}" + rt;
        }
        return proxyMe;
    }
}
           
public class MeiTuan implements MyInvocationHandler {

    private Tickets tickets;

    MeiTuan(Tickets tickets){
        this.tickets = tickets;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object args) {
        System.out.println("加速包开始抢票");
        try {
            tickets.sell();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        System.out.println("加速包抢票成功");
        return null;
    }
}
           
public class Test {
    public static void main(String[] args) throws NoSuchMethodException {
        Tickets demo12306 = new Demo12306();
        MeiTuan proxy = new MeiTuan(demo12306);
        Tickets meiTuan = (Tickets) MyProxy.newProxyInstance(new MyClassLoader("src/com/demo"),
                com.demo.Tickets.class,
                proxy);
        /**
         * 打印结果:
         * 加速包开始抢票
         * 卖票
         * 加速包抢票成功
         */
        meiTuan.sell();
    }
}
           

这里发现动态代理生效了,这里只是给大家做个小例子,实际过程中代码编写不是很完美不用纠结,体会过程最重要。

这个时候在我们的包下也生成了java类和.class文件

package com.demo;

import java.lang.reflect.Method;

public class $Proxy0 implements com.demo.Tickets {
    MyInvocationHandler h;

    public $Proxy0(MyInvocationHandler h) {
        this.h = h;
    }

    public void sell() throws NoSuchMethodException {
        Method md = com.demo.Tickets.class.getMethod("sell", new Class[]{});
        this.h.invoke(this, md, null);
    }

}
           
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.demo;

import java.lang.reflect.Method;

public class $Proxy0 implements Tickets {
    MyInvocationHandler h;

    public $Proxy0(MyInvocationHandler var1) {
        this.h = var1;
    }

    public void sell() throws NoSuchMethodException {
        Method var1 = Tickets.class.getMethod("sell");
        this.h.invoke(this, var1, (Object)null);
    }
}
           

cglib实现动态代理

jdk是通过接口实现代理,而cglib则是通过继承实现代理,对于final类无法代理

底层是采用字节码方式实现的

还是使用原来的例子

maven项目添加依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
           
public abstract class Tickets {
    abstract void sell();
}
           
public class Demo12306 extends Tickets {
    @Override
    public void sell() {
        System.out.println("卖票");
    }
}
           
public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        //设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    //实现MethodInterceptor接口方法
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        System.out.println("加速包开始抢票");
        //通过代理类调用父类中的方法
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("加速包抢票成功");
        return result;
    }
}
           
public class Test {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        Tickets tickets = (Tickets) proxy.getProxy(Demo12306.class);
        tickets.sell();
    }
}
           

最终结果和jdk的动态代理结果一致

参考博客:

https://blog.csdn.net/tanggao1314/article/details/50450459

https://www.cnblogs.com/9513-/p/8432276.html

https://www.cnblogs.com/yaphetsfang/articles/11274083.html

https://www.cnblogs.com/suizhikuo/p/13941272.html