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

【Spring】详细了解静态代理和动态代理的使用

目录

1.代理模式介绍

2. 静态代理

3.动态代理

3.1 JDK动态代理

3.2 CGLIB动态代理

4. 动态代理和静态代理的区别


1.代理模式介绍

代理模式分为动态代理和静态代理,目的是在不直接暴露真实对象的情况下,通过代理对象来间接访问真实对象,从而可以在访问前后添加额外的功能,比如日志记录、性能监控、事务管理等。核心思想:不改变原有的代码模式下,去增加一些功能。

 通过一张图来了解代理模式:在这张图中,房东要出租房,我要租房,但是房东不想去搞一系列复杂的事情,于是找了一个代理的中介角色,客户直接和中介去租房就可以。房东和代理中介有一个共同的接口就是出租房。

2. 静态代理

静态代理是指在程序运行前就已经确定了代理类的具体实现,也就是说代理类是在编译期就创建好的,并且需要显式地为每个接口或类编写一个代理类。

以上面事件为例,实现静态代理

(1)首先定义一个接口House,定义一个租房的方法 rent()

package com.its.ex02.poxy01;public interface House {/*1. 静态代理:*       以房东出租房子为例:房东,被代理的人。中介:代理人。租客:需要与被代理人沟通的人。* */void rent();
}

(2)定义一个实现类,代表房东,实现接口方法

package com.its.ex02.poxy01;public class MyHouse implements House{@Overridepublic void rent() {System.out.println("我要出租房子");}
}

(3)定义一个代理对象,用于帮被代理人出租房子

