背景

近期在写日志系统,需要在运行时在函数内注入日志记录,并附带函数信息,这时就想到用Aop注入的方式。

AOP分动态注入和静态注入两种注入的方式。

动态注入方式

  1. 利用Remoting的ContextBoundObject或MarshalByRefObject。
  2. 动态代理(反射),很多AOP框架都用这种方式。
  3. MVC的filter,也是反射。

第一种性能太差,必须继承基类等,所以不考虑。

第二种为了记日志,大量动态生成代理类,性能损耗不小,不建议生产环节推荐。

第三种MVC只能进行UI层的拦截,其他层需要实现自行实现动态拦截,跟第二种实现方式一样。

静态注入方式

基于Net的IL语言层级进行注入,性能损耗可以忽略不计,Net使用最多的Aop框架PostSharp采用的即是这种方式。

技术实现

声明Attribute

public class WeaveSign:Attribute
{
}
public class WeaveAction : Attribute
{ }
public class Log : WeaveAction
{
public static void OnActionBefore(MethodBase mbBase)
{
//mbBase 要注入方法的所有信息
var t = mbBase.GetParameters();
       LogManager.Record();
}
}

标记需要注入的方法

[Log]
public static string GetUserName(int userId)
{
return "Vidar";
}

IL注入关键的地方,这里使用Mono.Cecil进行IL分析和写入。

public static void AutoWeave()
{
var assemblies = BuildManager.GetReferencedAssemblies();
var result =
assemblies.Cast<Assembly>().Where(assembly =>!assembly.FullName.StartsWith("System.", StringComparison.OrdinalIgnoreCase)); Weave(result);
} private static void Weave(IEnumerable<Assembly> assemblyList)
{
//assemblyList要注入的程序集列表。
foreach (var item in assemblyList)
{
var filepath = item.CodeBase.Substring(8, item.CodeBase.Length - 8);
//读取程序集
var assembly = AssemblyDefinition.ReadAssembly(filepath); //获取WeaveSign类型的类型注入标记
var types = assembly.MainModule.Types.Where(n => n.CustomAttributes.Any(y => y.AttributeType.Resolve().Name == "WeaveSign")); foreach (var type in types)
{
foreach (var method in type.Methods)
{
//获取WeaveAction类型的方法注入标记
var attrs =
method.CustomAttributes.Where(y => y.AttributeType.Resolve().BaseType.Name == "WeaveAction");
foreach (var attr in attrs)
{
//解析类型
var resolve = attr.AttributeType.Resolve();
//获取IL容器
var ilProcessor = method.Body.GetILProcessor();
var firstInstruction = ilProcessor.Body.Instructions.First();
//找到标记类型的OnActionBefore方法。
var onActionBefore = resolve.GetMethods().Single(n => n.Name == "OnActionBefore");
//创建System.Reflection.MethodBase.GetCurrentMethod()方法引用
var mfReference=assembly.MainModule.Import(typeof (System.Reflection.MethodBase).GetMethod("GetCurrentMethod")); //注入到IL(调用GetCurrentMethod,入栈)
ilProcessor.InsertBefore(firstInstruction,
ilProcessor.Create(OpCodes.Call,mfReference));
//创建调用(call)标记类型的方法OnActionBefore
ilProcessor.InsertBefore(firstInstruction, ilProcessor.Create(OpCodes.Call, onActionBefore));
}
}
}
if (types.Any())
{
//写入程序集
assembly.Write(filepath);
}
}
}

为了演示简便,没有在MsBuild期间进行注入,而是单写了个测试页面直接触发进行代码注入。

  protected void Page_Load(object sender, EventArgs e)
{
CodeWeaver.AutoWeave();
}

运行成功后,反编译注入的DLL看到的IL代码:

  .method public hidebysig static string GetUserName(int32 userId) cil managed
{
.custom instance void TestLibrary.Log::.ctor()
.maxstack 1
.locals init (
[0] string str)
L_0000: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetCurrentMethod()
L_0005: call void TestLibrary.Log::OnActionBefore(class [mscorlib]System.Reflection.MethodBase)
L_000a: nop
L_000b: ldstr "Vidar"
L_0010: stloc.0
L_0011: br.s L_0013
L_0013: ldloc.0
L_0014: ret
}

C#

[Log]
public static string GetUserName(int userId)
{
Log.OnActionBefore(MethodBase.GetCurrentMethod());
return "Vidar";
}

Mono.Cecil地址 http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/

