栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

结构型模式——代理模式之JDK方式的动态代理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

结构型模式——代理模式之JDK方式的动态代理

文章目录
  • 前言
  • 代理模式的结构
  • JDK 动态代理
    • 1、定义抽象主题类
    • 2、定义具体主题实现类
    • 3、编写代理类工厂,用于生成动态代理对象
    • 4、编写消费者
  • JDK的动态代理源码分析
    • ProxyGenerator.generateProxyClass 做了什么
  • 资料参考
  • 测试代码下载

前言

在上一篇博客中,重点说明了代理模式的概念、结构和案例等。

链接地址:结构型模式——代理模式之静态代理

代理模式的结构

代理模式中,分为以下三种角色:

  • 抽象主题类(Subject):

    定义规范!
    通过接口或者抽象类声明真实主题和代理对象实现的业务方法。

  • 真实主题类(Real Subject):

    实现抽象主题中的具体业务,是代理对象所代表的真实对象,即最终需要引用的对象。

  • 代理类(Proxy):

    提供了与真实主题相同的接口,其内部包含有对真实主题的引用。它可以访问、控制或扩展真实主题的功能。

JDK 动态代理

代理模式一般分为静态代理和动态代理两种方式。其根本区别在于代理类一个是编译期生成,而另一个是在程序执行期间生成。

动态代理一般有JDK代理和CGLib代理两类。接下来详细说明这两种方式产生动态代理的区别。

Java中提供了一个动态代理类java.lang.reflect.Proxy,其中提供有一个newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)方法,用来获取代理对象。

动态代理和静态代理的主要区别,在于代理类对象的生成方式!
静态代理就是编写代码,根据编写的代码在程序编译期间生成!
动态代理,则是依据 Proxy 代理工具类,依据其中给定的方法自动去生成!

接下来依旧采取代码案例的方式,进行使用说明。

1、定义抽象主题类

抽象主题类,其实就是一种约束(或者叫规范)。

package proxy.jdk;


public interface SellPhone {
    // 定义约束,代理对象和具体主题对象,都有卖的操作
    void sell();
}
2、定义具体主题实现类

这里还是依据华为厂商为例。

package proxy.jdk;


public class HWContractor implements SellPhone {
    @Override
    public void sell() {
        System.out.println("华为厂商卖手机");
    }
}
3、编写代理类工厂,用于生成动态代理对象

在该小节之前就说到,JDK实现动态代理的方式,是采取动态代理类java.lang.reflect.Proxy,中的newProxyInstance方法,用来获取代理对象。

其中该方法具有三个参数信息。

这三个参数的含义如下所示:

名称含义
ClassLoader类加载器,用于代理类的加载方式。可以通过目标对象(具体主题实现类)获取类加载器。
代理类和具体主题实现类都是抽象主题类的子类
Class[]代理类实现的接口的字节码对象。
InvocationHandler代理对象的调用处理程序。

其中newProxyInstance中的InvocationHandler接口中定义的invoke方法,也存在三个参数:

其各个参数的含义如下所示:

名称含义
proxy代理对象,与Proxy.newProxyInstance返回的对象是同一个对象。
method对接口中的方法进行封装的Method对象。
args调用方法的实际参数。

根据上述说明,编写动态代理工厂实现类:

package proxy.jdk;

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


public class ProxyFactory {

    // 声明目标对象
    private HWContractor hwContractor = new HWContractor();

    // 提供获取代理对象的方法
    public SellPhone getProxyObj(){
        // 使用 Proxy 获取代理对象

        
        SellPhone proxyInstance = (SellPhone) Proxy.newProxyInstance(
                hwContractor.getClass().getClassLoader(),
                hwContractor.getClass().getInterfaces(),
                new InvocationHandler() {
                    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 增强
                        System.out.println("代售点赚取手续费");
                        // 执行目标对象的方法
                        Object obj = method.invoke(hwContractor, args);
                        return obj;
                    }
                });
        return proxyInstance;
    }
}

4、编写消费者
package proxy.jdk;

public class Client {
    public static void main(String[] args) {
        // 1、创建代理类工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 2、通过自定义的代理工厂中的方法,创建出代理对象
        SellPhone proxyObj = proxyFactory.getProxyObj();
        // 3、使用代理对象的方法
        proxyObj.sell();
    }
}

JDK的动态代理源码分析

在动态代理中,使用Proxy.newProxyInstance创建一个SellPhone 接口的子类,由代理模式的结构可知:

1、具体主题类是SellPhone的子类。
2、代理类是SellPhone的子类。

那么,采取Proxy.newProxyInstance则是一种自动生成代理类的方式。

进入源码中可以看出:

此处逻辑表示自动生成代理类。

Class cl = getProxyClass0(loader, intfs);

继续进入其中,或者采取debug运行可知,其生成代理类的实际操作如下所示:

采取从一个缓存中获取,其具体逻辑又是怎么操作的呢,继续向下看:



根据debug方式运行,发现此处subKeyFactory.apply(key, parameter)执行的并非是默认的apply方式,而是静态内部类 ProxyClassFactory中apply方法。

从该方法的逻辑中可以看出:

最后采取defineClass0生成一个代理类对象信息。这个方法是一个native修饰的方式。可以采取将其生成的信息保存至文本中进行查看。

ProxyGenerator.generateProxyClass 做了什么

编写代码,将该数组信息保存至文本中。

import java.io.IOException;
import sun.misc.ProxyGenerator;
import java.nio.file.Files;
import java.io.File;
import java.nio.file.Path;
import java.lang.reflect.Modifier;

public class TestFile {
    public static void main(String[] args)throws IOException {

        // 复制源码中java.lang.reflect.Proxy.ProxyClassFactory.apply,debug出的数据信息
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
        String proxyName = "proxy0";
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, new Class[] {SellPhone.class}, accessFlags);

        // 采取流的方式保存至文本
        File file = new File(System.getProperty("user.dir") + "/src/proxy/jdk/proxy0.class");
        //必须保证父目录的存在
        if(!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();//不存在  则创建父目录
        }
        Path path = file.toPath();
        System.out.println(path);
        Files.write(path, proxyClassFile);
    }
}

运行后,在对应的包中可以看到具体的文件信息:

提取其中与代理类相关的代码,如下所示:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.jdk.SellPhone;

public final class proxy0 extends Proxy implements SellPhone {
	private static Method m3;
	static{
		m3 = Class.forName("proxy.jdk.SellPhone").getMethod("sell");
	}
	public final void sell() throws  {
		// 调用父类的InvocationHandler中的invoke方法
		// 传递参数为null
	    super.h.invoke(this, m3, (Object[])null);
	}
}

从中可以看出,真正的逻辑很明了

代理模式类,也是SellPhone的子类,作为子类实现父类接口其子类必须重写抽象方法,

所以,上述代码中sell()为代理类的具体实现方式。其逻辑为super.h.invoke,调用父类中的 h 类的 invoke方法。

父类中的 h.invoke 为程序代码编写中的:

最后采取获取反射到的类的普通方法,执行其具体逻辑。

资料参考

反射的基本用法——第六点

测试代码下载

本篇博客下载地址

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/591283.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号