JDK动态代理的简单实现

Wesley13
• 阅读 371

1. 先理一下动态代理实现的思路:

    实现功能: 自己定义一个类 Proxy, 通过Proxy的静态方法 newProxyInstance(Class intface,InvocationHandler h)返回代理对象, intface: 被代理类的接口对象, h: InvocationHandler的实例对象

    1). 声明一段动态代理类的源码( 动态产生代理类 )

    2). 编译动态代理类的源码( JDK Compiler API ), 产生代理类

    3). 通过 ClassLoader 加载这个代理类, 创建一个代理类的实例对象

    4). return 返回这个代理对象

2. 代码实现: 

为什么代理类的类名为 $Proxy0?

这是因为 Java中动态代理, 生成的代理类的类名就是 $Proxy0, 依葫芦画瓢而已, 

可以用一个动态代理对象 proxy, 来验证:

System.out.println(proxy.getClass().getName());    //输出com.sun.proxy.$Proxy0

public class Proxy {
    /**
     * @param intface
     *            被代理类的接口的类对象
     * @param h
     *            InvocationHandler的实例对象
     * @return proxy 生成的动态代理对象
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T newProxyInstance(Class<T> intface,InvocationHandler h) throws Exception {
        String srcStr = "";    // 代理类$Proxy0的源码, 字符串形式
        String methodStr = "";    // 代理类$Proxy0的所有代理方法, 字符串形式
        String rt  = "\r\n";    // Windows平台下的换行符
        
        // 动态生成代理类$Proxy0的所有代理方法
        for(Method m : intface.getMethods()) {
            methodStr += 
            "    @Override" + rt +
            "    public void " + m.getName() + "() {" + rt +
            "        try {" + rt +
            "            Method method = " + intface.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +
            "            h.invoke(this, method, null);" + rt +    // 暂时只支持无参方法
            "        } catch (Throwable e) { e.printStackTrace(); }" + rt +
            "    }";
        }
        
        // 拼接代理类$Proxy0的源码
        srcStr +=
        "package proxy.my;" + rt +
        "import java.lang.reflect.Method;" + rt +
        "import java.lang.reflect.InvocationHandler;" + rt +
        "public class $Proxy0 implements " + intface.getName() + "{" + rt +
        "    private InvocationHandler h;" + rt +
        "    public $Proxy0(InvocationHandler h) {" + rt +
        "        this.h = h;" + rt +
        "    }" + rt +
        "  "+methodStr + rt + 
        "}";
        
        // 生成代理类的java源文件
        String srcFilePath = System.getProperty("user.dir") + "/bin/proxy/my/$Proxy0.java";
        File srcFile = new File(srcFilePath);
        // 使用commons-io-2.2.jar中的FileUtils, 向一个指定的文件中写入指定的字符串
        FileUtils.writeStringToFile(srcFile, srcStr);
        
        // 编译这个代理类的java源文件
        // 获取编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        // 获取文件管理者
        StandardJavaFileManager fileMgr= compiler.getStandardFileManager(null, null, null);
        // 获取文件
        Iterable<? extends JavaFileObject> compilationUnits = fileMgr.getJavaFileObjects(srcFilePath);
        // 获取编译任务
        CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, compilationUnits);
        // 编译
        task.call();
        
        // 获取代理类的类加载器
        ClassLoader classLoader = Proxy.class.getClassLoader();
        // 加载代理类
        Class<?> clazz = classLoader.loadClass("proxy.my.$Proxy0");
        // 获取代理类的构造器
        Constructor<?> constructor = clazz.getConstructor(InvocationHandler.class);
        // 通过代理类的构造器, 创建一个代理类的实例, 也就是代理对象, 返回代理对象
        T proxy = (T) constructor.newInstance(h);
        return proxy;
    }
}

3. 测试自己实现的 Proxy.newProxyInstance() 方法

先定义一个 ProxyInvocationHandler 类, 该类实现了java.lang.reflect.InvocationHandler接口, 实现invoke()方法

public class ProxyInvocationHandler implements InvocationHandler {
    private Object target;    // 被代理的目标对象

    public ProxyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("来不及解释了, 快上车");
        method.invoke(target, args);    // 调用目标对象的方法
        System.out.println("下车了, 快记住车牌号");
        return null;    // 暂时只支持无参方法
    }
}

测试动态代理

public static void main(String[] args) throws Throwable {
        Moveable car = new Car();
        InvocationHandler h = new ProxyInvocationHandler(car);
        Moveable carProxy = Proxy.newProxyInstance(Moveable.class, h);
        carProxy.move();
}

Console输出:    // 其实我想当个老司机, 天天飙车

来不及解释了, 快上车

飙车中...

下车了, 快记住车牌号

Moveable 接口

public interface Moveable {
    public void move();
}

Moveable 接口的实现类 Car

public class Car implements Moveable {
    @Override
    public void move() {
        System.out.println("飙车中...");
    }
}

生成的代理类 $Proxy0, 其路径: bin/proxy/$Proxy0.java

package proxy;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;
public class $Proxy0 implements proxy.Moveable{
    private InvocationHandler h;
    public $Proxy0(InvocationHandler h) {
        this.h = h;
    }
      @Override
    public void move() {
        try {
            Method method = proxy.Moveable.class.getMethod("move");
            h.invoke(this, method, null);    // 暂时只支持无参方法
        } catch (Throwable e) { e.printStackTrace(); }
    }
}

总结:

    调用 Proxy 实例的方法时, 都会被 InvocationHandler 的实例对象 invoke() 方法所捕获  

why?

生成的动态类 $Proxy0, 重写了被代理类 Car 的 move() 方法, 在 move() 方法里, 都是在调用InvocationHandler 实例对象的 invoke() 方法

点赞
收藏
评论区
推荐文章
秃头王路飞 秃头王路飞
5个月前
webpack5手撸vue2脚手架
webpack5手撸vue相信工作个12年的小伙伴们在面试的时候多多少少怕被问到关于webpack方面的知识,本菜鸟最近闲来无事,就尝试了手撸了下vue2的脚手架,第一次发帖实在是没有经验,望海涵。languageJavaScript"name":"vuecliversion2","version":"1.0.0","desc
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
技术小男生 技术小男生
5个月前
linux环境jdk环境变量配置
1:编辑系统配置文件vi/etc/profile2:按字母键i进入编辑模式,在最底部添加内容:JAVAHOME/opt/jdk1.8.0152CLASSPATH.:$JAVAHOME/lib/dt.jar:$JAVAHOME/lib/tools.jarPATH$JAVAHOME/bin:$PATH3:生效配置
光头强的博客 光头强的博客
5个月前
Java面向对象试题
1、请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
Easter79 Easter79
1年前
Spring的两种代理JDK和CGLIB的区别浅谈
一、原理区别:java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以
Stella981 Stella981
1年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
1年前
Java 动态代理实践AOP
大家都知道Spring中AOP是通过Java动态代理实现的,今天就来简单学习下demo。Java动态代理主要有两个核心类,InvocationHandler和Proxy。/{@codeInvocationHandler}istheinterfaceimplementedbythe<iinvo
Easter79 Easter79
1年前
Spring的两种动态代理:Jdk和Cglib 的区别和实现
一、原理区别:java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以
Wesley13 Wesley13
1年前
Java动态代理机制解析
动态代理是指在运行时动态生成代理类。不需要我们像静态代理那个去手动写一个个的代理类。生成动态代理类有很多方式:Java动态代理,CGLIB,Javassist,ASM库等。这里主要说一下Java动态代理的实现。Java动态代理InvocationHandler接口Java动态代理中,每一个
Wesley13 Wesley13
1年前
JDK动态代理源码分析
JDK动态代理,只能代理接口,为什么呢?我们从一个样例入手。JDK动态代理样例一个接口IHello.java:packagecom.example.demo.proxy.jdk;publicinterfaceIHello{voidsayHello(Stringname)