package com.its.ex02.poxy01;public class Poxy implements House {private House ownerHouse;public Poxy(House ownerHouse) {this.ownerHouse = ownerHouse;}@Overridepublic void rent() {lookHouse();talkPrice();//帮被代理人出租房子ownerHouse.rent();takeMoney();}//代理对象也可以有自己的一些功能private void lookHouse(){System.out.println("带客户去看房子");}private void talkPrice(){System.out.println("跟客户谈价格");}private void takeMoney(){System.out.println("拿提成");}
}

(4)执行测试,租客直接找代理人租房即可

package com.its.ex02.poxy01;public class Client {public static void main(String[] args) {
//        找房子的客户MyHouse client = new MyHouse();
//        通过代理人租房Poxy poxy = new Poxy(client);
//        代理人给客户租房,其中也能做自己的事情poxy.rent();}
}

测试结果如下,静态代理完成。

静态代理的特点

  • 固定代理类:对于每个接口或类都需要手动创建一个代理类。(造成代码臃肿)
  • 编译期生成:代理类在编译时就已经存在。
  • 不可扩展:一旦定义了代理类,要扩展其功能就需要修改代码并重新编译。
  • 易于理解:代码结构清晰,容易理解。

3.动态代理

动态代理是指在运行时动态创建代理对象,它不需要为每个接口编写单独的代理类。Java 提供了两种动态代理的方式:

  1. 基于接口的动态代理,JDK代理(使用 java.lang.reflect.Proxy 类)
  2. 基于类的动态代理,CGLIB代理(使用 CGLIB 库或其他第三方库)。

3.1 JDK动态代理

通过接口的方式,让代理类和实际业务类实现统一的接口,并且借助代理类统一管理接口 InvocationHandler(房子接口)进行动态代理机制的实现。这种方式依赖于接口,如果一个类不 实现接口则无法实现代理。

(1)首先定义一个接口House,定义一个租房的方法 rent()写一个

package com.its.ex02.poxy02;public interface House {void rent();
}

(2)然后定义一个房东类实现这个接口,重写租房的方法。

package com.its.ex02.poxy02;public class MyHouse implements House {@Overridepublic void rent() {System.out.println("我要出租房子");}
}

(3)定义proxyJDK这个类,这个类实现了 InvocationHandler 接口,它是用来处理代理对象方法调用的。

package com.its.ex02.poxy02;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;//这个类实现了 InvocationHandler 接口,它是用来处理代理对象方法调用的
public class PoxyJDK implements InvocationHandler {private House house;//    这个构造函数接收一个 House 类型的对象,并将其赋值给成员变量 house
//    接口类型的好处是只要实现了House接口的实现类都可以被代理public PoxyJDK(House house) {this.house = house;}//实现InvocationHandler接口,用于处理 代理对象上(房东) 的方法调用。@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("调用方法前可以执行动态代理人自己的事情");//使用反射机制调用实际对象 House接口 上的方法。Object o = method.invoke(house, args);System.out.println("调用方法后也可以执行动态代理人自己的事情");//返回方法调用的结果return o;}
}

(4)定义一个客户类,用于演示如何创建代理对象并调用其方法。

package com.its.ex02.poxy02;import java.lang.reflect.Proxy;public class Client {public static void main(String[] args) {//1.被代理的类MyHouse myHouse = new MyHouse();//2.代理类, 创建一个 PoxyJDK 实例,传入 myHouse 作为被代理对象。PoxyJDK poxyJDK = new PoxyJDK(myHouse);//3.获取myHouse的类加载器接口,这是使用 Proxy.newProxyInstance 方法创建代理对象所必需的ClassLoader classLoader = MyHouse.class.getClassLoader();//4.获取 MyHouse 类实现的所有接口,通常这里只有一个接口,即 HouseClass<?>[] interfaces = MyHouse.class.getInterfaces();//5.使用 Proxy.newProxyInstance 方法创建代理对象 houseHouse house = (House) Proxy.newProxyInstance(classLoader, interfaces, poxyJDK);//调用代理对象的 rent 方法。house.rent();}
}

(5)执行测试代码,结果如下,演示成功

3.2 CGLIB动态代理

CGLIB概念

  • CGLIB是一个强大的高性能的代码生成库。它允许你在运行时创建一个现有类的子类。CGLIB 主要用于实现动态代理,尤其适用于那些没有实现接口的类
  • 与JDK动态代理机制不同,CGLIB 不是通过接口来创建代理对象,而是通过继承来创建一个子类,因此它可以为任何类创建代理,只要这个类不是 final 的。

原理

GLIB 使用字节码技术为指定的类创建一个子类,并覆盖其中的方法。当调用代理对象的方法时,实际上会调用 CGLIB 生成的方法拦截器(MethodInterceptor)来处理方法调用。这个方法拦截器可以让你在方法调用前后添加额外的逻辑。

(1)首先定义一个接口House,定义一个租房的方法 rent()写一个

package com.its.ex02.poxy03;public interface House {void rent();
}

(2)然后定义一个房东类实现这个接口,重写租房的方法。

package com.its.ex02.poxy03;public class MyHouse implements House {@Overridepublic void rent() {System.out.println("我要出租房子");}
}

(3)接下来,我们需要创建一个实现了 MethodInterceptor 接口的类PoxyCGLIB,用于处理方法调用。

package com.its.ex02.poxy03;import com.its.ex02.poxy01.House;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class poxyCGLIB implements MethodInterceptor {public House house;public poxyCGLIB(House house) {this.house = house;}/*o: 当前代理对象实例。method: 被调用的方法的 Method 对象。objects: 被调用的方法的参数列表。methodProxy: 用于调用原始方法的代理对象。*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("执行方法前做的事情,方法名:"+method.getName());Object result = methodProxy.invoke(house, objects);System.out.println("执行方法后做的事情方,法名:"+method.getName());return result;}
}

注:上面的method参数的使用:

  1. 基于方法名的不同执行不同的前置或后置逻辑:以根据方法名来判断是否需要执行特定的前置逻辑或后置逻辑。

  2. 验证方法参数:可以通过检查方法的参数来确保它们符合预期。

  3. 修改方法行为:如果需要的话,您可以根据方法签名来改变方法的行为,例如,改变方法的返回值。

  4. 记录方法调用:可以记录哪些方法被调用了以及它们被调用时的参数信息。

(4)定义一个客户类,用于测试创建代理对象并调用代理对象的方法。

package com.its.ex02.poxy03;
import com.its.ex02.poxy03.House;
import org.springframework.cglib.core.DebuggingClassWriter;
import org.springframework.cglib.proxy.Enhancer;public class Client {public static void main(String[] args) {MyHouse myHouse = new MyHouse();// 代理类class文件存入本地磁盘方便我们反编译查看源码System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:\\code");// Enhancer 是 CGLIB 中用于创建代理对象的类。Enhancer enhancer = new Enhancer();// 设置 Enhancer 的父类为 MyHouse 类,这表示代理类将继承 MyHouse 类。enhancer.setSuperclass(MyHouse.class);// 设置enhancer的回调对象enhancer.setCallback(new poxyCGLIB(myHouse));// 创建代理对象,类型转换为 House 接口House house = (House) enhancer.create();// 通过代理对象调用目标方法house.rent();}
}

CGLIB 与 JDK 动态代理的区别

  • 适用场景:JDK 动态代理要求目标对象必须实现至少一个接口,而 CGLIB 可以为任何非 final 类创建代理。
  • 代理机制:JDK 动态代理是基于接口的,而 CGLIB 是基于继承的。
  • 性能:通常情况下,CGLIB 产生的代理类可能比 JDK 动态代理慢一点,因为它涉及到了字节码级别的操作。
  • 兼容性:CGLIB 是一个第三方库,需要在项目中引入相应的依赖

4. 动态代理和静态代理的区别

区别

  1. 代理类的创建时机

    • 静态代理:代理类在编译期就已经创建好了。
    • 动态代理:代理类是在运行时动态创建的。
  2. 代理类的创建方式

    • 静态代理:需要为每个接口或类手动编写代理类。
    • 动态代理:通过反射机制在运行时创建代理类。
  3. 代理类的数量

    • 静态代理:每个接口或类都有一个对应的代理类。
    • 动态代理:可以为多个接口或类创建一个代理类。
  4. 代码的可维护性和扩展性

    • 静态代理:代码结构简单,但扩展性较差,因为每次都需要修改代理类。
    • 动态代理:更易于扩展,因为可以复用同一个 InvocationHandler
  5. 性能考虑

    • 静态代理:由于代理类是提前编译好的,所以在运行时的性能可能略优于动态代理。
    • 动态代理:创建代理对象和执行方法调用时会有一些额外开销,但在大多数情况下这些开销是可以接受的。

动态代理可以动态的生成代理类,静态代理则需要预先定义代理类,对于每一个接口都需要有一个对应的代理类,下面举例说明。

假设我们有两个接口 ServiceAServiceB,我们希望为这两个接口创建代理,以便在调用实际服务之前和之后添加一些通用的逻辑(例如日志记录)。

静态代理

// ServiceA 接口
public interface ServiceA {void doSomethingA();
}// ServiceB 接口
public interface ServiceB {void doSomethingB();
}// ServiceA 的实现
public class ServiceAImpl implements ServiceA {@Overridepublic void doSomethingA() {System.out.println("Doing something A...");}
}// ServiceB 的实现
public class ServiceBImpl implements ServiceB {@Overridepublic void doSomethingB() {System.out.println("Doing something B...");}
}// ServiceA 的静态代理类
public class ServiceAProxy implements ServiceA {private ServiceA serviceA;public ServiceAProxy(ServiceA serviceA) {this.serviceA = serviceA;}@Overridepublic void doSomethingA() {System.out.println("Before doSomethingA");serviceA.doSomethingA();System.out.println("After doSomethingA");}
}// ServiceB 的静态代理类
public class ServiceBProxy implements ServiceB {private ServiceB serviceB;public ServiceBProxy(ServiceB serviceB) {this.serviceB = serviceB;}@Overridepublic void doSomethingB() {System.out.println("Before doSomethingB");serviceB.doSomethingB();System.out.println("After doSomethingB");}
}

动态代理

// 公共的日志记录 InvocationHandler
public class LoggingInvocationHandler implements InvocationHandler {private Object target;public LoggingInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method call: " + method.getName());Object result = method.invoke(target, args);System.out.println("After method call: " + method.getName());return result;}
}// 创建代理对象并使用
public class ProxyDemo {public static void main(String[] args) {ServiceA serviceA = new ServiceAImpl();ServiceA serviceAProxy = (ServiceA) Proxy.newProxyInstance(ServiceA.class.getClassLoader(),new Class<?>[]{ServiceA.class},new LoggingInvocationHandler(serviceA));ServiceB serviceB = new ServiceBImpl();ServiceB serviceBProxy = (ServiceB) Proxy.newProxyInstance(ServiceB.class.getClassLoader(),new Class<?>[]{ServiceB.class},new LoggingInvocationHandler(serviceB));serviceAProxy.doSomethingA();serviceBProxy.doSomethingB();}
}

相关文章:

  • Android读取拨号记录功能
  • 【九】Hadoop3.3.4HA高可用配置
  • Vue3 + js-echarts 实现前端大屏可视化
  • java计算机毕设课设—网上招聘系统(附源码、文章、相关截图、部署视频)
  • 扩展------零拷贝技术(Mmap,SendFile)
  • 统计语言模型——Ngram
  • SpringMVC 工作流程简述
  • 2024年华数杯数学建模竞赛——赛题浅析
  • FFmpeg实现文件夹多视频合并
  • 使用Python创建多功能文件管理器
  • AcWing食物链
  • Lua 脚本编程基础
  • 搭建nexus上传jar包,并结合jenkins运行项目
  • OpenCV||超细节的基本操作
  • Redis学习笔记——第19章 事务
  • Google 是如何开发 Web 框架的
  • 【node学习】协程
  • 〔开发系列〕一次关于小程序开发的深度总结
  • AngularJS指令开发(1)——参数详解
  • ES学习笔记(10)--ES6中的函数和数组补漏
  • Git初体验
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • Java反射-动态类加载和重新加载
  • Less 日常用法
  • MySQL几个简单SQL的优化
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • Python socket服务器端、客户端传送信息
  • React Native移动开发实战-3-实现页面间的数据传递
  • vue:响应原理
  • 前端面试总结(at, md)
  • 优化 Vue 项目编译文件大小
  • 优秀架构师必须掌握的架构思维
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 积累各种好的链接
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • # 达梦数据库知识点
  • ######## golang各章节终篇索引 ########
  • #HarmonyOS:软件安装window和mac预览Hello World
  • #stm32驱动外设模块总结w5500模块
  • #微信小程序:微信小程序常见的配置传旨
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (04)odoo视图操作
  • (a /b)*c的值
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (day6) 319. 灯泡开关
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (ZT)薛涌:谈贫说富
  • (zt)最盛行的警世狂言(爆笑)
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (转)【Hibernate总结系列】使用举例
  • (转)socket Aio demo
  • (转)母版页和相对路径
  • *上位机的定义