当前位置: 首页 > news >正文

深入理解Java代理模式:从静态到动态的实现与应用

1、引言

在Java编程中,代理模式是一种常见的设计模式,用于在不修改原始代码的情况下,为对象添加额外的功能。代理模式有两种主要类型:静态代理和动态代理。本文将全面探讨这两种代理模式,包括它们的基本概念、实现方式、应用场景及其优缺点。

2、静态代理

静态代理是在编译时确定代理类的。代理类和目标类都需要实现相同的接口,代理类持有对目标对象的引用,并在调用目标对象的方法前后插入额外的逻辑。

先给大家看一张基础设计代理的原理图

在这里插入图片描述

2.1、静态代理的示例

定义一个接口

public interface ISubject{void eat();
}

实际类

public class RealSubject implements ISubject{@Overridepublic void eat() {System.out.println("RealSubject do eat...");}
}

代理对象

public class ProxyObject implements ISubject{private ISubject iSubject;public ProxyObject(ISubject iSubject) {this.iSubject = iSubject;}private void preSomething() {System.out.println("preEat......");}private void afterSomething() {System.out.println("afterEat......");}@Overridepublic void eat() {preSomething();iSubject.eat();afterSomething();}
}

工厂类

public class ObjectFactory {public static ISubject getInstance(){return new ProxyObject(new RealSubject());}}

调用

public class TestProxy {public static void main(String[] args) {testProxy();}public static void testProxy(){ISubject iSubject = ObjectFactory.getInstance();iSubject.eat();}
}

上面这个是最简单的例子,我们一般在使用的代理模式的时候,我们只关注代理是谁,真实对象是谁即可,如何根据这两个参数(也就是类的全限定名称)进行代理呢?我们可以使用反射,下来我们重构一下工厂类和main方法,示例代码如下:

public class ObjectFactory { public ObjectFactory() {}/*** 通过类名字符串创建并返回指定类型的实例* * @param className 要创建的类的全限定名字符串* @return 创建的实例* @throws RuntimeException 如果指定的类不存在,或者实例化过程中出现错误,则抛出运行时异常*/public static <T> T getInstance(String className){T t = null;try {// 通过反射机制创建指定类的实例t =  (T) Class.forName(className).newInstance();} catch (InstantiationException e) {// 如果类无法被实例化,则抛出运行时异常throw new RuntimeException(e);} catch (IllegalAccessException e) {// 如果类的构造方法不可访问,则抛出运行时异常throw new RuntimeException(e);} catch (ClassNotFoundException e) {// 如果类不存在,则抛出运行时异常throw new RuntimeException(e);}return t;}/*** 根据代理名和真实对象名创建并返回一个代理对象实例* * @param proxyName 代理类的全限定名,用于动态加载和实例化代理对象* @param realName 真实对象类的全限定名,用于获取真实对象的实例* @param <T> 泛型标记,表示返回的代理对象类型* @return T 返回一个实现了真实对象接口的代理对象实例* @throws RuntimeException 如果在实例化代理对象过程中发生错误,则抛出运行时异常* * 此方法主要用于在运行时动态创建代理对象,通过反射机制加载并实例化指定的代理类* 代理类需要通过构造方法接收真实对象作为参数,以便代理对象能够调用真实对象的方法*/public static <T> T getInstance(String proxyName, String realName){// 初始化代理对象为nullT t = null;// 获取真实对象的实例T obj = getInstance(realName);// 尝试通过反射机制实例化代理对象try {// 使用代理类名和接口类型来创建代理对象实例t = (T) Class.forName(proxyName).getConstructor(obj.getClass().getInterfaces()[0]).newInstance(obj);} catch (InstantiationException e) {// 如果代理类无法实例化,则抛出运行时异常throw new RuntimeException(e);} catch (IllegalAccessException e) {// 如果构造方法非法访问,则抛出运行时异常throw new RuntimeException(e);} catch (InvocationTargetException e) {// 如果调用构造方法时目标方法抛出异常,则抛出运行时异常throw new RuntimeException(e);} catch (NoSuchMethodException e) {// 如果未找到符合要求的构造方法,则抛出运行时异常throw new RuntimeException(e);} catch (ClassNotFoundException e) {// 如果未找到代理类,则抛出运行时异常throw new RuntimeException(e);}// 返回创建的代理对象实例return t;}
}

调用

public class TestProxy {public static void main(String[] args) {testReflect();}public static void testReflect(){ISubject iSubject = ObjectFactory.getInstance("com.hy.proxy.ProxyObject", "com.hy.proxy.RealSubject");iSubject.eat();}}

2.2 静态代理的优缺点

优点:

  1. 代码清晰,易于理解。
  2. 适合于需要明确代理逻辑的场景。

缺点:

  1. 每个代理都需要一个对应的目标类,代码冗余。
  2. 不够灵活,代理类和目标类的关系在编译时确定。

3、动态代理

动态代理是在运行时创建代理对象的,能够在运行时决定具体的代理逻辑。Java的动态代理分为两种主要实现方式:

  • JDK动态代理
  • CGLIB动态代理

在这里插入图片描述

对于动态代理,代理对象可以代理一个类中的多个实现方法,即一个真实类实现多个接口,一个代理类就可以完全代理

3.1、JDK动态代理

JDK动态代理基于反射机制,可以在运行时创建代理对象。要使用JDK动态代理,目标类必须实现一个或多个接口。通过 Proxy 类和 InvocationHandler 接口,可以创建代理对象,并在调用方法时插入额外的逻辑。

3.1.1、JDK动态代理示例代码

接口

public interface ITest {void play();
}public interface ISubject{void eat();
}

真实类

public class DynamicRealObject implements ISubject, ITest{@Overridepublic void eat() {System.out.println("DynamicRealObject eat...");}@Overridepublic void play() {System.out.println("DynamicRealObject play...");}}

动态代理类

public class DynamicProxy implements InvocationHandler {private Object target;/*** 绑定目标对象并生成代理对象* 本方法主要用于通过动态代理绑定一个目标对象,并返回该目标对象的代理实例* * @param target 目标对象,即需要通过代理的对象* @return 返回目标对象的代理实例,该实例可以用于方法拦截和调用*/public Object bind(Object target) {// 绑定目标对象到当前代理实例this.target = target;// 利用Java动态代理机制创建并返回目标对象的代理实例// 该代理实例将使用目标对象的类加载器,实现目标对象所实现的所有接口,并将当前代理实例作为调用处理程序return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}private void preSomething() {System.out.println("preEat......");}private void afterSomething() {System.out.println("afterEat......");}/*** 该方法通过Java动态代理机制,拦截调用目标方法的请求* 在调用目标方法之前和之后执行特定操作,实现环绕通知的功能* 这种方式可以用于实现事务管理、日志记录、权限控制等横切关注点** @param proxy 代理对象,通常不用直接操作* @param method 被调用的目标方法的信息* @param args 调用目标方法时传递的参数* @return 目标方法的返回值* @throws Throwable 目标方法抛出的异常*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 在调用目标方法之前执行的操作,如开启事务、日志记录等preSomething();// 调用目标方法,并传递参数Object obj = method.invoke(target, args);// 在调用目标方法之后执行的操作,如关闭事务、日志记录等afterSomething();// 返回目标方法的执行结果return obj;}}

调用

public class TestProxy {public static void main(String[] args) {testDynamicProxy();}public static void testDynamicProxy(){// 结合静态代理的反射,可以这样写// ISubject iSubject = (ISubject) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));// ITest iTest = (ITest) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));ISubject iSubject = (ISubject) new DynamicProxy().bind(new DynamicRealObject());iSubject.eat();ITest iTest = (ITest) new DynamicProxy().bind(new DynamicRealObject());iTest.play();}}

3.2、CGLIB动态代理类

CGLIB(Code Generation Library)是一个功能强大的代码生成库,允许在运行时动态创建代理类。与JDK动态代理不同,CGLIB不要求目标类实现接口,而是通过继承目标类来创建代理。

3.2.1 CGLIB动态代理示例代码

目标类

public class CglibProxy {public void doSomething(){System.out.println("Cglib do something");}}

代理类

public class CglibInterceptor implements MethodInterceptor {private final Object target;public CglibInterceptor(Object target) {this.target = target;}private void preDoSomething() {System.out.println("preDoSomething......");}private void afterDoSomething() {System.out.println("afterDoSomething......");}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {preDoSomething();Object obj = method.invoke(target, objects);afterDoSomething();return obj;}
}

调用

public class TestProxy {public static void main(String[] args) {testCglib();}/*** 使用CGLIB进行动态代理的测试方法* 该方法通过CGLIB框架创建一个CglibProxy的子类实例,以实现方法的拦截和调用*/public static void testCglib(){// 创建CGLIB增强器实例Enhancer enhancer = new Enhancer();// 设置代理类的父类为CglibProxy类,这样CGLIB会生成该类的子类enhancer.setSuperclass(CglibProxy.class);// 设置拦截器,传入CglibProxy实例作为拦截器的关键信息enhancer.setCallback(new CglibInterceptor(new CglibProxy()));// 通过增强器创建代理类实例CglibProxy cglibProxy = (CglibProxy) enhancer.create();// 通过代理实例调用目标方法,此调用将被CglibInterceptor拦截并处理cglibProxy.doSomething();}
}

3.3、动态代理的优缺点

优点:

  1. 灵活性高,可以在运行时创建代理对象。
  2. 不需要修改目标类代码。

缺点:

  1. JDK动态代理只能代理实现了接口的类,不能代理具体类。
  2. CGLIB动态代理在生成代理类时会继承目标类,这可能导致一些问题,如不能代理final类或方法。

3.4 CGLIB和JDK动态代理区别

具体区别如下:

  1. 实现原理: JDK动态代理是基于Java反射机制实现的,它要求目标类必须实现一个或多个接口,代理对象在运行时动态创建,通过实现目标类接口的方式来代理目标类。 CGLIB代理则是基于ASM字节码框架实现的,它可以代理没有实现接口的目标类。CGLIB在运行时通过动态生成目标类的子类来实现代理。

  2. 性能表现: JDK动态代理因为需要实现目标类接口,所以它的性能相对较低,但是它的应用场景更为广泛,适用于大多数情况下的代理需求。 CGLIB代理则因为不需要实现目标类接口,所以它的性能相对较高,但是它不能代理final类和final方法,以及一些无法生成子类的类。

  3. 应用场景: JDK动态代理适用于代理接口的场景,例如Spring中的事务处理、日志记录等。 CGLIB代理适用于代理类的场景,例如Spring中的AOP切面编程等。


http://www.mrgr.cn/news/15477.html

相关文章:

  • 基于生成对抗模型GAN蒸馏的方法FAKD及其在EdgesSRGAN中的应用
  • 光纤FPV无人机技术详解
  • Dell 服务器 PowerEdge T440 驱动器故障
  • 【网络】P2P打洞原理
  • UE的Gameplay框架(一) —— GameMode与GameState
  • [解决]Invalid configuration `aarch64-openwrt-linux‘: machine `aarch64-openwrt
  • 19.神经网络 - 线性层及其他层介绍
  • 用例设计面试:微信发送语音的测试用例
  • 使用 FormCreate 渲染 Element Plus 表单
  • docker与宿主机之间拷贝文件
  • 力扣题/图论/实现 Trie (前缀树)
  • CSS 知识点及使用案例
  • 海南云亿商务咨询有限公司助力商家破浪前行
  • Java 面试题:HTTP版本演变--xunznux
  • 5G无线电链路监控(Radio Link Monitoring,简称RLM)
  • 业务单据号每日重置后从1开始
  • 商标权-系统架构师(五十八)
  • VBA学习(67):Excel VBA 提取数字/自定义工作表函数/正则表达式/批量提取电话号码
  • Java和C#哪个更适合大型项目?
  • 自建远程桌面RustDesk服务器(CentOS配置,保姆级案例)