Java设计模式之代理模式(一)

什么是代理?可以理解为其他对象提供一种代理以控制对这个对象的访问。

举个例子,生活中的外卖平台,店铺是制作外卖的,然后放到平台上售卖。这里的店铺就是真实角色,为了能够让店铺不用担心销售等问题,从而能够更加专注做外卖,所以店铺的外卖都会放到平台上面。这里平台就是代理。平台代店家出售,而店家只需要做外卖就好了。

代理模式和装饰器模式的区别:装饰器模式为了增强目标对象的功能,而代理模式是为了代理或增强目标对象的功能,本质上两者都可以为目标对象添加一些功能。

代理模式可以分为静态代理和动态代理。

一、静态代理

静态代理:程序在运行之前需要手动创建代理类,代理类与目标类实现同一个接口。代理类为目标类增加一些额外的功能。

1、创建接口:

package com.statics.proxy.interfaces;

/**
 * @Author: 倚天照海
 * @Description:
 */
public interface HotelInterface {

    public void cook();

}

2、创建实现类

package com.statics.proxy.impl;

import com.statics.proxy.interfaces.HotelInterface;

/**
 * @Author: 倚天照海
 * @Description:
 */
public class HuangMenJi implements HotelInterface {

    @Override
    public void cook() {
        System.out.println("黄焖鸡米饭很好吃!");
    }

}

3、创建代理类

package com.statics.proxy.impl;

import com.statics.proxy.interfaces.HotelInterface;

/**
 * @Author: 倚天照海
 * @Description:
 */
public class MeiTuan implements HotelInterface {

    private HuangMenJi huangMenJi;

    public MeiTuan(HuangMenJi huangMenJi) {
        this.huangMenJi = huangMenJi;
    }

    @Override
    public void cook() {
        discount();
        huangMenJi.cook();
        pay();
    }

    private void discount(){
        System.out.println("5折优惠,量大实惠,欢迎来购");
    }

    private void pay(){
        System.out.println("快捷付款,一键支付");
    }

}

4、创建测试类

package com.statics.proxy;

import com.statics.proxy.impl.HuangMenJi;
import com.statics.proxy.impl.MeiTuan;

/**
 * @Author: 倚天照海
 * @Description:
 */
public class ProxyTest {

    public static void main(String[] args) {
        HuangMenJi huangMenJi = new HuangMenJi();
        MeiTuan meiTuan = new MeiTuan(huangMenJi);
        meiTuan.cook();
    }

}

输出结果:

二、JDK动态代理

动态代理是一种在运行时动态地创建代理对象并调用代理方法的机制。动态代理不需要定义代理类的.java源文件,在程序运行时由JVM根据反射机制动态的创建代理类对象。动态代理其实就是jdk运行期间,动态创建class字节码并加载到JVM。

动态代理的优点是提高了代码的灵活性和扩展性,具有解耦的意义。缺点是生成代理对象和调用代理方法需要耗费时间。

动态代理主要有两种实现方式:JDK动态代理和CGLIB动态代理

JDK动态代理其实是通过反射机制实现的。

使用JDK动态代理主要有以下几个步骤:

1.编写一个被代理的接口和接口实现类;

2.自定义一个调用处理器,并实现InvocationHandler接口,重写invoke方法;

3.在invoke方法中通过反射调用被代理方法(即上面接口实现类中的方法);

4.创建接口实现类的实例,并将该实例作为参数来创建自定义调用处理器的实例;

5.通过Proxy类的静态方法newProxyInstance获取代理对象(实现接口),并通过代理对象调用接口中的目标方法(实际是代理对象中重写后的目标方法)。

InvocationHandler是proxy代理实例调用处理程序实现的一个接口,每一个proxy代理实例都与一个调用处理器进行关联,在代理实例调用接口方法时,方法调用会被转发到调用处理器的invoke方法,即代理对象调用接口方法时,在内部会调用InvocationHandler的invoke方法。

Proxy类就是用来创建一个代理对象的类,其中最常用的方法是newProxyInstance静态方法。这个方法的作用就是创建一个代理类对象,它接收三个参数,三个参数的含义如下所示:

loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载。

