代理模式之动态代理->JDK动态代理

代理就是对某一个类进行非入侵式的增强,即在其执行前后执行某些逻辑。

静态代理:在Java程序执行前,手动实现一个代理类,去实现被代理者中需要被代理的方法并完成逻辑增强。一旦被代理者的方法有增减,则需要修改代理类对应的位置,从而违背了【开闭原则】–对修改关闭,推拓展开放。

动态代理:在Java程序执行过程中,由Java代码通过反射或其他方式动态的生成一个代理类。这个代理类包含被代理者的所有方法,和所需要的方法的增强。如果被代理者的方法出现增减,代理者不需要程序员手动修改,而是会自动生成与被代理者对应的新的代理者。即为动态代理

静态代理此处跳过,本文主要讨论JDK动态代理。
第一步:先不管具体理解,按照JDK代理的代码流程实现一个案例;
第二部:根据这个案例去理解JDK动态代理

代码实现

1.准备一个规范的接口


public interface IGamePlayer {
    /**
     * 登录
     * @param name
     * @param pwd
     */
    public void login(String name,String pwd);

    /**
     * 打怪
     */
    public void killBoss();

    /**
     * 升级
     */
    public void upGread();

}

2.准备一个需要被代理的接口实现类

/**
 * 玩家的实现类
 */
public class GamePlayer implements IGamePlayer {
    /**
     * 玩家的名字
     */
    private String name = "";

    /**
     * 有参构造传入玩家名字
     * @param name
     */
    public GamePlayer(String name){
        this.name = name;
    }

    @Override
    public void login(String name, String pwd) {
        System.out.println(this.name+"登录上线");
    }

    @Override
    public void killBoss() {
        System.out.println(this.name+"打怪成功");
    }

    @Override
    public void upGread() {
        System.out.println(this.name+"升级了!");
    }
}

3.准备一个用来完成JDK动态代理的代理功能的类

/**
 * 利用JDK proxy实现动态代理
 * InvocationHandler:通过反射来实现代理一个对象
 */
public class DynamicGamePlayerProxy implements InvocationHandler {
    /**
     * 用于存储要被代理的对象
     */
    private IGamePlayer gamePlayer;

    /**
     * 通过有参构造传入要被代理的对象
     * @param player
     */
    public DynamicGamePlayerProxy(IGamePlayer player){
        this.gamePlayer = player;
    }
	/**
     * 调用方法,并对指定方法进行增强
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("killBoss")) {
            System.out.println("开启无敌模式");
        }
        Object obj = method.invoke(this.gamePlayer,args);
        if (method.getName().equals("killBoss")) System.out.println("关闭无敌模式");
        return obj;
    }
}

4.测试

public static void main(String[] args) {
		System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        GamePlayer player = new GamePlayer("扫地僧");
        DynamicGamePlayerProxy playerProxyHandler = new DynamicGamePlayerProxy(player);

        //使用JDK动态代理的代理对象
        IGamePlayer proxy =
                (IGamePlayer) Proxy.newProxyInstance(player.getClass().getClassLoader(),//被代理者
                                                        new Class[]{IGamePlayer.class},//规范化的接口
                                                        playerProxyHandler);//代理类
        proxy.login("saodis","123245432");
        proxy.killBoss();
        proxy.upGread();
		//查看proxy到底是什么
        System.out.println(proxy.getClass());
    }

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

分析其实现过程

1.反编译

在main方法的最后,我们查看了proxy到底是什么,显示的结果是

class com.sun.proxy.$Proxy0

为了搞清楚$Proxy0是什么,我们对其进行了反编译,在测试类的main方法中加入了一句

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

已经通过这一句完成了反编译。反编译后的文件存储在
在这里插入图片描述

2.初步查看反编译代码

进入$Proxy0类,首先看到的是:

public final class $Proxy0 extends Proxy implements IGamePlayer {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    private static Method m5;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    ...

可以看出,$Proxy0是Proxy的一个子类,并且实现了我们一开始指定的规范接口,那么这个它必定会实现我们在接口中定义的那三个方法。同时,这一对m0~m5是什么?
翻到末尾,看到了它的一段静态代码块:

...
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.gupao.vip.partten.proxy.game.IGamePlayer").getMethod("upGread");
            m4 = Class.forName("com.gupao.vip.partten.proxy.game.IGamePlayer").getMethod("killBoss");
            m5 = Class.forName("com.gupao.vip.partten.proxy.game.IGamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

所以可以看出,m0-m5就是接口中的所有方法,包括Object自带的方法和用户创建的方法。而我们所关心的,自己创建的
upGread方法对应的是m3;
killBoss方法对应的是m4;
login方法对应的是m5;
由于我们在动态代理中是对killBoss做出了增强
在这里插入图片描述
所以我们先去找一下m4和killBoss在哪里出现:往上走,看到了killBoss、upGread、login三个方法的实现过程,发现几乎都是一样的,都是调用了super.h.invoke()方法。

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

这里的super不难理解,就是$Proxy0的父类Proxy,点击h,进入代码查看:发现h是一个成员变量,类型是InvocationHandler 。

/**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

这个InvocationHandler 就很熟悉了,因为在代理类DynamicGamePlayerProxy中,我们就实现了InvocationHandler 这个接口,并且实现了其内部唯一的方法:invoke,这里回顾一下:

public class DynamicGamePlayerProxy implements InvocationHandler {
    /**
     * 用于存储要被代理的对象
     */
    private IGamePlayer gamePlayer;
    ...
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("killBoss")) {
            System.out.println("开启无敌模式");
        }
        Object obj = method.invoke(this.gamePlayer,args);
        if (method.getName().equals("killBoss")) System.out.println("关闭无敌模式");
        return obj;
    }
    ...