日志系统实战(一)—AOP静态注入的更多相关文章

  1. 日志系统实战(二)-AOP动态获取运行时数据

    介绍 这篇距上一篇已经拖3个月之久了,批评自己下. 通过上篇介绍了解如何利用mono反射代码,可以拿出编译好的静态数据.例如方法参数信息之类的. 但实际情况是往往需要的是运行时的数据,就是用户输入等外 ...

  2. 日志系统实战(三)-分布式跟踪的Net实现

    介绍 在大型系统开发调试中,跨系统之间联调开始变得不好使了.莫名其妙一个错误爆出来了,日志虽然有记录,但到底是哪里出问题了呢? 是Ios端参数传的不对?还是A系统或B系统提供的接口导致?相信有不少人遇 ...

  3. 日志系统实战 AOP静态注入

    http://www.cnblogs.com/mushroom/p/3932698.html http://www.cnblogs.com/mushroom/p/4124878.html http:/ ...

  4. Linux系统实战项目——sudo日志审计

    Linux系统实战项目——sudo日志审计   由于企业内部权限管理启用了sudo权限管理,但是还是有一定的风险因素,毕竟运维.开发等各个人员技术水平.操作习惯都不相同,也会因一时失误造成误操作,从而 ...

  5. 实战3--项目开始--准备:::资源分类, 日志系统, 写BaseDao

     项目资源分类: 1.   package: base, dao, dao.impl, domain, service, service.impl, util, view.action 2.   co ...

  6. Spring Boot 揭秘与实战(三) 日志框架篇 - 如何快速集成日志系统

    文章目录 1. 默认的日志框架 logback2. 常用的日志框架 log4j 1.1. 日志级别 1.2. 日志文件 3. 源代码 Java 有很多日志系统,例如,Java Util Logging ...

  7. 记一次基于springboot+aop实现日志记录实战

    1. 为什么要记录日志 好处: a. 可以对一些重要功能进行记录,方便以后跟踪是谁操作此功能的. b. 在操作某些功能时可能会发生异常,但每次出现异常我们想定位日志都要去服务器查看我们的日志.有了日志 ...

  8. Ansible实战:部署分布式日志系统

    本节内容: 背景 分布式日志系统架构图 创建和使用roles JDK 7 role JDK 8 role Zookeeper role Kafka role Elasticsearch role My ...

  9. 2 (mysql实战) 日志系统

    前面我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块.相信你还记得,一条查询语句的执行过程一般是经过连接器.分析器.优化器.执行器等功能模块,最后到达存储引擎. 那么,一条更新语 ...

随机推荐

  1. Javascript、Jquery获取浏览器和屏幕各种高度宽度

    Javascript: IE中:document.body.clientWidth ==> BODY对象宽度document.body.clientHeight ==> BODY对象高度d ...

  2. ubuntu_tftp服务搭建

    搭建过程: 1. sudo apt-get install tftpd-hpa tftp-hpa是客户端 tftpd-hpa是服务器端 2.建立目录 执行:mkdir /home/wmx/Deskto ...

  3. 【iOS自定义键盘及键盘切换】详解

    [iOS自定义键盘]详解 实现效果展示: 一.实现的协议方法代码 #import <UIKit/UIKit.h> //创建自定义键盘协议 @protocol XFG_KeyBoardDel ...

  4. Gridview中几个Button的应用

    gridview中有三种方式添加button的应用,CommandField.ButtonField.TemplateField中加Button这三种方式.三种方式都可以实现同样的功能,但在实现某些功 ...

  5. 巴特沃斯(Butterworth)滤波器 (2) - 双线性变换

    这里接着上篇讲一下双线性变换Bilinear Transformation,它实现了模拟信号(连续域)与数字信号(离散域)之间的转换. 双线性变换公式如下: 反推可得到: 因此可以根据连续域传递函数推 ...

  6. Laravel使用笔记 —— migration

    在使用 php artisan make:migration 创建migration时,可用 --path 指定创建migration文件的路径, 如果在执行的 php artisan migrate ...

  7. 初识Scala反射

    我们知道,scala编译器会将scala代码编译成JVM字节码,编译过程中会擦除scala特有的一些类型信息,在scala-2.10以前,只能在scala中利用java的反射机制,但是通过java反射 ...

  8. 用SQL Server(T-SQL)获取连接字符串

    一般情况下,C# 连接SQL Server的字符串可以直接按照说明文档直接手动写出来,或者也可以参考大名鼎鼎的connectionstrings手动拼写 但是如果你已经连接到SQL Server也可以 ...

  9. 关于MVC的开源商城 Nop之闲聊

    nopcommerce是国外的一个高质量的开源b2c网站系统,基于EntityFramework4.0和MVC3.0,使用Razor模板引擎,有很强的插件机制,包括支付配送功能都是通过插件来实现的,基 ...

  10. <十三>JDBC_dbcp数据库连接池

    配置文件:jdbc.properties username=rootpassword=kkdriverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://1 ...