实战动态代理

news2024/11/16 3:18:55

代理模式介绍

代理模式有点像老大和小弟,也有点像分销商。主要解决的是问题是为某些资源的访问、对象的类的易用操作上提供方便使用的代理服务。而这种设计思想的模式经常会出现在我们的系统中,或者你用到过的组件中,它们都提供给你一种非常简单易用的方式控制原本你需要编写很多代码的进行使用的服务类。

类似这样的场景可以想到;

  1. 你的数据库访问层面经常会提供一个较为基础的应用,以此来减少应用服务扩容时不至于数据库连接数暴增。

  1. 使用过的一些中间件例如;RPC框架,在拿到jar包对接口的描述后,中间件会在服务启动的时候生成对应的代理类,当调用接口的时候,实际是通过代理类发出的socket信息进行通过。

  1. 另外像我们常用的MyBatis,基本是定义接口但是不需要写实现类,就可以对xml或者自定义注解里的sql语句进行增删改查操作。

动态代理的两种实现方式

一、JDK 动态代理机制

介绍

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

java.lang.reflect.Proxy

这个方法一共有 3 个参数:

  1. loader :类加载器,用于加载代理对象。

  1. interfaces : 被代理类实现的一些接口;

  1. h : 实现了 InvocationHandler 接口的对象;

要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。当我们的动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

java.lang.reflect.InvocationHandler

invoke() 方法有下面三个参数:

  1. proxy :动态生成的代理类

  1. method : 与代理类对象调用的方法相对应

  1. args : 当前 method 方法的参数

也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

JDK 动态代理类使用步骤

  1. 定义一个接口及其实现类;

  1. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;

  1. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;

简单代码使用示例

1.定义发送短信的接口

public interface SmsService {
    String send(String message);
}

2.实现发送短信的接口

public class SmsServiceImpl implements SmsService {
    @Override
    public String send(String message) {
        System.out.println("来到了SmsServiceImpl:send message"+message);
        return message;
    }
}

3.定义一个 JDK 动态代理类

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

public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     *
     * @param proxy jdk创建的代理对象,无需复制
     * @param method 目标类中的方法
     * @param args 目标类中方法的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("来到了代理类DebugInvocationHandler");
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}

4.获取代理对象的工厂类

import java.lang.reflect.Proxy;

public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new DebugInvocationHandler(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}

测试

代码

    @Test
    public void jdkProxy(){
        SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
        smsService.send("穗爷来也");
    }

结果

来到了代理类DebugInvocationHandler
before method send
来到了SmsServiceImpl:send message穗爷来也
after method send

二、CGLIB 动态代理机制

介绍

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。

为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。

CGLIB[3](Code Generation Library)是一个基于ASM[4]的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB[5], 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

你需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。

net.sf.cglib.proxy.MethodInterceptor

  1. obj :被代理的对象(需要增强的对象)

  1. method :被拦截的方法(需要增强的方法)

  1. args :方法入参

  1. methodProxy :用于调用原始方法

你可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

CGLIB 动态代理类使用步骤

  1. 定义一个类;

  1. 自定义 MethodInterceptor 并重写 intercept 方法,intercept用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;

  1. 通过 Enhancer 类的 create()创建代理类;

简单实用示例

