设计模式-代理模式

news/2024/10/31 3:19:45/

文章目录

  • 前言
  • 一、静态代理
  • 二、动态代理
    • 1. JDK 动态代理
    • 2. CGLIB 动态代理
  • 三、总结


前言

在学习代理模式之前,我们需要先理解什么是代理?

百度上是这样解释代理的:委托人与代理人签订代理协议,授权代理人在一定范围内代表其向第三者进行商品买卖或处理有关事务。

比如:某服装厂卖衣服,它可以找商家帮它去经销这些衣服;汽车厂生产出来的车子,4s 店卖车;或者是某些公司招人,会委托猎头去帮他们筛选人才。

这些模式都属于代理,而且代理方在处理被代理方安排的事情的基础上,还能进行增强,比如服装厂的代理商能够搞促销活动提高销售量;4s 店对车送脚垫、送保养;猎头可以帮委托的公司筛选出相对适合的人。

Java 中也存在这样的关系,比如说 A 需要调 C 的某个方法,但是 A 不能直接调用 C ,而 B 可以调用 C,这时 A 只需要调用 B,让 B 调用 C 就行了,在这段关系中 B 就是代理对象,C 为被代理对象。这个过程中 B 还可以增加新的业务,增强对 C 的访问。

由上述例子的可以看出代理模式的作用:

  • 功能增强,在原有的功能上增加新的功能
  • 控制访问,在代理中控制是否可以调用目标对象的方法,例如商家不让用户直接访问到厂家

实现代理的两种方式:

  • 静态代理:是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  • 动态代理:是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

一、静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同的父类

优缺点分析:

  • 优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
  • 缺点:① 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类;② 一旦接口增加了新的方法,目标对象和代理对象都需要维护

接下来我以公司委托猎头招人为例,写一个静态代理:

在这里插入图片描述

public class StaticProxyDemo {public static void main(String[] args) {Recruit recruit = new Headhunting(new Company());recruit.interview();}
}/*** 招工接口*/
interface Recruit {/*** 面试*/void interview();}/*** 公司*/
class Company implements Recruit {@Overridepublic void interview() {System.out.println("对应聘者进行面试...");}}/*** 猎头*/
class Headhunting implements Recruit {private final Recruit target;public Headhunting(Recruit target) {this.target = target;}@Overridepublic void interview() {System.out.println("筛选简历....");target.interview();System.out.println("通知应聘者面试结果...");}
}

从这个例子中可以看出,猎头对公司的面试做出了增强,在面试前进行简历的筛选和面试后告知应聘者面试结果。


二、动态代理

Java 中,动态代理是一种机制,它允许程序在运行时动态地生成代理类,从而可以在不修改源代码的情况下,为原有的类提供额外的功能或者控制访问。

换句话说就是动态代理是一种创建 java 对象的能力,可以在不创建代理类的前提下就创建代理对象。

动态代理主要通过反射机制来实现,其基本原理是:在运行时创建一个实现了指定接口的代理类对象,然后将请求转发给实际的目标对象,并在转发过程中执行额外的操作。

由于动态代理可以在运行时生成代理类,因此它比静态代理更加灵活和易于扩展,常用于实现各种框架、AOP 等技术。

优点

  • 减少重复代码:通过动态代理,可以将一些通用的代码逻辑(如日志记录、事务管理、异常处理等)抽象出来,统一处理,从而减少代码的重复性,提高代码的可维护性和可读性。
  • 灵活性强:使用动态代理可以实现对原始对象的透明代理,从而使得代理对象和被代理对象具有相同的接口,对于调用者来说是透明的,同时也可以灵活地切换代理对象,从而实现不同的业务逻辑。
  • 可扩展性强:通过动态代理,可以在不修改原始对象的情况下,对其进行增强和扩展,从而实现更丰富的功能和更高的可复用性。
  • 提高性能:动态代理可以通过缓存等技术,减少创建代理对象的开销,从而提高系统的性能和效率。
  • 可以实现横向功能:动态代理可以通过在代理类中添加额外的功能实现横向功能(cross-cutting concerns),如安全控制、权限控制、性能监控等。

常见的动态代理分为:JDK 动态代理cglib 动态代理


1. JDK 动态代理

JDK 动态代理是基于 Java 的反射机制实现的,在使用 JDK 中接口和类实现代理对象的动态创建。JDK 的动态代理要求目标对象必须实现接口

