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

news2024/11/17 3:33:57

目录

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

    @Override
    public 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 {
    @Override
    public 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接口,用于处理 代理对象上(房东) 的方法调用。
    @Override
    public 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 类实现的所有接口,通常这里只有一个接口,即 House
        Class<?>[] interfaces = MyHouse.class.getInterfaces();
        //5.使用 Proxy.newProxyInstance 方法创建代理对象 house
        House 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 {
    @Override
    public 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: 用于调用原始方法的代理对象。
     */
    @Override
    public 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 {
    @Override
    public void doSomethingA() {
        System.out.println("Doing something A...");
    }
}

// ServiceB 的实现
public class ServiceBImpl implements ServiceB {
    @Override
    public void doSomethingB() {
        System.out.println("Doing something B...");
    }
}

// ServiceA 的静态代理类
public class ServiceAProxy implements ServiceA {
    private ServiceA serviceA;

    public ServiceAProxy(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【设计模式】设计模式之观察者模式

文章目录 观察者模式什么是观察者模式引入组成UML图代码实现1. 定义观察者接口2. 定义主题接口3. 实现具体观察者4. 实现具体被观察者5.测试 应用场景优点缺点 观察者模式 什么是观察者模式 观察者模式&#xff08;Observer Pattern&#xff09;是一种设计模式 它定义了一种…

vLLMcuda安装笔记

1. 引言 最近在部署Qwen模型时&#xff0c;文档上有提到强烈建议用vLLM来部署模型&#xff0c;按照公开的性能测试数据&#xff0c;用vLLM部署Qwen模型的文本推理速度要比transformers部署快3~4倍。带着这个好奇就开始安装尝试&#xff0c;但试下来这个安装过程并没有那么顺利…

最新个人免签约支付系统源码|PHP源码 | 码支付系统 | ThinkPHP6框架 | 开源

源码介绍&#xff1a; 这个最新的个人专用免签约支付系统源码&#xff01;是PHP源码写的哦&#xff0c;而且是用ThinkPHP6框架开发的&#xff0c;完全开源的码支付系统。 这个系统适合个人用户使用&#xff0c;作为收款的免签约解决方案。它还加入了监控端&#xff0c;可以拒…

Linux 调试追踪: trace-cmd 和 kernelshark

文章目录 1. 前言2. 概述3. trace-cmd3.1 下载3.2 交叉编译3.3 安装、运行3.3.1 trace-cmd 示范&#xff1a;抓取系统调度信息 4. kernelshark5. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承…

Java多线程-----定时器(Timer)及其实现

目录 一.定时器简介&#xff1a; 二.定时器的构造方法与常见方法&#xff1a; 三.定时器的模拟实现&#xff1a; 思路分析&#xff1a; 代码实现&#xff1a; 在开发中&#xff0c;我们经常需要一些周期性的操作&#xff0c;例如每隔几分钟就进行某一项操作&#xff0c;这…

【QT】常用控件-上

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 目录 &#x1f449;&#x1f3fb;QWidgetenabledgeometryrect制作上下左右按钮 window frame 的影响window titlewindowIcon代码示例: 通过 qrc 管理图片作为图标 windowOpacitycursor使用qrc自…

Python | Leetcode Python题解之第309题买卖股票的最佳时机含冷冻期

题目&#xff1a; 题解&#xff1a; class Solution:def maxProfit(self, prices: List[int]) -> int:if not prices:return 0n len(prices)f0, f1, f2 -prices[0], 0, 0for i in range(1, n):newf0 max(f0, f2 - prices[i])newf1 f0 prices[i]newf2 max(f1, f2)f0, …

【笔记】《冲击弹性波理论与应用》[2-2] 振动信号分析

1.前级硬件滤波 - 降噪 2.软件降噪 2.1 移动平滑滤波 2.1.1 移动平滑滤波的效果 2.2 经验模态分解法 2.1.1 效果 3 信号分析 除了FFT,最大熵和小波变换现在也很流行。 3.1 最大熵 3.1.1 与FFT的比对 3.2 相关性分析 3.2.1 自相关 3.2.2 互相关 3.3. 小波 非等周期信号 3…

《python语言程序设计》2018第6章第28题 掷骰子 两个色子,分别是1到6

2、3、12 玩家输 7、11玩家赢 4、5、6、8、9、10算1点&#xff0c;之后出7玩家输或者和上一次相同。def rolled(num_t):count 0still_win 0second_win 0still_lose 0second_lose 0while count < num_t:a_1 random.randint(1, 6)b_1 random.randint(1, 6)tTen a_1 b…

力扣-41.缺失的第一个正数

刷力扣热题–第二十五天:41.缺失的第一个正数 新手第二十五天 奋战敲代码&#xff0c;持之以恒&#xff0c;见证成长 1.题目简介 2.题目解答 做这道题有点投机取巧的感觉&#xff0c;要求时间复杂度O(N),且空间复杂度O(1)&#xff0c;那么就是尽可能的去找到更多的可能性&…

C语言程序设计之数组1

程序设计之数组1 问题1_1代码1_1结果1_1 问题1_2代码1_2结果1_2 问题1_3代码1_3结果1_3 问题1_4代码1_4结果1_4 问题1_5代码1_5结果1_5 问题1_1 函数 f u n fun fun 的功能是&#xff1a;移动一位数组中的内容&#xff0c;若数组中有 n n n 个整数&#xff0c;要求把下标从 …

软件测试生命周期、BUG描述与处理策略

软件测试的生命周期 需求分析&#xff1a;需求是否完整、是否正确 测试计划&#xff1a;确定由谁测试、测试的起止时间、设计哪些模块 测试设计、测试开发&#xff1a;写测试用例&#xff08;手工、自动化测试用例&#xff09;、编写测试工具 执行测试用例 测试评估&…

衢州骨伤科医院为98岁高龄老人做髋关节置换,患者第三天便下地行走

灵活迈步、周身整洁、双手提着两口袋鸡蛋......在清晨的菜市场里&#xff0c;王阿婆&#xff08;化名&#xff09;在人群里穿梭&#xff0c;买一些自己和女儿想吃的菜。如果没有看到她的脸&#xff0c;大家都以为她只有 60 多岁&#xff1b;再定睛一看&#xff0c;她虽然脸上布…

Token的原理及区别,以及与Cookie,Session之间的区别?

Token&#xff0c;特别是JSON Web Token&#xff08;JWT&#xff09;&#xff0c;也是一种用于管理用户状态和身份的机制&#xff0c;但它与Cookie和Session的工作方式有所不同。下面将详细解释Token如何管理用户状态和身份。 Token的工作原理 Token是一种无状态的认证机制&am…

QQ邮箱 + Kafka + Redis + Thymeleaf 模板引擎实现简单的用户注册认证

1. 前提条件 1.1 Redis 1.1.1 拉取 Redis 镜像 docker pull redis:latest 1.1.2 启动 Redis 容器 docker run --name my-redis -d -p 6379:6379 redis:latest1.2 Kafka 1.2.1 docker-compose.yml version: 3.8 services:zookeeper:image: "zookeeper:latest"h…

【C++入门(上)】—— 我与C++的不解之缘(一)

前言&#xff1a; 学完C语言和初阶数据结构&#xff0c;感觉自己又行了&#xff1f; 接下来进入C的学习&#xff0c;准备好接受头脑风暴吧。 一、第一个C程序 C 的第一个程序&#xff0c;梦回出学C语言&#xff0c;第一次使用C语言写代码&#xff1b;这里使用C写第一个C代码。 …

微信文件如何直接打印及打印功能在哪里设置?

在数字化时代&#xff0c;打印需求依旧不可或缺&#xff0c;但传统打印店的高昂价格和不便操作常常让人头疼。幸运的是&#xff0c;琢贝打印作为一款集便捷、经济、高效于一体的网上打印平台&#xff0c;正逐渐成为众多用户的首选。特别是通过微信小程序下单&#xff0c;更是让…

html+css前端作业和平精英2个页面(无js)

htmlcss前端作业和平精英2个页面&#xff08;无js&#xff09;有视频播放器等功能效果 下载地址 https://download.csdn.net/download/qq_42431718/89608232 目录1 目录2 项目视频 和平精英2个页面&#xff08;无js&#xff09;带视频播放 页面1 页面2

MinIO:高性能轻量云存储轻松搭建与springboot应用整合实践

简介 Minio是一款用Golang编写的开源对象存储套件&#xff0c;遵循Apache License v2.0开源协议。它虽然体积小巧&#xff0c;但性能出色。Minio支持亚马逊S3云存储服务接口&#xff0c;可以方便地与其他应用如NodeJS、Redis、MySQL等集成使用。 纠删码技术 Minio纠删码是一…

SpringBoot项目以及相关数据库部署在Linux常用命令

SpringBoot项目部署 1.IDEA打包&#xff0c;在IDEA终端&#xff0c;输入mvn clean install mvn clean package&#xff1a;删除目标文件夹、编译代码并打包mvn clean install&#xff1a;删除目标文件夹、编译代码并打包、将打好的包放置到本地仓库中 2.将项目target中的jar包…