基于JDK的动态代理技术详解

虽然对于Spring的基本思想Aop是基于动态代理和CGlib这一点很早就有所认识,但是什么是动态代理却不甚清楚。为了对Spring加深理解,我觉得好好学习一下java的动态代理是非常有必要的。

静态代理

在学习动态代理之前我先花一点时间了解一下静态代理,从静态代理出发了解代理到底是怎么一回事,以及了解静态代理的局限性,进而明白为什么要发展及使用动态代理技术。

相信使用过Spring框架的同学都知道Spring利用Aop完成声明式事务管理以及其他的代理增强,也就是在方法执行前后加上一些譬如时间、日志、权限控制等。假如现在我们从比较复杂的Spring Aop中跳出来,那么,有什么简单的方法能够对我们的方法进行增强呢?

继承实现

最简单的方法就是继承,子类继承父类并重写父类的方法,在重写的过程总就可以对原有的方法进行增强。下面代码就是这种代理增强思想的实现。

package cn.proxy;

public class Tank implements Moveable{

    @Override
    public void move() {
        System.out.println("tank moveing...");
    }

}

// son
package cn.proxy;

public class LogProxyTank extends Tank {

    public void move() {
        System.out.println("tank start...");
        super.move();
        System.out.println("tank stop...");
    }

}

可以看见,通过重写父类方法可以非常方便实现代理增强,但是就一个日志功能的代理增强,假如涉及到100个类呢?那就要为100个类实现子类。这还不算麻烦,假如涉及到日志、时间、权限等多个功能的增强的时候,先时间后日志和先日志后时间可不是一回事,那么就要分别实现两个代理的子类。想一想这其中涉及到许多增强的功能和许多被代理类时,就会造成代理类爆炸。显然这种方式是很不灵活的。

聚合实现

被代理类和代理类实现同一个接口,同时代理类不再与被代理类存在继承关系,而是代理类包含一个被代理类类型的成员变量。

package cn.proxy;

public class LogProxy implements Moveable {

    private Moveable m;

    public LogProxy(Moveable m) {
        super();
        this.m = m;
    }

    @Override
    public void move() throws Exception {
        System.out.println("moveable start...");
        m.move();
        System.out.println("moveable stop...");
    }

}

可以看见,相较于继承方式,聚合的方式实现代理增强,通过传入不同的被代理类,可以实现对不同的被代理进行增强,但是这种方式实现不同的增强还是需要写不同的代理类,灵活性上还不是很完美。

动态代理

我们都知道,一个类要经过编写、编译、加载进JVM最终才能进行实例化。通常这些工作是分开进行的。那么有没有可能将这些过程封装到一个方法里面呢?

答案是可以的。现在大致讲一下步骤:

1. 首先将可以将源码保存成字符串,并将增强的代码加进去,然后通过Java IO技术将其写入到一个java文件中

2. 使用JavaCompiler进行编译生成.class的二进制文件

3. 使用一个类加载器(这里使用URLClassLoader)将二进制文件加载进内存

4. 实例化代理对象

基本思路就是这样,现在要实现灵活的java动态代理,问题就是如何动态的确定对什么类进行代理,进行怎样的代理增强。关键就在步骤1,源码字符串不能写死,而应该动态生成。那么这个方法的参数就需要有被代理对象和增强的逻辑。

被代理类很容易理解,就是要通过这个被代理类知道要对哪些方法进行代理增强,通过反射就可以获取被代理类非所有方法。还有一个重要的接口是InvocationHandler,这个接口有一个方法invoke,在这个方法中可以定义代理逻辑以及调用被代理类的实例对象的欲代理方法来完成主要的方法逻辑,因为代理类是不能完成被代理类的方法逻辑的。就像歌星(被代理类)的经纪人(代理类)可以帮助歌星去接演出、安排行程,但是不能代替歌星去唱歌一样。

为了进一步了解Java动态代理,可以对JDK中的Proxy类进行一个简单的模拟,代码如下:

package cn.proxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class Proxy {

    public static Object newProxyInstance(Class interfacee, InvocationHandler handler) throws Exception {

        String rt = "\r\n";
        // 源码
        String methodStr = "";
        Method[] methods = interfacee.getMethods();
        for (Method m : methods) {
            methodStr +=
                    rt +
            "    @Override" + rt +
            "    public void " + m.getName() + "() throws Exception {" + rt +
            "        Method md = " + interfacee.getName()+ ".class.getMethod(\"" + m.getName() + "\");" + rt +
            "        handler.invoke(this, md);" + rt +
            "    }" + rt;
        }

        String src =
        "package cn.proxy;" + rt + rt +

        "import java.lang.reflect.Method;" + rt +
        "import cn.proxy.InvocationHandler;" + rt + rt +

        "public class TankTimeProxy implements " +  interfacee.getName() + "{" + rt +

        "    private InvocationHandler handler;" + rt + rt +

        "    public TankTimeProxy(InvocationHandler handler) {" + rt +
        "        super();" + rt +
        "        this.handler = handler;" + rt +
        "    }" + rt +

        methodStr +

        "}";

        // 生成一个java源码
        String fileName = System.getProperty("user.dir") + "/src/cn/proxy/TankTimeProxy.java";
        File file = new File(fileName);
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(src);
        fileWriter.flush();
        fileWriter.close();

        // 使用jdk编译api动态编译
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjects(fileName);
        CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
        task.call();
        fileManager.close();

        // 将二进制文件加载进入内存
        URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir") + "src")};
        URLClassLoader cl = new URLClassLoader(urls);
        Class<?> clazz = cl.loadClass("cn.proxy.TankTimeProxy");
        System.out.println(clazz);

        // 实例化新对象
        Constructor<?> constructor = clazz.getConstructor(InvocationHandler.class);
        Object m = constructor.newInstance(handler);

        return m;
    }
}