interfaces:一个interface对象数组,表示给代理对象提供一组接口对象,也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。

h:一个InvocationHandler对象,表示的是当动态代理对象调用接口方法时,会关联到指定的InvocationHandler对象上,并调用指定的InvocationHandler的invoke方法。

示例代码:

被代理的接口及实现类:

package com.tx.study.others.proxy.jdkProxy;

/**
 * @Author: 倚天照海
 */
public interface Manager {

    public void modify();

}

package com.tx.study.others.proxy.jdkProxy;

/**
 * @Author: 倚天照海
 */
public class ManagerImpl implements Manager {

    @Override
    public void modify() {
        System.out.println("*******实现类的modify()方法被调用*********");
    }

}

自定义的调用处理器

package com.tx.study.others.proxy.jdkProxy;

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

/**
 * @Author: 倚天照海
 */
public class ManagerInvocationHandler implements InvocationHandler {

    private Object object = null;

    public ManagerInvocationHandler(Object object) {
        this.object = object;
    }

    /**
     * 重写了InvocationHandler接口中的invoke方法(该方法在何处被调用?)
     * @param proxy 代理对象
     * @param method 被代理类中的被代理方法的Method对象
     * @param args 被代理方法的参数
     * @return 被代理方法的返回值,如果被代理方法是被void修饰的,则返回null
     * @throws Throwable 可能会抛出的异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在真实的对象执行之前可以添加自己的操作
        System.out.println("do something before method");
        //通过反射调用被代理方法,即接口实现类中的方法
        Object ret = method.invoke(this.object, args);
        //在真实的对象执行之后可以添加自己的操作
        System.out.println("do something after method");
        return ret;
    }

}

获取代理对象并调用方法

package com.tx.study.others.proxy.jdkProxy;

import java.lang.reflect.Proxy;

/**
 * @Author: 倚天照海
 */
public class JdkProxyTest {

    public static void main(String[] args) {
        //原对象(被代理对象)
        Manager managerImpl = new ManagerImpl();
        //业务代理类
        ManagerInvocationHandler businessHandler = new ManagerInvocationHandler(managerImpl);
        //获得代理类($Proxy0 extends Proxy implements Manager)的实例
        Manager managerProxy = (Manager) Proxy.newProxyInstance(
                managerImpl.getClass().getClassLoader(),
                managerImpl.getClass().getInterfaces(),
                businessHandler);
        //通过代理对象调用代理类中重写的接口方法(重写的invoke方法在此处被调用)
        managerProxy.modify();
    }

}

执行结果:

do something before method
*******实现类的modify()方法被调用*********
do something after method

拓展:生成jdk动态代理类的源码:

package com.tx.study.others.proxy.jdkProxy;

import sun.misc.ProxyGenerator;

import java.io.*;

/**
 * @Author: 倚天照海
 * @Description: 生成jdk动态代理类的源码
 */
public class JdkProxySourceCodeUtil {

    public static void main(String[] args) {
        //指定生成的代理类名称
        String proxyClassName = "$proxy0";
        Class<Manager> interfaceClass = Manager.class;
        //指定保存代理类的路径
        String path = "D:\\ProgramFiles\\workspace\\myself\\data-query\\tx-study\\src\\main\\java\\com\\tx\\study\\others\\proxy\\jdkProxy\\";
        path = path.concat(proxyClassName).concat(".class");
        writeClassToFile(proxyClassName,interfaceClass,path);
    }

