.net 使用IL生成代理类实现AOP对比Java Spring Boot的AOP
首先,我们需要定义一个接口,代表我们要代理的目标对象的功能:
// 日志记录器接口
public interface ILogger
{/// <summary>/// 记录日志/// </summary>/// <param name="message">日志消息</param>void Log(string message);
}// 日志记录器实现类
public class Logger : ILogger
{public void Log(string message){Console.WriteLine($"Logging: {message}");}
}
然后,我们创建一个代理类,实现该接口,并在目标方法的执行前后注入额外的逻辑:
// 切面接口
public interface IInterceptor
{/// <summary>/// 在方法执行前执行的逻辑/// </summary>/// <param name="targetType">目标类型</param>/// <param name="methodName">方法名称</param>void BeforeMethod(Type targetType, string methodName);/// <summary>/// 在方法执行后执行的逻辑/// </summary>/// <param name="targetType">目标类型</param>/// <param name="methodName">方法名称</param>void AfterMethod(Type targetType, string methodName);
}// 日志记录切面类
public class LoggingAspect : IInterceptor
{public void BeforeMethod(Type targetType, string methodName){Console.WriteLine($"Before {targetType.Name}.{methodName}");}public void AfterMethod(Type targetType, string methodName){Console.WriteLine($"After {targetType.Name}.{methodName}");}
}
接下来,我们通过使用IL生成代理类型的字节码,动态创建代理对象:
// 代理生成器
public static class ProxyGenerator
{/// <summary>/// 创建代理对象/// </summary>/// <typeparam name="TInterface">目标接口类型</typeparam>/// <param name="target">目标对象实例</param>/// <param name="interceptor">切面对象实例</param>/// <returns>代理对象</returns>public static TInterface CreateProxy<TInterface>(TInterface target, IInterceptor interceptor)where TInterface : class{Type targetType = typeof(TInterface);TypeBuilder typeBuilder = CreateTypeBuilder(targetType);ImplementInterface(typeBuilder, targetType);ImplementMethods(typeBuilder, targetType, interceptor);Type proxyType = typeBuilder.CreateType();return Activator.CreateInstance(proxyType, target, interceptor) as TInterface;}private static TypeBuilder CreateTypeBuilder(Type targetType){string typeName = targetType.Name + "Proxy";AssemblyName assemblyName = new AssemblyName(typeName);AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(typeName + "Module");TypeBuilder typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class);typeBuilder.AddInterfaceImplementation(targetType);return typeBuilder;}private static void ImplementInterface(TypeBuilder typeBuilder, Type targetType){foreach (MethodInfo method in targetType.GetMethods()){Type[] parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name,MethodAttributes.Public | MethodAttributes.Virtual,method.ReturnType,parameterTypes);typeBuilder.DefineMethodOverride(methodBuilder, method);}}private static void ImplementMethods(TypeBuilder typeBuilder, Type targetType, IInterceptor interceptor){foreach (MethodInfo method in targetType.GetMethods()){MethodBuilder methodBuilder = typeBuilder.GetMethod(method.Name).GetBaseDefinition() as MethodBuilder;if (methodBuilder != null){ILGenerator ilGenerator = methodBuilder.GetILGenerator();// 调用切面方法ilGenerator.Emit(OpCodes.Ldarg_0); // 加载this到堆栈ilGenerator.Emit(OpCodes.Ldfld, typeBuilder.GetField("interceptor")); // 加载interceptor到堆栈ilGenerator.Emit(OpCodes.Ldarg_0); // 加载this到堆栈ilGenerator.Emit(OpCodes.Ldtoken, targetType); // 加载targetType到堆栈ilGenerator.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); // 调用GetTypeFromHandle方法ilGenerator.Emit(OpCodes.Ldstr, method.Name); // 加载方法名到堆栈ilGenerator.Emit(OpCodes.Callvirt, typeof(IInterceptor).GetMethod("BeforeMethod")); // 调用BeforeMethod方法ilGenerator.Emit(OpCodes.Pop); // 丢弃返回值// 调用目标方法ilGenerator.Emit(OpCodes.Ldarg_0); // 加载this到堆栈for (int i = 1; i <= method.GetParameters().Length; i++){ilGenerator.Emit(OpCodes.Ldarg_S, i); // 加载方法参数到堆栈}ilGenerator.Emit(OpCodes.Callvirt, targetType.GetMethod(method.Name)); // 调用目标方法// 调用切面方法ilGenerator.Emit(OpCodes.Ldarg_0); // 加载this到堆栈ilGenerator.Emit(OpCodes.Ldfld, typeBuilder.GetField("interceptor")); // 加载interceptor到堆栈ilGenerator.Emit(OpCodes.Ldarg_0); // 加载this到堆栈ilGenerator.Emit(OpCodes.Ldtoken, targetType); // 加载targetType到堆栈ilGenerator.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); // 调用GetTypeFromHandle方法ilGenerator.Emit(OpCodes.Ldstr, method.Name); // 加载方法名到堆栈ilGenerator.Emit(OpCodes.Callvirt, typeof(IInterceptor).GetMethod("AfterMethod")); // 调用AfterMethod方法ilGenerator.Emit(OpCodes.Pop); // 丢弃返回值}}}
}
最后,我们可以使用以下代码来测试动态代理的功能:
class Program
{static void Main(string[] args){// 创建目标对象ILogger logger = new Logger();// 创建切面对象IInterceptor interceptor = new LoggingAspect();// 创建代理对象ILogger proxy = ProxyGenerator.CreateProxy(logger, interceptor);// 调用代理对象的方法proxy.Log("Hello, World!");Console.ReadLine();}
}
对比一下Java Spring Boot 的AOP
-
动态代理实现方式:在Java Spring Boot中,基于代理的AOP主要使用JDK动态代理和CGLIB代理来实现。而在C#中,使用IL生成器(ILGenerator)直接操作IL指令来生成和修改类型的字节码,实现动态代理。这种方式相对于代理类的生成更加底层,需要对CLR(Common Language Runtime)和IL指令有一定的了解。
-
IL的语法和特性:IL是.NET平台的中间语言,类似于汇编语言,但具有一些.NET特定的语法和特性。IL指令用于描述类型、方法、属性等的结构和操作,需要了解这些指令的使用规则和约束。相比之下,Java字节码(Java bytecode)是Java虚拟机(JVM)上的中间语言,其语法和特性与IL有所不同。
-
第三方库和框架:在Java生态系统中,有许多第三方库和框架(如AspectJ、Spring AOP)提供了高级别的API和工具,使AOP的使用更加方便。而在C#中,虽然也有一些库(如Castle DynamicProxy、Unity Interception)可以辅助实现AOP,但相对于Java生态系统来说,可选择的工具和框架较少
综上所述,C#使用IL实现AOP与Java Spring Boot的AOP在实现方式、编程语言和生态系统等方面存在一些不同。C#开发者需要直接操作IL指令来生成和修改类型的字节码,需要对CLR和IL指令有一定的了解。而Java Spring Boot的AOP则基于代理实现,使用注解和切点表达式等高级别的API来配置和管理AOP。
相关文章:
.net 使用IL生成代理类实现AOP对比Java Spring Boot的AOP
首先,我们需要定义一个接口,代表我们要代理的目标对象的功能: // 日志记录器接口 public interface ILogger {/// <summary>/// 记录日志/// </summary>/// <param name"message">日志消息</param>void L…...
美容店预约小程序搭建流程
随着科技的不断发展,小程序已经成为了人们生活中不可或缺的一部分。对于美容店来说,搭建一个预约小程序不仅可以提高工作效率,还可以增加客户数量、提高服务质量。那么,如何搭建一个美容店预约小程序呢?本文将为你详细…...
ppt 作图 如何生成eps格式
需求 ppt中画的图,按照eps格式导出。 环境 软件: ppt, Gsview(用来将ps格式转成eps), Adobe 操作系统: win11 思路 直接在ppt里选择adobe打印机,将图片以文件形式打印到ps格式的文件中,再由gsview转化成eps。 建议在本身就…...
渗透测试中的前端调试(上)
一、前言 前端调试是安全测试的重要组成部分。它能够帮助我们掌握网页的运行原理,包括js脚本的逻辑、加解密的方法、网络请求的参数等。利用这些信息,我们就可以更准确地发现网站的漏洞,制定出有效的攻击策略。前端知识对于安全来说ÿ…...
跨境电商引流之Reddit营销,入门保姆级攻略
在当今竞争激烈的在线市场中,企业不断寻求新的方法来加强其数字营销工作。Reddit 是最受欢迎的社交媒体平台之一,为企业提供了巨大的潜力,可以通过引人入胜且相关的内容来接触目标受众。然而,将 Reddit 用于营销目的需要仔细考虑某…...
Linux下虚拟网卡的基本命令
文章目录 创建虚拟网卡查看虚拟网卡删除虚拟网卡 创建虚拟网卡 # 创建tap模式的虚拟网卡tap0 sudo ip tuntap add mode tap tap0 # 开启网卡 sudo ip link set tap0 up # 设置网卡的ip地址和子网掩码 sudo ip addr add 192.168.1.1/24 dev tap0查看虚拟网卡 # 查看虚拟网卡ta…...
conan入门(二十七):因profile [env]字段废弃导致的boost/1.81.0 在aarch64-linux-gnu下交叉编译失败
今天在尝试用conan 1.60.0使用aarch64-linux-gnu编译器交叉编译boost/1.81.0时报错了: conan install boost/1.81.0 -pr:h aarch64-linux-gnu.jinja -pr:b default --build boost输出如下: Configuration (profile_host): [settings] archarmv8 arch_b…...
BFS专题7 多终点迷宫问题
题目: 样例: 输入 3 3 0 0 0 1 0 0 0 1 0 输出 0 1 2 -1 2 3 -1 -1 4 思路: 单纯的 BFS 迷宫问题,只是标记一下每个点的 step,注意初始化答案数组都为 -1. 代码详解如下: #include <iostream> #…...
ES6中对象新增了哪些扩展?
一、属性的简写 当对象字面量的属性名与变量名相同时,可以省略属性名,直接使用变量名作为属性名。 const x 10; const y 20;// ES6之前 const obj1 { x: x, y: y };// ES6属性简写 const obj2 { x, y };注意:简写的对象方法不能用作构造…...
蓝桥杯每日一题2023.9.22
4960. 子串简写 - AcWing题库 题目描述 题目分析 原本为纯暴力但是发现会超时,可以加入前缀和,从前往后先记录一下每个位置c1出现的次数 再从前往后扫一遍,如果遇到c2就将答案加上此位置前的所有c1的个数(直接加上此位置的前缀…...
vscode左键无法跳转到定义的文件
之前用vscode的时候,明明是可以ctrl键鼠标左键跳转到定义文件的,突然之间就不行了,鼠标移到引入上根本都没有下划线,无法跳转 解决方法: 项目的根目录新建 jsconfig.json 文件,代码如下 {"compiler…...
c、c++排序的相关知识(归并排序、计数排序、稳定性等)
排序,是对给定的一组数,按照某种逻辑关系,进行位置上的移动。由于排序至少需要将所有数过一遍(正常情况下,非特殊数组),因此排序的时间复杂度一定不能小于O(N)。 归并排…...
oracle定时任务的使用
常见错误: PLS-00225: subprogram or cursor xxx reference is out of scope # job名字太长PLS-00201: identifier COUNT_JOB.SUBMIT must be declared # DBMS_JOB.SUBMIT是固定写法创建存储过程 -- 建表 CREATE TABLE TEST_A(TEST_ADD_DATA DATE); -- 存储过程 C…...
VSCode 配置 Lua 开发环境(清晰明了)
概述 由于 AutoJS 学得已经差不多了,基本都会了,现在开始向其他游戏脚本框架进发, Lua 语言很强大,就不多说, 按键精灵、触动精灵等等都是用该语言编程脚本的,由于按键精灵、触动精灵 和 AutoJS 类似,不是…...
JS合并2个远程pdf
要在HTML和JavaScript中读取远程PDF文件的矢量数据并合并两个PDF文件,您可以使用pdf-lib和Axios库。以下是使用pdf-lib和Axios在HTML和JavaScript中读取和合并远程PDF文件的步骤: 1. 引入 首先,确保您在HTML文件中引入了pdf-lib和Axios库。…...
TikTok的伦理挑战:虚拟世界与现实世界的交汇
在数字时代,社交媒体平台已经不再只是一个信息传播的工具,它已经深刻地改变了我们的社交行为、价值观和伦理观。 而在这一领域的佼佼者之一,TikTok,正面临着伦理挑战,这是虚拟世界与现实世界交汇的产物。 本文将深入…...
C# 获取磁盘空间大小的方法
方法一:利用System.IO.DriveInfo.GetDrives方法来获取 /// 获取指定驱动器的空间总大小(单位为B)////// 只需输入代表驱动器的字母即可 (大写)///public static long GetHardDiskSpace(string str_HardDiskName){long totalSize new long();…...
JVM机制理解与调优方案
作者:逍遥Sean 简介:一个主修Java的Web网站\游戏服务器后端开发者 主页:https://blog.csdn.net/Ureliable 觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言! 前言 很多Java开发…...
Django的设计模式及模板层
Django的设计模式及模板层 设计模式MVC和MVT MVC 代表 Model-View-Controller(模型-视图-控制器)模式。 M 模型层(Model),主要用于对数据库层的封装 V 视图层(View),用于向用户展示结果 (WHAT HOW) C 控制(Controller,用于处理请求、获取数据、返回结果(重要) 作…...
写代码生成流程图
我们在写文档,博客的时候,一般都会使用markdown语法,最常见的就是一些github开源项目的README。有时候会去画一些流程图,例如使用process.on或者xmind等第三方网站,然后截图插入到文档中。 今天我们介绍一种使用代码直…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
