深刻理解Java动态代理及手动实现

前言

文章目录以下,便于快速索引java

1、什么叫代理?git

2、什么叫动态代理?github

3、动态代理有什么优点?数组

4、动态代理的JDK实现原理缓存

    4.1核心类/接口ide

    4.2 代理类$Proxy0解析函数

    4.3 动态代理的经典使用工具

5、手写代码模拟JDK动态代理学习

6、参考资料测试

先将本身总结的Java动态代理UML图放在前面,用相同的颜色表明同一个或本质上相同的类与方法,便于你们理解。接口UserService是咱们本身定义的接口——接口中有方法execute();被代理类是实现了该接口的具体类;代理类则是存在于内存中,也实现了UserService接口的类,在内存中,该类名为$Proxy0。

    下面进入正文,今天想跟你们分享一下我本身在学习Java动态代理过程当中的理解与收获,最后用本身的代码模拟实现JDK的动态代理。

1、什么叫代理?

    这个概念不是我想表达的重点,因此这里我引用别人一篇博文的例子

    “动态代理技术就是用来产生一个对象的代理对象的。在开发中为何须要为一个对象产生代理对象呢?
    举一个现实生活中的例子:歌星或者明星都有一个本身的经纪人,这个经纪人就是他们的代理人,当咱们须要找明星表演时,不能直接找到该明星,只能是找明星的代理人。好比刘德华在现实生活中很是有名,会唱歌,会跳舞,会拍戏,刘德华在没有出名以前,咱们能够直接找他唱歌,跳舞,拍戏,刘德华出名以后,他干的第一件事就是找一个经纪人,这个经纪人就是刘德华的代理人(代理),当咱们须要找刘德华表演时,不能直接找到刘德华了(刘德华说,你找我代理人商谈具体事宜吧!),只能是找刘德华的代理人,所以刘德华这个代理人存在的价值就是拦截咱们对刘德华的直接访问!
    这个现实中的例子和咱们在开发中是同样的,咱们在开发中之因此要产生一个对象的代理对象,主要用于拦截对真实业务对象的访问。那么代理对象应该具备什么方法呢?代理对象应该具备和目标对象相同的方法

    因此在这里明确代理对象的两个概念:

    一、代理对象存在的价值主要用于拦截对真实业务对象的访问。(事务的开启与关闭)

    二、代理对象应该具备和目标对象(真实业务对象)相同的方法。(要求实现同一接口)

    刘德华(真实业务对象)会唱歌,会跳舞,会拍戏,咱们如今不能直接找他唱歌,跳舞,拍戏了,只能找他的代理人(代理对象)唱歌,跳舞,拍戏,一我的要想成为刘德华的代理人,那么他必须具备和刘德华同样的行为(会唱歌,会跳舞,会拍戏),刘德华有什么方法,他(代理人)就要有什么方法,咱们找刘德华的代理人唱歌,跳舞,拍戏,可是代理人不是真的懂得唱歌,跳舞,拍戏的,真正懂得唱歌,跳舞,拍戏的是刘德华,在现实中的例子就是咱们要找刘德华唱歌,跳舞,拍戏,那么只能先找他的经纪人,交钱给他的经纪人,而后经纪人再让刘德华去唱歌,跳舞,拍戏。”

2、什么叫动态代理?

    代理类在程序运行时建立的代理方式被成为动态代理。 也就是说,这种状况下,代理类并非在Java代码中定义的,而是在运行时根据咱们在Java代码中的“指示”动态生成的。

3、动态代理有什么优点?

    经过使用代理,一般有两个优势:

    优势一:能够隐藏被代理类的实现;

    优势二:能够实现客户与被代理类间的解耦,在不修改被代理类代码的状况下可以作一些额外的处理。