不同于 JDK 动态代理不需要额外的依赖。CGLIB[6](Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。

         <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

1.实现一个使用发送邮箱的类

public class EMailService {
    public String send(String message){
        System.out.println("send message:"+message);
        return message;
    }
}

2.自定义 MethodInterceptor(方法拦截器)

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class DebugMethodInterceptor implements MethodInterceptor {
    /**
     *
     * @param obj 被代理的对象(需要增强的对象)
     * @param method 被拦截的方法(需要增强的方法)
     * @param args 方法入参
     * @param proxy 用于调用原始方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("来到了代理类DebugMethodInterceptor");
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object object = proxy.invokeSuper(obj, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }
}

3.获取代理类工厂

public class CglibProxyFactory {
    public static Object getProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加裁器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

测试

代码

    @Test
    public void CglibProxy(){
        EMailService aliSmsService = (EMailService) CglibProxyFactory.getProxy(EMailService.class);
        aliSmsService.send("穗爷来也");
    }

结果

来到了代理类DebugMethodInterceptor
before method send
send message:穗爷来也
after method send

JDK 动态代理和 CGLIB 动态代理对比

  1. JDK 动态代理只能只能代理实现了接口的类,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

  1. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

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

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

相关文章

【CVPR 2018】PU-Net: Point Cloud Upsampling Network

文章目录PU-Net: Point Cloud Upsampling Network网络架构训练数据生成点特征嵌入Feature ExpansionCfoordinate Reconstruction端到端训练Joint Loss FunctionPU-Net: Point Cloud Upsampling Network 网络架构 PU-Net有四个组件&#xff1a;patch extraction, point feature…

「自定义类型」C语言中的构造数据类型如结构,联合,枚举

​​​​​​​&#x1f680;&#x1f680;&#x1f680;大家觉不错的话&#xff0c;就恳求大家点点关注&#xff0c;点点小爱心&#xff0c;指点指点&#x1f680;&#x1f680;&#x1f680;目录 &#x1f430;结构 &#x1f3e1; 前言 &#x1f338;数据类型的定义 &…

SpringBoot动态导出word文档(完美实整教程 复制即可使用,不能实现你找我)

背景 最近有一个需求是需要动态导出合同、订单等信息&#xff0c;导出一个word文档供客户进行下载查看。 需要导出的word文件&#xff0c;主要可以分为两种类型。 导出固定内容和图片的word文档导出表格内容不固定的word文档 经过对比工具&#xff0c;我实践过两种实现方式…

一文细说引导内存分配器

一、引导内存分配器 1.引导内存分配器的作用 因为内核里面有很多内存结构体&#xff0c;不可能在静态编译阶段就静态初始化所有的这些内存结构体。另外&#xff0c;在系统启动过程中&#xff0c;系统启动后的物理内存分配器本身也需要初始化&#xff0c;如伙伴分配器&#xff…

OD笔试题-空汽水瓶可以换汽水

/*** 某商店规定&#xff1a;三个空汽水瓶可以换一瓶汽水&#xff0c;允许向老板借空汽水瓶&#xff08;但是必须要归还&#xff09;。* 小张手上有n个空汽水瓶&#xff0c;她想知道自己最多可以喝到多少瓶汽水。* 数据范围&#xff1a;输入的正整数满足 1≤n≤100* <p>*…

springboot 多环境配置yml

创建多个配置文件 创建文件时注意&#xff0c;一定是 application-文件名称.yml 这种格式 application.yml #主配置文件 application-dev.yml #开发环境的配置 application-prod.yml #生产环境的配置application-prod.yml # 生产环境端口为90 server:port: 90applica…

Python实现将一段话txt生成字幕srt文件

Python实现将一段话txt生成字幕srt文件 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、为什么要将txt转换成字幕 1.1方便到剪辑软件剪辑 有时获取到一段文本&#xff0c;想要直…

点分治学习笔记

有时候我们会碰到一些树上的路径问题&#xff0c;如果需要处理的规模很大的话&#xff0c;这时候点分治是一个很好的工具&#xff0c;往往可以在O(nlogn)的复杂度内完成操作&#xff0c;一般用于离线处理问题 前置芝士 树的重心&#xff1a;最大子树的值最小的点叫做重心。 …

【手撕面试题】HTML+CSS(高频知识点二)

目录 面试官&#xff1a;页面导入样式时&#xff0c;使用 link 和 import 有什么区别&#xff1f; 面试官&#xff1a;简要说说 title与h1的区别、b与strong的区别、i与em的区别&#xff1f; 面试官&#xff1a;img标签的title和alt有什么区别&#xff1f; 面试官&#xff…

给特别规则FeignClient增加统一的RequestInterceptor

需求背景&#xff1a; 在微服务横行天下的今天&#xff0c;Spring Cloud OpenFeign 早就成了我们服务间调度的主要远程调用手段。 在Feign进行远程调度的时候&#xff0c;难免会做一些心跳&#xff0c;权限的校验&#xff0c;来保证调度过程的相对安全。 但对于外部系统来说…

Unity 之 Addressable可寻址系统 -- 资源远程加载 | 资源预下载 -- 进阶(三)

可寻址系统远程加载 -- 资源预下载 -- 进阶&#xff08;三&#xff09;一&#xff0c;Unity 云资源分发 -- 使用介绍1.1 CCD 的介绍1.2 后台准备工作二&#xff0c;CDD的使用2.1 CCD可视化界面的使用2.2 CDD命令行界面使用2.2.1 准备工作2.2.2 CLI 用法三&#xff0c;AA CCD资…

Java中的快速排序

快速排序递归版本挖坑法Hoare法优化非递归相信即使大家并不知道快速排序究竟是个啥,但也一定听说过快排,今天我来给兄弟们讲讲快速排序!递归版本 快速排序的思想就是找基准,就比如我们以数组中的第一个数字12为基准,我们从最后往前面找,如果找到一个比12小的数字就用它覆盖12,但…

Linux—InstallOS-RedHat9.1

下载https://developers.redhat.com/products/rhel/download 需注册账号。安装正常安装就行。安装注意事项&#xff1a;(1)Software SelectionCentOS的摘录过来&#xff0c;通用。最小安装&#xff08;Minimal Install&#xff09;这个选项只提供运行CentOS 的基本软件包。最小…

Python学习-----起步4(列表元素的添加,删除,修改,查询,获取长度)

目录 前言&#xff1a; 列表元素的添加&#xff08;或者叫写入&#xff09; 1.append&#xff08;&#xff09;函数 2.extend&#xff08;&#xff09;函数 3.insert()函数 列表元素的删除 1.remove() 函数 2. pop() 函数 3.clear&#xff08;&#xff09;函数 4.del …

公司40岁的程序員到底在写什么代码

去年在前公司玩了一年&#xff08;基本兩三個月一個需求&#xff09;&#xff0c;除了日常維護就一些特別簡單的功能開發&#xff0c;到年底也沒見到公司黃&#xff08;國企背景&#xff09;&#xff0c;沒辦法只好裸辭&#xff0c;現在這個公司各方面还不错&#xff0c;但是令…

Cookie、Session、Token、JWT只看这一篇文章就够了

什么是认证&#xff08;Authentication&#xff09; 通俗地讲就是验证当前用户的身份&#xff0c;证明“你是你自己”&#xff08;比如&#xff1a;你每天上下班打卡&#xff0c;都需要通过指纹打卡&#xff0c;当你的指纹和系统里录入的指纹相匹配时&#xff0c;就打卡成功&a…

MongoDB Map Reduce

在用 MongoDB 查询时&#xff0c;若返回的数据量很大&#xff0c;或者做一些比较复杂的统计和聚合操作做花费的时间很长时&#xff0c;可以使用 MongoDB 中的 mapReduce 进行实现。mapReduce 是个灵活且强大的数据聚合工具&#xff0c;它的好处是可以把一个聚合任务分解为多个小…

设计模式(三)----创建型模式之单例模式(一)

一、创建型模式 创建型模式的主要关注点是“怎样创建对象&#xff1f;”&#xff0c;它的主要特点是“将对象的创建与使用分离”。 这样可以降低系统的耦合度&#xff0c;使用者不需要关注对象的创建细节。 创建型模式分为&#xff1a; 单例模式 工厂方法模式 抽象工厂模式…

英语学习 3

1 词汇积累 1、ships 船 2、class 级 3、marvels 奇迹 4、marvelous 非凡的、了不起的、极好的 5、cursed 诅咒、被诅咒的 6、the most luxurious ships 最豪华的船 7、luxury 奢侈、奢华的 8、luxurious 心满意足的、舒适的 9、utmost 极度的、最大的 10、kind 种类 11、voya…

Kali Linux神秘工具教程(详细版)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、Kali Linux - 安装和配置信息收集工具二、NMAP隐形扫描搜索Searchsploit域名系统工具dnsenum.plDNSMAPdnstracerLBDHping3漏洞分析工具Cisco-torch工具Cisco…