再回到$Proxy0的killBoss方法代码里,就不难理解了,这里执行

super.h.invoke(this, m4, (Object[])null);

其实就是回到了代理类DynamicGamePlayerProxy里面,来执行invoke方法了。而具体是执行哪一个方法,则已经在调用的时候通过参数输入了(即这里是执行m4方法–>m4方法对应的是killBoss方法,所以这里传入了IGamePlayer的killBoss方法,并将会执行这个方法。)
整个执行流程大致如下
在这里插入图片描述
通过这样连串的调用,完成了动态的代理执行

细看JDK动态代理的执行过程

从测试类中的关键一句:

//使用JDK动态代理的代理对象
        IGamePlayer proxy =
                (IGamePlayer) Proxy.newProxyInstance(player.getClass().getClassLoader(),
                                                        new Class[]{IGamePlayer.class},
                                                        playerProxyHandler);

先分析参数:

参数是什么
player.getClass().getClassLoader()是需要被代理的对象的类加载器
new Class[]{IGamePlayer.class}一个Class数组,里面只放了一个规范化接口
playerProxyHandler代理类

点击进入newProxyInstance的源码:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);//要求代理类不能为空

        final Class<?>[] intfs = interfaces.clone();//1.将传进来的规范化接口数组备份
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        //2.根据传入的ClassLoader和规范接口生成代理类的class对象【注意】这里只是一个代理类对象,不是实例
        Class<?> cl = getProxyClass0(loader, intfs);
        ...

这里经过getProxyClass0,就获取到了代理类的对象,具体的获取方法需要继续点进去看:

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

这里是先判断是否接口数量小于65535,然后调用了proxyClassCache.get方法,注意上面说的注释:

如果由指定加载器和指定接口锁确定的这个代理类已经存在,则直接返回这个缓存起来的副本,否则才会通过ProxyClassFactory的工厂模式创建代理类。

至此,proxyClassCache.get的基本功能清楚了,既然说是通过ProxyClassFactory来创建的代理类,那就找到ProxyClassFactory并进入这个类:发现这是Proxy里面的一个静态内部类:

/**
     * A factory function that generates, defines and returns the proxy class given
     * the ClassLoader and array of interfaces.
     */
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
        ...

注释说清楚了它的功能:

一种工厂函数,它根据传入的ClassLoader 和接口数组生成,定义并返回对应的代理类

两个成员变量

proxyClassNamePrefix;生成的所有代理类的前缀名都是$Proxy

AtomicLong nextUniqueNumber = new AtomicLong();下一个用于生成唯一代理类名称的数字

在这个静态内部类里面发现只有一个apply方法,下面展示其一部分代码:

...
			for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }
...

这里的for是判断数组中是否非public的接口都在同一个包里面,对非public的代理类,生成之后放置于原来的包下面,对public的包,统一放置于指定的包下面(com.sun.proxy这个包下)。下面一段展示了如何加载这个代理类:

			/*
             * Generate the specified proxy class.
             */
             //加载这个代理类的字节码文件
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
            	//将字节码文件加载到 JVM内存中,返回class对象
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }

这里是先生成代理类的字节码文件,而后调用一个native方法defineClass0,这个方法会返回一个class,这个class对象即为代理类对象。由于defineClass0不是Java语言写的,所以到此无法深入了。所以这里看字节码的生成,即ProxyGenerator.generateProxyClass方法:

...
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
...
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        if (saveGeneratedFiles) {
            ...
        }
        return var4;

可以看出if中的内容其实就是在吧这个代理类的字节码写出来(在main中设置的)。而具体的生成字节码,是在这一句

final byte[] var4 = var3.generateClassFile();

再进入generateClassFile方法,看到频繁出现的一个方法:addProxyMethod
在这里插入图片描述
看一下ProxyMethod是什么,发现是ProxyGenerator下面的一个内部类:
在这里插入图片描述
在generateClassFile中看到,完成所有方法的搜集后,会将类的字节码结构中的所有部件(包括属性、方法等,这里涉及到编译和字节码,不再展开细看),按照JVM文件规范,一步一步全部写入var13这个ByteArrayOutputStream类型的变量中
在这里插入图片描述
在这里插入图片描述
并在最后将这个字节码数组返回给Proxy中的byte[] proxyClassFile,而后调用defineClass0转为class对象返回给上一级
在这里插入图片描述
到此,ProxyClassFactory这个静态内部类中的apply方法就执行完成,并返回了一个Class对象给Proxy中的newProxyInstance方法的这一步
在这里插入图片描述
接下来,通过获取这个class对象的构造器来产生一个代理类的实例,并返回这个实例
在这里插入图片描述
由此,main方法中,获取代理对象这一个动态的(根据不同的对象获取对应的属性与方法)、关键的步骤完成了。而且返回的是一个命名为$Proxy开头的类
在这里插入图片描述
也就是说,此时返回的已经不再是GamePlayer的实例了,而是自动根据GamePlayer中所有接口、所有方法和增强逻辑合并后生成的一个新的代理类。
总结:整个代码流程大致如下
在这里插入图片描述
1.根据传进来的接口,获取接口里面所有的方法及其包含的异常信息
2.根据这些信息、以及增强的逻辑合并,一起生成一个字节码文件;
3.将字节码文件加载的JVM,创建一个Class对象;
4.调用Class对象的构造器创建一个实例,返回给main。

与静态代理相比

静态代理是在编译的时候就已经编译了代理类的字节码,而后将字节码加载到JVM

JDK动态代理是在Java程序运行时才根据被代理者的接口和方法生成字节码文件,而后才将这个字节码文件加载到JVM中来。JDK代理依赖于规范化的接口,被代理者必须实现接口,否则无法实现JDK动态代理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值