4、动态代理的JDK实现原理

    在java的动态代理机制中,有两个重要的类和接口,一个是 InvocationHandler(Interface)、另外一个则是 Proxy(Class),这一个类和接口是实现咱们动态代理所必须用到的。固然,我还想带各位深刻了解一下存在于JVM中神秘的动态代理类——$Proxy0。最后再给出java动态代理的经典使用流程。

4.1 核心类/接口

4.1.1 java.lang.reflect.Proxy类

    Proxy类提供了用于建立动态代理类和实例的静态方法,它也是由这些方法建立的全部动态代理类的超类。

public class Proxy extends Object implements Serializable

    (1)Proxy的主要静态变量

// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map loaderToCache = new WeakHashMap(); 
 
// 标记:用于标记一个动态代理类正在被建立中
private static Object pendingGenerationMarker = new Object(); 
 
// 同步表:记录已经被建立的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); 
 
// 关联的调用处理器引用
protected InvocationHandler h;

    (2)Proxy的构造方法

// 因为 Proxy 内部从不直接调用构造函数,因此 private 类型意味着禁止任何调用
private Proxy() {} 
 
// 因为 Proxy 内部从不直接调用构造函数,因此 protected 意味着只有子类能够调用
protected Proxy(InvocationHandler h) {this.h = h;}

    (3)Proxy静态方法newProxyInstance

public static Object newProxyInstance(ClassLoader loader, 
            Class<?>[] interfaces, 
            InvocationHandler h) 
            throws IllegalArgumentException { 
 
    // 检查 h 不为空,不然抛异常
    if (h == null) { 
        throw new NullPointerException(); 
    } 
 
    // 得到与制定类装载器和一组接口相关的代理类类型对象
    /*
     * Look up or generate the designated proxy class.
     */
        Class<?> cl = getProxyClass0(loader, interfaces); 
 
    // 经过反射获取构造函数对象并生成代理类实例
    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            SecurityManager sm = System.getSecurityManager();
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
                return newInstance(cons, ih);
            }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    } 
    }
 
private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
        try {
            return cons.newInstance(new Object[] {h} );
        } catch (IllegalAccessException e) {
            throw new InternalError(e.toString());
        } catch (InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString());
            }
        }
    }

    这个方法的做用就是获得一个动态的代理对象,其接收三个参数,咱们来看看这三个参数所表明的含义。

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
 
//loader:  一个ClassLoader对象,定义了由哪一个ClassLoader对象来对生成的代理对象进行加载
//interfaces:  一个Interface对象的数组,表示的是我将要给我须要代理的对象提供一组什么接口,若是我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
//h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪个InvocationHandler对象上

    官方JDK文档给出了使用该方法建立一个动态代理类的模板:

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

    动态代理真正的关键是在 getProxyClass0 方法。这个咱们在后面手动实现模拟JDK动态代码的时候能够看到。

4.1.2  java.lang.reflect.InvocationHandler接口

    每个动态代理类中都有一个实现了InvocationHandler这个接口(代码中的中介)的实例handler类(即前言中的MyInvocationHandler类),当咱们经过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke(对方法的加强就写在这里面) 方法来进行调用。

import java.lang.reflect.Method;
 
public interface MyInvocationHandler {
 
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
 
}

    咱们看到这个方法一共接受三个参数,那么这三个参数分别表明什么呢?

Object invoke(Object proxy, Method method, Object[] args) throws Throwable
//proxy:  内存中的代理实例 $proxy0
//method:  内存代理实例中class.forName("被代理类").getMethod("目标方法") 即被代理的类的方法对象
//args:  指代的是调用真实对象某个方法时接受的参数

    咱们怎么确认上面几个参数就是这个意思呢?那就看看下面这节吧~

4.2 代理类$Proxy0解析

(1)为何内存中的动态代理类叫作$Proxy0?

    这个能够经过断点查看到~

(2)怎么拿到动态代理类的字节码文件?

