简单谈谈动态代理

一、前言

在我接触到spring框架的核心之一aop时,我就好奇aop这玩意怎么这么神奇呢!还能在不伤及原有代码筋骨的基础上,对它不留痕迹的增强。这中间我不断琢磨,查找相关资料,借助这么好的平台,我给大家分享一下我所理解的和我整理的-动态代理。

二、正文

1、静态代理

代理模式简单分为两种:动态代理和静态代理。要更清晰理解动态代理,我们先得从静态代理入手。

//定义交通工具类
public interface Vehicle {

    //公共内容
    void commonContent();

	//这个方法回头再看
	void m(String str);
}
//用车类实现交通工具
public class Car implements Vehicle {

    //车名
    String name;

    public Car(String name) {
        this.name = name;
    }

    @Override
    public void commonContent() {
        System.out.println(name+"行驶1000米");
    }
	
	//这个方法回头再看
	@Override
    public void m(String str) {
        System.out.println(str);
    }
}
//创建代理类
public class MyProxy implements Vehicle {

	//被代理对象
    private Vehicle vehicle;

    public MyProxy(Vehicle vehicle) {
        this.vehicle = vehicle;
    }

    @Override
    public void commonContent() {

        long start = System.currentTimeMillis();
        System.out.println("起跑时间:" + start + "毫秒");

        //调用公共内容
        vehicle.commonContent();

        long end = System.currentTimeMillis();
        System.out.println("完成时间:" + end + "毫秒");

        //耗时时间
        System.out.println("消耗" + (end - start) + "毫秒");
    }

	//这个方法回头再看
	@Override
    public void m(String str) {
        vehicle.m(str);
    }
}
//测试类
public class Test {

     public static void main(String[] args) {
        //创建实例
        Vehicle myProxy = new MyProxy(new Car("奔驰gls450"));
        myProxy.commonContent();

    	System.out.println("================================================");
		//这个方法回头再看
        myProxy.m("mmmmmmmmmmm");
    }
}

执行结果:
在这里插入图片描述

小结:这里,我们就使用MyProxy (代理类)对Car(被代理类)中的commonContent方法,实现了静态方式的增强。大家在这里,有没有感觉这种方式跟装饰者模式有点类似;还有,这里的例子中在这里插入图片描述
对应的是aop的Before advice通知类型,在这里插入图片描述
对应的是aop的After returning advice通知类型。

2.动态代理(JDK)

动态代理和静态代理的最大区别:静态代理在程序运行之前就已经编译完成;动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。

带着这个结论,我们一起看看Java在java.lang.reflect包下,为我们提供的Proxy类和InvocationHandler接口。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 先定义一个我的调用处理程序MyInvocationHandler,去实现java.lang.reflect包下
 * 的接口InvocationHandler
 */
public class MyInvocationHandler<T> implements InvocationHandler {

    //被代理类对象
    T t;

    public MyInvocationHandler(T t) {
        this.t = t;
    }

    /**
     * @param proxy  代理类对象
     * @param method 被代理类中的被增强方法(可看成commonContent方法)
     * @param args   传给被增强方法的参数
     * @return    被增强方法有返回值则返回;被增强方法返回值为void则返回null,即result为null
     * @throws Throwable 接住被增强方法可能抛出的异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        long start = System.currentTimeMillis();
        System.out.println("起跑时间:" + start + "毫秒");

        //调用公共内容(commonContent)
        Object result = method.invoke(t, args);

        long end = System.currentTimeMillis();
        System.out.println("完成时间:" + end + "毫秒");

        //耗时时间
        System.out.println("消耗" + (end - start) + "毫秒");

        return result;
    }
}
//测试类
public class Test {

    public static void main(String[] args) {
        //使用构造方法将被代理对象car传入
        MyInvocationHandler<Vehicle> car = new MyInvocationHandler<>(new Car("奔驰gls450"));

        //借助java.lang.reflect包下的Proxy类生成代理类和代理类对象
        Vehicle vehicleProxy = (Vehicle) Proxy.newProxyInstance(Vehicle.class.getClassLoader(),
                Car.class.getInterfaces(), car);

        //调用代理类对象中的公共内容方法commonContent,这里的commonContent方法是增强之后的
        vehicleProxy.commonContent();

        System.out.println("===================================================");
		
        vehicleProxy.m("mmmmmmmmmm");
    }
}

执行结果:
在这里插入图片描述
这里,我们可以再回头看看m()方法了,对比上下两个结果,发现使用jdk动态代理,m()方法也给我们增强了,这也是静态与动态代理之间的一个小区别。这是怎么一回事?为了更加直观的看到,我们将代理类字节码文件输出后再反编译(XJad反编译工具,在我的码云-工具包

public static void main(String[] args) {
        //使用这句代码就能接收到代理类字节码byte数组
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Car.class.getInterfaces());
        //指定输出位置
        String path = "F:/$Proxy0.class";
        //写入文件
        try (FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理类class文件写入成功");
        } catch (Exception e) {
            System.out.println("写入文件错误");
        }
    }

反编译结果:

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space 

import java.lang.reflect.*;

//生成的代理类
public final class $Proxy0 extends Proxy
        implements Vehicle {

    //被代理方法
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    //利用反射获取到方法
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{
                    Class.forName("java.lang.Object")
            });
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m4 = Class.forName("Vehicle").getMethod("commonContent", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m3 = Class.forName("Vehicle").getMethod("m", new Class[]{
                    Class.forName("java.lang.String")
            });
        } catch (NoSuchMethodException nosuchmethodexception) {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        } catch (ClassNotFoundException classnotfoundexception) {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }

    //利用构造方法给Proxy类中的protected InvocationHandler h;赋值
    public $Proxy0(InvocationHandler invocationhandler) {
        /**
         * 看到这,再结合下面的super.h.invoke(this, m4, null)就能知道,
         * 为何代理对象vehicleProxy调用方法都是在执行InvocationHandler中的invoke方法,
         * 也就解释了为何Vehicle中m()方法也被增强了。
         * 而MyInvocationHandler持有一个被代理对象的实例(T t;),
         * 这样就完成了对被代理对象的整个代理过程。
         */
        super(invocationhandler);
    }

    //代理方法
    public final void commonContent() {
        try {
            //调用InvocationHandler中的invoke方法
            //不难看出InvocationHandler被当作了一个中转站
            super.h.invoke(this, m4, null);
            return;
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    //代理方法
    public final void m(String s) {
        try {
            super.h.invoke(this, m3, new Object[]{
                    s
            });
            return;
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    //代理方法
    public final boolean equals(Object obj) {
        try {
            return ((Boolean) super.h.invoke(this, m1, new Object[]{
                    obj
            })).booleanValue();
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    //代理方法
    public final String toString() {
        try {
            return (String) super.h.invoke(this, m2, null);
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    //代理方法
    public final int hashCode() {
        try {
            return ((Integer) super.h.invoke(this, m0, null)).intValue();
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

小结:jdk动态代理为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会依次递增)的代理类,这个代理类文件存放在内存中,我们在创建代理对象时,就是通过反射获得这个代理类的构造方法,然后创建的代理实例。这也就是Proxy.newProxyInstance为我们做的所有事。

如果将在这里插入图片描述

改为在这里插入图片描述
生成的代理类是这样的
在这里插入图片描述
这也是jdk动态代理(代理接口)和cglib动态代理(代理父类)最明显的区别。理解过程中,可以认为jdk动态代理就是通过代码操作了模板而产生的。

三、结尾

感谢这篇文章Java动态代理实现与原理详细分析,对我的帮助。如果你有不同的理解,欢迎留言。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值