如果不懂反射,建议先去看下:Java-反射

从 JDK 1.3 以来,Java 语言通过 java.lang.reflect 包提供三个类支持代理模式 ProxyMethodInovcationHandler

  • Proxy:是所有动态代理的父类,它提供了一个静态方法(newProxyInstance)来创建动态代理的 class 对象和实例

newProxyInstance() 方法源码:

/*** 参数* ClassLoader loader:类加载器,负责向内存中加载对象,使用反射获取目标对象的 classloader,比如对象 A,它的类加载器获取 A,getClass().getClassLoader()* Class<?>[] interfaces:目标对象所实现的接口,也是通过反射获取,比如对象 A,它所实现的接口获取 A.getClass().getInterfaces()* InvocationHandler h:我们自己写的,代理类要完成的功能*/public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException {...}
  • Method:方法对象,通过 Method 可以执行目标类的方法(method.invoke)

invoke() 方法源码:

	/*** 参数* Object obj:目标对象* Object... args:方法的参数** 返回值:* Object :目标对象方法的返回值*/public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException {...    }
  • InvocationHandler(调用处理器): 每个动态代理实例都有一个关联的 InvocationHandler,该接口中只有一个 invoke 方法,该方法表示代理对象要执行的功能代码。

InvocationHandler 源码:

package java.lang.reflect;public interface InvocationHandler {/*** 参数:* Object proxy:jdk 创建的代理对象,无需赋值* Method method:目标类中的方法,jdk 提供 method 对象* Object[] args:目标类中方法的参数,jdk 提供** 返回值:* Object :方法的返回值*/public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

实现步骤:

  1. 创建接口,定义目标类要完成的功能
  2. 创建目标类实现接口
  3. 创建 InvocationHandler 接口的实现类,在 invoke() 方法中完成代理类的功能
    • 调用目标方法
    • 增强功能
  4. 使用 Proxy 类的静态方法创建代理对象,并且返回值转为接口类型

还是以之前公司找猎头招人为例来写 JDK 动态代理,源码如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class JdkProxyDemo {public static void main(String[] args) {// 创建目标对象// Recruit companyRecruit = new CompanyA();Recruit companyRecruit = new CompanyB();// 创建 InvocationHandler 对象RecruitHandler recruitHandler = new RecruitHandler(companyRecruit);// 创建代理对象Recruit proxy = (Recruit)Proxy.newProxyInstance(companyRecruit.getClass().getClassLoader(), companyRecruit.getClass().getInterfaces(),recruitHandler);// 通过代理执行方法proxy.interview();}
}/*** 招工接口*/
interface Recruit {/*** 面试*/void interview();/*** 发送 offer*/void senderOffer();}/*** 公司A*/
class CompanyA implements Recruit {@Overridepublic void interview() {System.out.println("CompanyA 对应聘者进行面试...");}@Overridepublic void senderOffer() {System.out.println("CompanyA 发送 offer");}
}/*** 公司A*/
class CompanyB implements Recruit {@Overridepublic void interview() {System.out.println("CompanyB 对应聘者进行面试...");}@Overridepublic void senderOffer() {System.out.println("CompanyB 发送 offer");}
}/*** 创建 InvocationHandler 接口的实现类,在 invoke() 方法中完成代理类的功能*/
class RecruitHandler implements InvocationHandler {private final Object target;public RecruitHandler(Recruit recruit) {this.target = recruit;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("筛选简历....");// 执行目标方法Object ret = method.invoke(target);System.out.println("通知应聘者面试结果...");return ret;}
}

2. CGLIB 动态代理

JDK 动态代理需要代理的类必须实现接口,如果没有实现接口,只能通过 CGLIB 来实现,其实就是对 JDK 动态代理的一个补充。

CGLIB 动态代理创建代理对象,它的原理是继承,它通过继承目标类,创建它的子类,在子类中重写父类中同名的方法,实现功能的修改。

因为 CGLIB 是继承,重写方法,所以要求目标类和方法不能被 final 修饰

CGLIB 的实现也有两个重要的成员组成,EnhancerMethodInterceptor

  • Enhancer:用于指定要代理的目标对象,实际处理代理的逻辑对象,最终通过调用 create() 方法得到代理对象,对这个对象所有的非 final 方法的调用都会转发给 MethodInterceptor

可以跟进下 Enhancer 类中 create() 方法有的源码了解一下:

public class Enhancer extends AbstractClassGenerator {...// 创建代理类public Object create() {this.classOnly = false;this.argumentTypes = null;return this.createHelper();}...private Object createHelper() {this.preValidate();Object key = KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID);this.currentKey = key;// 调用父类 AbstractClassGenerator 的 create 方法Object result = super.create(key);return result;}
}

AbstractClassGenerator 类中 create(Object key) 方法:

public abstract class AbstractClassGenerator<T> implements ClassGenerator {...// 创建动态代理对象protected Object create(Object key) {try {ClassLoader loader = this.getClassLoader();Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> cache = CACHE;AbstractClassGenerator.ClassLoaderData data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);if (data == null) {Class var5 = AbstractClassGenerator.class;synchronized(AbstractClassGenerator.class) {cache = CACHE;data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);if (data == null) {Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> newCache = new WeakHashMap(cache);data = new AbstractClassGenerator.ClassLoaderData(loader);newCache.put(loader, data);CACHE = newCache;}}}this.key = key;// 调用 get 方法,实际上是调用了 generate 方法生成动态代理的 class 对象Object obj = data.get(this, this.getUseCache());// 通过反射生成具体的代理对象return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj);} catch (RuntimeException var9) {throw var9;} catch (Error var10) {throw var10;} catch (Exception var11) {throw new CodeGenerationException(var11);}}... public Object get(AbstractClassGenerator gen, boolean useCache) {if (!useCache) {return gen.generate(this);} else {Object cachedValue = this.generatedClasses.get(gen);return gen.unwrapCachedValue(cachedValue);}}... // 该方法的作用是生成动态代理 class 对象protected Class generate(AbstractClassGenerator.ClassLoaderData data) {Object save = CURRENT.get();CURRENT.set(this);Class var8;try {ClassLoader classLoader = data.getClassLoader();if (classLoader == null) {throw new IllegalStateException("ClassLoader is null while trying to define class " + this.getClassName() + ". It seems that the loader has been expired from a weak reference somehow. Please file an issue at cglib's issue tracker.");}String className;synchronized(classLoader) {className = this.generateClassName(data.getUniqueNamePredicate());data.reserveName(className);this.setClassName(className);}Class gen;if (this.attemptLoad) {try {gen = classLoader.loadClass(this.getClassName());Class var25 = gen;return var25;} catch (ClassNotFoundException var20) {}}// 此处是关键,调用 DefaultGeneratorStrategy 的 generate 方法生成 class 文件的二进制流byte[] b = this.strategy.generate(this);className = ClassNameReader.getClassName(new ClassReader(b));ProtectionDomain protectionDomain = this.getProtectionDomain();// 以下代码通过代理类 class 文件的二进制流生成具体的 class 对象synchronized(classLoader) {if (protectionDomain == null) {gen = ReflectUtils.defineClass(className, b, classLoader);} else {gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);}}var8 = gen;} catch (RuntimeException var21) {throw var21;} catch (Error var22) {throw var22;} catch (Exception var23) {throw new CodeGenerationException(var23);} finally {CURRENT.set(save);}return var8;}
}
  • MethodInterceptor:方法拦截器,动态代理的方法调用都会转发到 intercept 方法进行增强

MethodInterceptor 接口源码如下:

import java.lang.reflect.Method;public interface MethodInterceptor extends Callback {/*** 参数:* Object var1:代理对象,无需赋值* Method var2:目标类中的方法* Object[] var3:目标类中方法的参数* MethodProxy var4:方法代理,通过此对象可以调用代理对象的方法,也可以调用目标对象的方法 invokeSuper,无需经过反射来调用方法** 返回值:* Object :方法的返回值*/Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

由于 cglib 是第三方的工具库,所以在实现 CGLIB 动态代理前我们需要先进行导包(Maven 仓库)

在这里插入图片描述

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

这里我还是以之前公司找猎头招人为例来实现 CGLIB 动态代理,源码如下:

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;
import java.math.BigDecimal;public class CglibProxyDemo {public static void main(String[] args) {// 生成目标代理类// String filePath = "D:\\static\\class";// System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,filePath);cglibProxyFun3();}/*** 方式一*/public static void cglibProxyFun1() {// 获取方法的拦截器CglibRecruitIntercept intercept = new CglibRecruitIntercept();// 使用 CGLIB 框架生成目标类的子类(代理类),实现功能增强Enhancer enhancer = new Enhancer();// 设置父类字节码enhancer.setSuperclass(Company.class);// 设置拦截处理enhancer.setCallback(intercept);// 创建代理类Company proxy = (Company) enhancer.create();// 调用方法proxy.interview();BigDecimal salary = proxy.senderOffer();System.out.println("薪酬:" + salary.toString());}/*** 方式二*/public static void cglibProxyFun2() {// 使用 CGLIB 框架生成目标类的子类(代理类),实现功能增强Enhancer enhancer = new Enhancer();// 设置父类字节码enhancer.setSuperclass(Company.class);// 设置拦截处理,以这种方式可以不写 CglibRecruitIntercept 类enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Object ret = null;// 对面试方法的增强if ("interview".equals(method.getName())) {System.out.println("筛选简历....");ret = methodProxy.invokeSuper(o, objects);System.out.println("通知应聘者面试结果...");}// 对发简历方法的增强if ("senderOffer".equals(method.getName())) {System.out.println("与应聘者谈薪酬....");ret = methodProxy.invokeSuper(o,objects);System.out.println("压2000工资...");ret = new BigDecimal(ret.toString()).subtract(new BigDecimal(2000));}return ret;}});// 创建代理类Company proxy = (Company) enhancer.create();// 调用方法proxy.interview();BigDecimal salary = proxy.senderOffer();System.out.println("薪酬:" + salary.toString());}/*** 方式三*/public static void cglibProxyFun3() {// 获取 company 的代理对象Company proxy = CglibProxy.getProxy(Company.class, new CglibRecruitIntercept());// 调用方法proxy.interview();BigDecimal salary = proxy.senderOffer();System.out.println("薪酬:" + salary.toString());}}/*** 公司A*/
class Company {public void interview() {System.out.println("Company 对应聘者进行面试...");}public BigDecimal senderOffer() {System.out.println("Company 发送 offer");return new BigDecimal(15000);}
}/*** 创建 MethodInterceptor 接口的实现类,在 intercept() 方法中完成代理类的功能*/
class CglibRecruitIntercept implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Object ret = null;if ("interview".equals(method.getName())) {System.out.println("筛选简历....");ret = methodProxy.invokeSuper(o, objects);System.out.println("通知应聘者面试结果...");}if ("senderOffer".equals(method.getName())) {System.out.println("与应聘者谈薪酬....");ret = methodProxy.invokeSuper(o,objects);System.out.println("压2000工资...");ret = new BigDecimal(ret.toString()).subtract(new BigDecimal(2000));}return ret;}
}/*** cglib 代理类*/
class CglibProxy {/*** 获取代理对象*/public static  <T> T getProxy(Class<T> clazz, Callback callback) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(clazz);enhancer.setCallback(callback);@SuppressWarnings("unchecked")T proxy = (T)enhancer.create();return proxy;}
}

为什么说 CGLIB 底层是继承呢?

我们可以通过 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,filePath); 这段代码将 CGLIB 动态生成的代理类的 .class 文件打印出来,查看源文件 Company$$EnhancerByCGLIB$$c40a01b1.class 我们可以看到生成的代理类是继承了需要代理的类,并且重写了该类中的方法。

Company$$EnhancerByCGLIB$$c40a01b1.class 源码如下:

import java.lang.reflect.Method;
import java.math.BigDecimal;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;public class Company$$EnhancerByCGLIB$$c40a01b1 extends Company implements Factory {private boolean CGLIB$BOUND;public static Object CGLIB$FACTORY_DATA;private static final ThreadLocal CGLIB$THREAD_CALLBACKS;...final BigDecimal CGLIB$senderOffer$0() {return super.senderOffer();}// 重写父类的 senderOffer 方法public final BigDecimal senderOffer() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}return var10000 != null ? (BigDecimal)var10000.intercept(this, CGLIB$senderOffer$0$Method, CGLIB$emptyArgs, CGLIB$senderOffer$0$Proxy) : super.senderOffer();}final void CGLIB$interview$1() {super.interview();}// 重写父类的 interview 方法public final void interview() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$interview$1$Method, CGLIB$emptyArgs, CGLIB$interview$1$Proxy);} else {super.interview();}}...
}

三、总结

代理模式能够在客户端和目标对象之间起到一个中介作用保护目标对象的作用,可以扩展目标对象的功能,能够将客户端和目标对象分离,在一定程度上降低了系统的耦合度

静态代理需要与目标对象实现一样的接口,只能通过手动完成代理操作,所以会有很多代理类,一旦接口中新方法,所有的代理类都需要维护,灵活的比较低,违背开闭原则。

动态代理在一定程度上解决了静态代理的缺点,它可以在运行时动态生成代理对象,取消对被代理类的扩展限制,遵循开闭原则,所以在很多框架中都使用到了动态代理。

而动态代理又分为 JDK 动态代理CGLIB 动态代理

JDK 动态代理 需要目标类实现接口,通过反射机制获取到接口中的方法,并且自定义 InvocationHandler 接口的实现类,实现对方法的拦截;而 CGLIB 动态代理 是通过继承和重写目标方法,使用 MethodInterceptor 调用父类的目标方法从而实现代理。

JDK 动态代理 的效率要高于 CGLIB 动态代理CGLIB 动态代理 在第一次调用的时候会生成字节码比较耗时,多次调用性能还行。

JDK 动态代理 的回调方式是调用了 invoke() 方法,而 CGLIB 动态代理 的回调方式是调用了 intercept() 方法


http://www.ppmy.cn/news/362104.html

相关文章

3D打印机的调平问题

快打完第五批料了&#xff0c;也算是有一些仅限于PLA以及PLA&#xff0b;耗材心得 3D打印机调平的简易方式有哪些&#xff1f; 在3D打印机中&#xff0c;打印平台作为模型的承载平台&#xff0c;如果有偏差&#xff0c;那么在后期的打印中&#xff0c;必然会导致细节的出现差…

小米摄像机升级失败,小米摄像机黄灯常亮修复,全网最硬核修复

小米摄像机升级失败&#xff0c;小米摄像机黄灯常亮修复&#xff0c;全网最硬核修复 背景刷机方法准备相机拆机修补固件刷入固件 破解相机 背景 2020年买了个小米云台相机pro&#xff0c;后来搬家没怎么用&#xff0c;放家里吃灰一年多&#xff0c;前两天突然翻到想着拿来用一…

Vue中如何进行表单联动与级联选择?

Vue中如何进行表单联动与级联选择&#xff1f; 表单联动和级联选择是Vue.js中常见的功能。表单联动是指在一个表单中&#xff0c;当某一个输入框的值发生变化时&#xff0c;其他输入框的值也会随之改变。级联选择是指在一个选择框中&#xff0c;当选择一个选项时&#xff0c;另…

中国汽车脚垫市场消费趋势与营销渠道分析报告2022版

中国汽车脚垫市场消费趋势与营销渠道分析报告2022版 ------------------------------------- 《修订日期》&#xff1a;2022年2月 《出版单位》&#xff1a;鸿晟信合研究院 《对接人员》&#xff1a;周文文 【内容分析有删减了解详情可查看咨询鸿晟信合研究院专员&#xff01;…

我的新车常备用品

提车也有一周的时间了,一直在某宝上淘一些常用的汽车用品,列个清单。 1. 脚垫 这个是4s店送的&#xff0c;算是必须品吧。对了&#xff0c;驾驶室一定要带卡扣的垫子&#xff0c;不然影响刹车加油操作。 比如这种&#xff0c;请忽略外观和颜色。我没有去我的车上拍&#xff…

如何购买北京法拍房

1、对于有限购的法拍房来说&#xff0c;在没有购房者资格的前提下&#xff0c;请不要跟拍&#xff0c;就算是房子拍下来了&#xff0c;没办法过户的话&#xff0c;房子还不能算是你的&#xff0c;弃权则被罚保证金。2、对于没有限购的法拍房来说&#xff0c;在没有购房资格的前…

Go WebAssembly 介绍

1.WebAssembly 是什么 以下是 Mozilla 在 MDN 上给出的定义&#xff1a; WebAssembly&#xff08;缩写&#xff1a;Wasm&#xff09;是一种新的编码方式&#xff0c;可以在现代的网络浏览器中运行 &#xff0d; 它是一种低级的类汇编语言&#xff0c;具有紧凑的二进制格式&…

详解模板模式

目录 1.概述 2.实际业务场景示例 2.1.需求和实现思路 2.1.完整代码实现 1.概述 模板模式是一种常用的设计模式&#xff0c;它定义了一个操作中的算法的骨架&#xff0c;将某些步骤延迟到子类中实现。模板模式使得子类可以在不改变算法结构的情况下重新定义算法中的某些步骤…