public static void createProxyFile() throws IOException {
        byte[] generateProxyClass = ProxyGenerator.generateProxyClass("$Proxy0", new Class<?>[]{UserService.class});
 
        FileOutputStream outputStream = new FileOutputStream("$Proxy0.class");
        outputStream.write(generateProxyClass);
        outputStream.close();
    }

    在4.3 (5)中最终输出动态代理类执行结果后能够调用上面的方法,便可获得字节码文件

(3)动态代理类$Proxy0字节码文件解析

    很清楚,动态代理类实现了UserService接口,继承了Proxy类

    首先咱们看左边为动态代理类的代码结构。

 

  • 构造方法
//$Proxy类
public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
}
//父类Proxy
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
}

    能够看到动态代理类包含一个有参构造方法,内部调用了父类方法,其实也就是完成了调用处理器 InvocationHandler的实例化。该构造方法你们可得注意,在4.3(6)中提到的动态代理建立流程第3步,使用JAVA反射机制获取动态代理对象时:

Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

    就是为了调用本构造方法。

 

  • Object方法

    你们能够看到动态代理类有m0~m3四个方法,这四个方法分别是什么呢?

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("proxy.test.UserService").getMethod("execute");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    能够看到,经过java反射机制,除了m3是UserService接口方法的实现之外,其余方法都是Object的方法。那这些方法有什么特色呢?

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

    其实核心就一句话 h.invoke() 也就是说动态代理类中的方法都使用了java反射机制去调用调用处理器 InvocationHandler中的方法。

 

  • 接口方法

    接口方法与Object方法同样,内部就一句话。咱们注意到,invoke方法传入3个参数,这个invoke方法也就是4.1.2中咱们提到的InvocationHandler接口的 invoke方法,那理解3个参数的意义也就很简单了。

    参数1传入的为this——即$Proxy0自己,因此是内存中的动态代理对象

   参数2传入的为m3——也就是proxy.test.UserService中名为execute的方法,即接口中的方法。而这也彻底证明了以前在“1、什么是代理?”部分提到的第二个特色——代理对象应该具备和目标对象(真实业务对象)相同的方法。(要求实现同一接口)

    参数3传入的为null——由于execute方法没有参数,因此为空。

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

4.3 动态代理的经典使用

(1)理解UML图

    再把本图放上来暖场~JAVA中动态代理的使用基本就是如上图所示。下图是咱们程序的目录结构,下面咱们用代码来实现。

(2)定义对象的行为接口UserService

package proxy.test;
 
public interface UserService {
 
    public String execute() throws Throwable ;
}

(3)定义目标业务对象类UserServiceImpl

package proxy.test;
 
public class UserServiceImpl implements UserService{
    @Override
    public String execute() throws Throwable {
        System.out.println("step 2 执行方法啦!!");
        return "step 2 执行方法啦!!";
    }
}

(4)自定义“调用处理程序”——MyInvocationHandler

package proxy.test;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
public class MyInvocationHandler implements InvocationHandler {
 
    private UserService userService;
 
    public MyInvocationHandler(UserService userService) {
        this.userService = userService;
    }
    