    private static <T> void writeClassToFile(String proxyClassName, Class<T> interfaceClass, String path){
        byte[] proxyClass = ProxyGenerator.generateProxyClass(proxyClassName, new Class[]{interfaceClass});
        OutputStream os = null;
        try {
            os = new FileOutputStream(new File(path));
            os.write(proxyClass);
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null){
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

由上可知,Proxy类中的newProxyInstance静态方法被调用,该方法返回一个代理对象,然后向上转型为其对应的接口。接下来简单看一下Proxy类中的部分源码(如下),主要看一下静态方法newProxyInstance。在该方法中调用getProxyClass0(loader,intfs)方法,根据类加载器和接口数组获取动态代理类的字节码文件对象,进而获取构造器对象,最后执行cons.newInstance(new Object[]{h})(根据指定的调用处理器对InvocationHandler进行初始化),通过反射调用有参构造器创建代理对象,并返回该代理对象。

public class Proxy implements java.io.Serializable {
    private static final long serialVersionUID = -2222568056686623797L;
    /** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };
    /** a cache of proxy classes */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    /** the invocation handler for this proxy instance. */
    protected InvocationHandler h;
    private Proxy() {
    }
	//有参构造器创建代理对象(为InvocationHandler初始化)
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
	
    @CallerSensitive
	public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
		Objects.requireNonNull(h);
		final Class<?>[] intfs = interfaces.clone();
		final SecurityManager sm = System.getSecurityManager();
		if (sm != null) {
			checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
		}
		/* Look up or generate the designated proxy class. */
		
		//根据类加载器和接口数组获取动态代理类的字节码文件对象
		Class<?> cl = getProxyClass0(loader, intfs);
		/* Invoke its constructor with the designated invocation handler. */
		try {
			if (sm != null) {
				checkNewProxyPermission(Reflection.getCallerClass(), cl);
			}
			//通过字节码文件对象获取构造器对象
			final Constructor<?> cons = cl.getConstructor(constructorParams);
			final InvocationHandler ih = h;
			if (!Modifier.isPublic(cl.getModifiers())) {
				AccessController.doPrivileged(new PrivilegedAction<Void>() {
					public Void run() {
						cons.setAccessible(true);
						return null;
					}
				});
			}
			//获取代理对象(通过反射调用有参构造器创建对象)
			return cons.newInstance(new Object[]{h});
		} catch (IllegalAccessException|InstantiationException e) {
			throw new InternalError(e.toString(), e);
		} catch (InvocationTargetException e) {
			Throwable t = e.getCause();
			if (t instanceof RuntimeException) {
				throw (RuntimeException) t;
			} else {
				throw new InternalError(t.toString(), t);
			}
		} catch (NoSuchMethodException e) {
			throw new InternalError(e.toString(), e);
		}
	}
	
    //根据类加载器和接口数组获取动态代理类的字节码文件对象
    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);
    }
	//省略了Proxy类中的其他内部类和方法
}

在自定义调用处理器中重写了InvocationHandler接口中的invoke方法,但是,该方法在何处被调用?接下来看一下本例的$Proxy0类的源码。

下面是本例的代理类$Proxy0的源码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Manager {
	private static Method m1;
	private static Method m0;
	private static Method m3;
	private static Method m2;
	static {
   		try {
    		m1 = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") });
			m0 = Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]);
			m3 = Class.forName("com.tx.study.others.proxy.jdkProxy.Manager").getMethod("modify",new Class[0]);
			m2 = Class.forName("java.lang.Object").getMethod("toString",new Class[0]);
		} catch (NoSuchMethodException nosuchmethodexception) {
   			 throw new NoSuchMethodError(nosuchmethodexception.getMessage());
   		} catch (ClassNotFoundException classnotfoundexception) {
    			throw new NoClassDefFoundError(classnotfoundexception.getMessage());
   		}
	} //静态代码块结束

	//在代理类$Proxy0的构造方法中调用父类Proxy的构造方法,初始化InvocationHandler
	public $Proxy0(InvocationHandler invocationhandler) {
   		super(invocationhandler);
	}

	@Override
	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);
       	}
	}

	@Override
	public final int hashCode() {
  		try {
           	return ((Integer) super.h.invoke(this, m0, null)).intValue();
       	} catch (Throwable throwable) {
           	throw new UndeclaredThrowableException(throwable);
       	}
	}

	//自定义的InvocationHandler中重写的invoke方法就是在此处被调用的
	public final void modify() {
   		try {
			super.h.invoke(this, m3, null);
			return;
        } catch (Error e) {
		} catch (Throwable throwable) {
			throw new UndeclaredThrowableException(throwable);
        }
	}

	@Override
	public final String toString() {
   		try {
        	return (String) super.h.invoke(this, m2, null);
       	} catch (Throwable throwable) {
        	throw new UndeclaredThrowableException(throwable);
       	}
 	}
}

