蜘蛛资讯网 > 柳岩 > 九江尚滤健身服务中心

钢铁侠2_用AOP思想改造一个服务器的数据存储

背景是有一个游戏服务器一直以来都是写SQL的, 后来改过一段时间的redis, 用的是别的员工写的类orm方式将实体类型映射成各种key-value对进行写入, 但是仍有一个缺点就是需要在增删改的时候显式调用API, 更糟糕的是要注明删改的字段名, 不然就会整个实体重写入. 实际使用中经常会出现写错字段名, 或是重命名字段之后忘记修改旧的字段名字符串参数, 甚至忘记调用update API导致数据没有保存...(同样的情况也发生在写SQL的时候)

其实增/删/改用到的SQL或是reids api都非常简单, 都可以自动生成, 免去反复书写的麻烦也容易写错的问题.?

qi shi zeng shan gai yong dao de SQL huo shi reids api dou fei chang jian dan, dou ke yi zi dong sheng cheng, mian qu fan fu shu xie de ma fan ye rong yi xie cuo de wen ti.?

于是乎, 我计划通过监视/拦截增/删/改 方法以在数据修改的同时自动生成SQL/Redis调用, 来对改属性值进行保存. 其实增删比较简单, 只要自定义一个集合类型继承下IEnumerable接口, 然后实现增删方法即可, 对原代码的修改量也是极小的.?

但是改就有点麻烦了, 需要拦截属性的set方法, 这是以前没有怎么用过的AOP领域.

一些调查之后, 觉得有两种方式比较适合.

一是静态织入, 就是用类似代码模板的方式, 先写实体类型模板, 然后生成真正的实体类型类库, 在生成的时候进行修改在属性的set方法中注入PropertyChangedNotify事件, 服务中使用的是生成后的实体类库. PostSharp貌似不错, 但是不想引入更多的开销就罢了. 用T4或者上Emit或许不错.

二是动态, 大致上就是生成一个代理类型, 拥有原类型的所有接口, 但是重新给实现了一遍. 可以是组合的方式, 将原类型实例作为私有成员保存, Emit生成所有原类型的开放方法; 也可以是继承的方式, 重载. 考虑到对原代码的修改量, 我觉得继承重载不错.?

比如原实体类型如下:

public class Person
{
    public string Id{get;set;}
    public string Name{get;set;}
}

对这个类实现修改保存的托管, 那么直接使用这个类型是不行的了(除非方案1静态修改注入), 只有使用动态生成的代理实体类, 而如果使用组合方式的代理类型, 在OO的情况下很别扭, 所以我选择用继承重载的方式. 这就需要对这个实体类型进行改造, 将所有需要监视的属性修改成virutal声明 ctrl+H搞定.?

然后, 因为是增删改, 所以Key是必不可少的, 那么实体需要用attribute标记出Key属性, 支持多属性组合Key. 因为原代码用过一段时间的entityframework, 所以这个改造非常顺利.?

最后的类型定义如下:

[DataContract]
public class Person
{
    [Key]  
    public string Id{get;set;}
    public virtual string Name{get;set;}
}

首先用attribute标记Person为需要代理的类型, 便于在实际使用中纠错和预加载, 然后标记Id属性为Key, 并且不需要重载, 其他属性声明为virtual表示需要重载进行监视.

然后手写一个代理类型:

public class PersonProxy : Person
{
    public override string Name
    {
        set
        {
            base.Name= value;
            Interceptor.Invoke(this, "Name", value);
        }
    }  
}

public static class Interceptor
{
    public static void Invoke(object instance, string name, object value)
    {
        //log
    }
}

再定义个Factory的静态类, 实现静态泛型函数Create()来创建Person的代理类型PersonProxy. 其实后面调用的都是代理类型, 只是使用方法和Person一致, 兼容旧代码.

只是这样的话, 还不如使用静态织入了. 所以上面手写的这个PersonProxy需要动态生成, 这样通过泛型来支持所有自定义class, 减少频繁修改. 于是Emit登场了.

不过我对IL和Emit认识浅薄, 我的办法是使用VS自带工具 -- ildasm, 用它打开刚才生成的PersonProxy查看IL代码, 反复对比总结之后终于将这个PersonProxy类型的动态生成用Emit实现成功并修改成泛型通用. 这套特殊的IL学习方法论真是屡试不爽. 方法大致如此抛砖引玉, 所以这里也就不贴emit的代码了. Invoke织入也可以做各种进化, 以达成更高级的功能, 比如invoke在赋值前, 写入失败回退throw exception等等...

这样ProxyFactory.Create()函数就写好了.

在Invoke里生成需要SQL或是redis set方法就可以了.?

最后再实现增删代理的集合类型, 在Add的时候直接将插入的对象通过ProxyFactory.Create()转成proxy子类对象, 为了避免Add之后继续使用传入的原对象而丢失对属性set的监控, 修改Add的声明为Add(ref T obj)强行修改引用为代理对象. 从入口处根绝, 完美.?

理论Ok, 于是封装成类库.?

最后放上代码地址 :?https://github.com/Roytin/SmartDb

当前文章:http://www.azubaweb.com/ew8ydx/421081-1320340-50916.html

发布时间:00:25:38

王中王开奖直播2018??大赢家??7489大赢家??www.11139f.com??www.9066888.com??香港惠泽社群??黄大仙综合资料大全免费一肖中特??最准平特一肖??历史图库??香港开奖结果??