    /*
    * @proxy 内存中的代理实例 $proxy0
    * @method 内存代理实例中class.forName("被代理类").getMethod("目标方法") 即被代理的类的方法对象
    * @args 方法参数
    * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();   //事务开启
 
        method.invoke(userService, args);
 
        after();    //事务关闭
 
        return "成功了";
    }
 
    private void before() {
        System.out.println("事务开启!");
    }
 
    private void after() {
        System.out.println("事务关闭");
    }
}

    动态代理最具魅力所在——也就是上面代码中事务开启和关闭部分,总结起来就是:实现了方法的加强,让你能够在不修改源码的状况下,加强一些方法,在方法执行先后作任何你想作的事情(甚至根本不去执行这个方法),由于在InvocationHandler的invoke方法中,你能够直接获取正在调用方法对应的Method对象,具体应用的话,好比能够添加调用日志,作事务控制等。

(5)代理类生成并测试代码

package proxy.test;
import java.lang.reflect.Proxy;
 
public class MyTest {
 
    public static void main(String[] args) throws Throwable {
        System.out.println("---------------JDK动态代理----------------");
        UserService userService = (UserService) Proxy.newProxyInstance(MyTest.class.getClassLoader(),
                new Class<?>[]{UserService.class},
                new MyInvocationHandler(new UserServiceImpl()));
 
        userService.execute();
    }
}

    上面的代码也就是4.1.1(3)中提到的JDK文档给出的建立代理类方式。经过强制类型转换并执行相应方法,获得输出以下:

(6)动态代理建立流程总结

 

    动态代理的建立是基于java反射的,一个典型的动态代理建立对象过程可分为如下四个步骤:

  • 1.经过实现InvocationHandler接口建立本身的调用处理器 

        InvocationHandler handler = new InvocationHandlerImpl(...);

  • 2.经过为Proxy类指定ClassLoader对象和一组interface建立动态代理类

        Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

  • 3.经过反射机制获取动态代理类$Proxy0的构造函数,其参数类型是调用处理器接口类型

        Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

  • 4.经过构造函数建立代理类实例,此时需将调用处理器对象做为参数被传入

        Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

    为了简化对象建立过程,Proxy类中的newInstance方法封装了2~4,只需两步便可完成代理对象的建立。生成的动态代理类$Proxy0继承Proxy类实现UserService接口,实现的UserService的方法实际调用调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))。

(7)JDK动态代理的不足

    诚然,Proxy已经设计得很是优美,可是仍是有一点点小小的遗憾之处,那就是它始终没法摆脱仅支持interface代理的桎梏,由于它的设计注定了这个遗憾。摆脱这个遗憾就得依靠CGLIB(此处等待下文)。

5、手写代码模拟JDK动态代理

5.1 原理解析

    手撸代码模拟JDK动态代理,其实也就是对java.lang.reflect.Proxy类的功能进行模拟。其步骤主要有如下四步:

(1)建立代理类的源码; 

    拿到被代理类(如UserServiceImpl)实现的接口类对象(如UserService.class),遍历里面的方法(如execute()方法),以字符串的形式拼凑出代理类源码(动态代理类与被代理类实现同一接口在此体现),将代理类的源码写到本地java文件

(2)将源码进行编译成字节码; 

    读取源码,编译java文件,获得.class字节码文件(的路径)

(3)将字节码加载到内存; 

(4)实例化代理类对象并返回给调用者。

    其中步骤(1)、(2)、(3)就是咱们自定义的Proxy类所要完成的功能,类的结构以下图;步骤(4)是咱们功能代码/测试代码要实现的。下面咱们对每一步进行解析。

5.2 建立代理类的源码

项目实现源码已经上传,欢迎点击下载~

(1)使用字符串拼凑动态代理对象的java源码

//用字符串的形式拼凑出内存里的代理类
    static String rt = "\r\n";
    private static String get$Proxy0(Class<?> interfaces) {
 
        Method[] methods = interfaces.getMethods();
 
        String proxyClass = "package proxy;" + 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(methods, interfaces)
                + rt + "}";
        return proxyClass;
    }
 
    private static String getMethodString(Method[] methods, Class<?> interfaces) {
        String proxyMethod = "";
 
        for (Method method : methods) {
            proxyMethod += "public String " + method.getName()
                    + "() throws Throwable {" + rt + "Method md = "
                    + interfaces.getName() + ".class.getMethod(\"" + method.getName()
                    + "\",new Class[]{});" + rt
                    + "return (String)this.h.invoke(this, md, null);" + rt + "}" + rt;
        }
 
        return proxyMethod;
    }

    上面这段代码所模拟的具体代码实如今JDK中是在jar包中的,其获得的结果就是生成了$Proxy0.java

package proxy;
import java.lang.reflect.Method;
 
public class $Proxy0 implements proxy.test.UserService{
    MyInvocationHandler h;
    public $Proxy0(MyInvocationHandler h) {
        this.h = h;
    }
    
    public String execute() throws Throwable {
        Method md = proxy.test.UserService.class.getMethod("execute",new Class[]{});
        return (String)this.h.invoke(this, md, null);
    }
}

(2)将源码写入本地文件

private static void outputFile(String proxyClass, String path) {
        File f = new File(path);
        try {
            FileWriter fw = new FileWriter(f);
            fw.write(proxyClass);
            fw.flush();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

5.3 将源码进行编译成字节码

private static void compileJavaFile(String fileName) {
        try {
            //得到当前系统中的编译器
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            //得到文件管理者
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> fileObjects = manager.getJavaFileObjects(fileName);
            //编译任务
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, fileObjects);
            //开始编译,执行完可在当前目录下看到.class文件
            task.call();
            //关闭文件管理者
            manager.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    这段代码用的都是JAVA自带的编译工具,不作过多解释。

5.4 将字节码加载到内存

private static Object loadClassToJvm(MyInvocationHandler h) {
        try {
            //使用自定义类加载器
            MyClassLoader loader = new MyClassLoader("C:\\HailinLuo\\CODING\\JAVA\\JavaProgramming\\src\\proxy");
            //获得动态代理类的反射对象
            Class<?> $Proxy0 = loader.findClass("$Proxy0");
            //经过反射机制获取动态代理类$Proxy0的构造函数,其参数类型是调用处理器接口类型
            Constructor<?> constructors = $Proxy0.getConstructor(MyInvocationHandler.class);
            //经过构造函数建立动态代理类实例
            return constructors.newInstance(h);
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    这里使用了自定义的类加载器MyClassLoader 

package proxy;
 
import java.io.*;
 
public class MyClassLoader extends ClassLoader {
    File dir;
    //把文件路径用构造函数传进来
    public MyClassLoader(String path) {
        dir = new File(path);
    }
    /*
     * 本方法就是去加载对应的字节码文件
     * */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //若是文件路径可用
        if (dir != null) {
            File clazzFile = new File(dir, name + ".class");
            //若是字节码文件存在
            if (clazzFile.exists()) {
                //把字节码文件加载到VM
                try {
                    //文件流对接class文件
                    FileInputStream inputStream = new FileInputStream(clazzFile);
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    //将class文件读取到buffer中
                    while ((len = inputStream.read(buffer)) != -1) {
                        //将buffer中的内容读取到baos中的buffer
                        baos.write(buffer, 0, len);
                    }
                    //将buffer中的字节读到内存加载为class
                    return defineClass("proxy." + name, baos.toByteArray(), 0, baos.size());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return super.findClass(name);
    }
}

    也可使用URLClassLoader 加载,以下所示

//load到内存
URL[] urls = new URL[]{new URL("C:\\HailinLuo\\CODING\\JAVA\\JavaProgramming\\src\\proxy")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class cls = urlClassLoader.loadClass("proxy.$Proxy0");

5.5 实例化代理类对象并返回给调用者

UserService service = (UserService) MyProxy.newProxyInstance(MyTest.class.getClassLoader(),
                UserService.class,
                new MyInvocationHandlerImpl(new UserServiceImpl()));
 
        service.execute();

    没什么好说的。。。

5.6 输出结果

    如上图所示,下方红框所示输出结果与JDK动态代理效果一致。说明咱们的模拟是成功的!而上面那个红框中的$Proxy0.java和$Proxy0.class则是5.2(生成源码)与5.3(编译为字节码)执行的结果。

    OK,分享就到这里~

6、参考资料

项目源码已经上传,欢迎点击下载~

http://www.jb51.net/article/86531.htm

https://blog.csdn.net/pangqiandou/article/details/52964066

https://blog.csdn.net/scplove/article/details/52451899

https://www.jianshu.com/p/dbce090d5c3e?1487292535486

https://blog.csdn.net/ljt2724960661/article/details/52507314

相关文章
相关标签/搜索