仔细分析一下上面的代码可以看出,基本思路就是上面所述的四步走。通过反射技术来动态解析传入的被代理类,获取欲代理的方法,而每个方法的具体实现交由InvocationHandler的具体实现类来完成,其invoke方法完成代理增强并调用被代理类的相应的被代理方法。

为了对InvocationHandler有一个直观的了解,写一个简单的InvocationHandler接口以及实现类,具体代码如下:

package cn.proxy;

import java.lang.reflect.Method;

public interface InvocationHandler {

    void invoke(Object o, Method m);

}
package cn.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

    // 被代理类
    private Tank t;

    public TimeHandler(Tank t) {
        this.t = t;
    }

    @Override
    public void invoke(Object o, Method m) {
        long start = System.currentTimeMillis();
        System.out.println("start time: " + start);
        try {
            m.invoke(t, new Object[]{});
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("end time: " + end);
    }

}

InvocationHandler的实现类中有一个要注意:要有一个被代理类类型的成员变量,要通过这个变量才能过调用被代理类的相应方法来完成主要的方法功能。通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是InvocationHandler类型,也不是定义的那接口的类型,而是在运行是动态生成的一个对象。

总结

简单理解,代理模式就是在一个方法执行前后加入代理增强的代码。我们不断探索新的方式,就是要实现更好的灵活性,可以对任意的被代理类实现任意的代理增强。动态代理技术通过传入被代理类(这就可以任意传入),反射解析这个类来获得欲代理的方法。通过传入InvocationHandler的实现类来实现方法的代理增强,可以根据传入的实现类的不同来实现任意的代理,而且这个实现类可以做到复用,就像机械零件一样,自由组装实现不同形式的代理组合。Java具体的代理类Proxy与本文中模仿的代理类有些许不同,包括可以传入方法参数实现对有参方法的代理,代理类被命名为$proxy1等等。但这些是细节问题,主要的思想还是一样的。

===========================================================================================================================

本文只是我现阶段的学习心得总结而成,内容可能不够深入,由于水平所限,不保证所有内容正确,欢迎有同学在评论中指正,万分感谢!

        保证每一个字的原创性!

        我是一个程序员,我所能做的就是每一天都在进步,面对技术保持一颗赤子之心,这是我人生现阶段全部的追求。"Stay hungry, stay foolish"!

===========================================================================================================================

原文地址:https://www.cnblogs.com/sunmin/p/9322807.html

时间: 2024-10-02 18:28:35

基于JDK的动态代理技术详解的相关文章

好程序员Java教程Java动态代理机制详解

好程序员Java教程Java动态代理机制详解:在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface).另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的.首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的: InvocationHandler: 1InvocationHandler is the interface implemented by the invocation handle

Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

作者:亦山 推荐:hh375的图书馆 class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象: class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的.具体class文件是怎样组织类信息的,可以参考 此博文:深入理解Java Class文件格式系列.或者

Java 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象: class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的.具体class文件是怎样组织类信息的,可以参考 此博文:深入理解Java Class文件格式系列.或者是Java虚拟机规范. 下面通过一段代

代理设计模式之静态代理与动态代理(超..)详解

在学习Spring框架的时候,有一个重要的思想就是AOP,面向切面编程,利用AOP的思想结合Spring的一些API可以实现核心业务与辅助业务的分离,即可以在执行核心业务时,将一些辅助的业务加进来,而辅助业务(如日志,权限控制等)一般是一些公共业务,这样就实现了两者的分离,使得核心业务的代码更加纯粹,而且辅助业务也能得到复用,这一篇笔记是当时学习spring的时候写的,使用springAPI以及自定义类 实现AOP的一个例子 ,.AOP底层就是通过动态代理来实现的,最近专门学习了一下代理模式,反

java的动态代理机制详解

在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于 Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是 java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾. 在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface).另一个则是 Proxy(Cla

(转)java的动态代理机制详解

原文出自:http://www.cnblogs.com/xiaoluo501395377/p/3383130.html 在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾. 在java的动态代理机制中,有

[转载] java的动态代理机制详解

转载自http://www.cnblogs.com/xiaoluo501395377/p/3383130.html 代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务. 按照代理的创建时期,代理类可以分为两种. 静态代理

Java动态代理 深度详解

代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 今天我将用非常简单易懂的例子向大家介绍动态代理的两种类型,接着重点介绍动态代理的两种实现方式(Java 动态代理和 CGLib 动态代理),最后深入剖析这两种实现方式的异同,最后说说动态代理在我们周边框架中的应用. 在开始之前,我们先假设这样一个场景:有一个蛋糕店,它们都是使用蛋糕机来做蛋糕的,而且不同种类的蛋糕

java Proxy InvocationHandler 动态代理实现详解

spring 两大思想,其一是IOC,其二就是AOP..而AOP的原理就是java 的动态代理机制.这里主要记录java 动态代理的实现及相关类的说明. java  动态代理机制依赖于InvocationHandler接口.Proxy类.这是java 实现动态代理必须用到的. 一.InvocationHandler:该接口中只有一个方法 public Object invoke(Object proxy, Method method, Object[] args)throws Throwable