本例的代理类$Proxy0继承了Proxy类,并实现了Manager接口(上面编写的需要被代理的接口),所以,在$Proxy0类中也实现了Manager接口中的modify()方法。在$Proxy0类中通过反射获取到了实现的modify()方法(由于此例中Manager接口中只有modify一个方法,所以就获取到了modify这一个方法,如果Manager接口中有多个方法,那么在$Proxy0类中通过反射将获取所有的实现方法),即Method m3方法。在$Proxy0类的modify方法中调用父类Proxy中h的invoke()方法,即InvocationHandler.invoke()方法。由于自定义调用处理器实现了InvocationHandler接口,并重写了invoke方法,由多态性可知,实际上调用的是重写后的invoke方法。所以,在上面的例子中,当创建了代理对象managerProxy,并通过代理对象调用managerProxy.modify()方法时,实际上是调用代理类$Proxy0中重写的modify方法,并在modify方法中调用了自定义调用处理器中重写的invoke方法。因此,自定义调用处理器中重写的invoke方法是在执行managerProxy.modify()方法时被调用的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/905758.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WebSocket 连接频繁断开的问题及解决方案

文章目录 WebSocket 连接频繁断开的问题及解决方案1. 引言2. 什么是 WebSocket&#xff1f;2.1 WebSocket 的优势2.2 WebSocket 的工作原理 3. WebSocket 连接频繁断开的常见原因3.1 服务器端问题3.1.1 服务器负载过高3.1.2 服务器配置不当3.1.3 超时设置 3.2 网络问题3.2.1 网…

字符串逆序(c语言)

错误代码 #include<stdio.h>//字符串逆序 void reverse(char arr[], int n) {int j 0;//采用中间值法//访问数组中第一个元素和最后一个元素//交换他们的值&#xff0c;从而完成了字符串逆序//所以这个需要临时变量for (j 0; j < n / 2; j){char temp arr[j];arr[…

elcipse工具使用记录

安装 创建项目并运行Helloword 没有显示console? Window–>Show View–>Console 快捷键的积累 代码提示功能 windows->prference->java->Content Assist, 修改Auto…,内容为.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.

头歌——数据库系统原理(数据的简单查询)

文章目录 第1关&#xff1a;基本 SELECT 查询代码 第2关&#xff1a;带限制条件的查询和表达式查询代码 第3关&#xff1a;使用 WHERE 语句进行检索代码 第1关&#xff1a;基本 SELECT 查询 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 如何获取数据表中指…

关于我、重生到500年前凭借C语言改变世界科技vlog.13——深入理解指针(3)

文章目录 1.字符指针变量2.数组指针变量3.函数指针变量4.函数指针数组5.二维数组传参本质6.拓展补充希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 本章节接着学习常见的指针变量类型 1.字符指针变量 字符指针变量&#xff0c;顾名思义就是字…

贪心算法入门(一)

1.什么是贪心算法&#xff1f; 贪心算法是一种解决问题的策略&#xff0c;它将复杂的问题分解为若干个步骤&#xff0c;并在每一步都选择当前最优的解决方案&#xff0c;最终希望能得到全局最优解。这种策略的核心在于“最优”二字&#xff0c;意味着我们追求的是以最少的时间和…

MacBook 如何设置打开json格式文件的默认程序是vs code

首先右键选中文件&#xff0c;然后选中显示简介 然后选中打开方式 设置成vs code

宝塔使用clickhouse踩坑

前言 最近有个物联网项目,需要存储物联网终端发送过来的信息(类似log日志,但又要存储在数据库里,方便后期聚合统计),本来想写文件的奈何客户要求聚合统计,所以只能用数据库才能达到更高的计算效率,当然mysql对这种日志型数据库并没有优势,数据量上去后反而不利于计算…

ML 系列:第 18 部 - 高级概率论:条件概率、随机变量和概率分布

文章目录 一、说明二、关于条件概率2.1 为什么我们说条件概率&#xff1f;2.2 为什么条件概率在统计学中很重要 三、 随机变量的定义3.1 定义3.2 条件概率中的随机变量 四、概率分布的定义五、结论 一、说明 条件概率是极其重要的概率概念&#xff0c;它是因果关系的数学表述&…

十个常见的软件测试面试题,拿走不谢

所有面试问题一般建议先总后分的方式来回答&#xff0c;这样可以让面试官感觉逻辑性很强。 1. 自我介绍 之所以让我们自我介绍&#xff0c;其实是面试官想找一些时间来看简历&#xff0c;所以自我介绍不用太长的时间&#xff0c;1-2分 钟即可。 自我介绍一般按以下方式进行介…

软考高级中哪个好考?软考5个高级资格详细分析!

计算机软件资格考试是由国家人力资源和社会保障部、工业和信息化部领导下的国家级考试&#xff0c;这个考试既是职业资格考试&#xff0c;又是职称资格考试。 软考专业资格层次对应表 计算机软件资格考试设置了27个专业资格&#xff0c;涵盖5个专业领域&#xff0c;3个级别层次…

vi —— 终端中的编辑器

目标 vi 简介打开和新建文件三种工作模式常用命令分屏命令常用命令速查图 01. vi 简介 1.1 学习 vi 的目的 在工作中&#xff0c;要对 服务器 上的文件进行 简单 的修改&#xff0c;可以使用 ssh 远程登录到服务器上&#xff0c;并且使用 vi 进行快速的编辑即可常见需要修改…

sklearn|机器学习:决策树(一)

文章目录 sklearn&#xff5c;机器学习&#xff1a;决策树&#xff08;一&#xff09;&#xff08;一&#xff09;概述&#xff08;二&#xff09;实战1. 环境配置2. sklearn 中的决策树&#xff08;1&#xff09;模块 sklearn.tree&#xff08;2&#xff09;sklearn 基本建模流…

React基础语法

1.React介绍 React由Meta公司开发&#xff0c;是一个用于构建Web和原生交互界面的库 1.1 React优势 相较于传统基于DOM开发的优势 1.组件化的开发方式 2.不错的性能 相较于其他前端框架的优势 1.丰富的生态 2.跨平台支持 1.2React的时长情况 全球最流行&#xff0c;大厂…

docker安装、设置非sudo执行、卸载

安装 sudo snap install docker 设置docker非sudo执行 sudo groupadd docker sudo usermod -aG docker $USER newgrp docker sudo chown root:docker /var/run/docker.sock 卸载docker 1.删除docker及安装时自动安装的所有包 apt-get autoremove docker docker-ce docker-…

golang的RSA加密解密

参考&#xff1a;https://blog.csdn.net/lady_killer9/article/details/118026802 1.加密解密工具类PasswordUtil.go package utilimport ("crypto/rand""crypto/rsa""crypto/x509""encoding/pem""fmt""log"&qu…

SpringSecurity框架(入门)

简介&#xff1a; Spring Security 是一个用于构建安全的 Java 应用程序的框架&#xff0c;尤其适用于基于Spring的应用程序。它提供了全面的安全控制&#xff0c;从认证&#xff08;Authentication&#xff09;到授权&#xff08;Authorization&#xff09;&#xff0c;以及…

探索C嘎嘎:初步接触STL

#1024程序员节&#xff5c;征文# 前言&#xff1a; 在前文小编讲述了模版初阶&#xff0c;其实讲述模版就是为了给讲STL提前铺垫&#xff0c;STL是C中很重要的一部分&#xff0c;各位读者朋友要知道它的份量&#xff0c;下面废话不多说&#xff0c;开始走进STL的世界。 目录&am…

使用Python进行数据分析入门

文章目录 Python环境搭建安装Anaconda验证安装 必备库介绍NumPyPandasMatplotlibSciPy 数据导入与清洗导入数据清洗数据 数据探索与分析描述性统计相关性分析 数据可视化绘制直方图 高级主题机器学习深度学习 总结 随着大数据时代的到来&#xff0c;数据分析变得越来越重要。Py…

宏组学干货|一文get宏基因组产品如何选择

大家好&#xff0c;小编来分享宏基因组产品选做思路喽~ 随着微生物研究的发展和高通量测序技术的出现&#xff0c;微生物的研究迎来了宏组学技术研究时代。目前宏组学产品种类繁多&#xff0c;常见项目主要包括宏基因组、宏病毒组和宏转录组。宏基因组项目可以检测宏样本中所有…