如何將Ado.net中的事務(wù)抽離到業(yè)務(wù)層

最近在實(shí)現(xiàn)一個(gè)簡(jiǎn)單的審批功能般贼,涉及到一些事務(wù)的處理。一個(gè)管控臺(tái)項(xiàng)目骑科,我使用的是最簡(jiǎn)單的三層架構(gòu)橡淑。使用的是Ado.net操作數(shù)據(jù)庫(kù)。先看一段我們經(jīng)常用到的事務(wù)代碼:

SqlConnection con = new Sqlconnection("數(shù)據(jù)庫(kù)連接語(yǔ)句");
con.Open();
var trans = con.BeginTransaction();
try
{
     SqlCommand com = new SqlCommand(trans);
     //處理插入或更新邏輯
     trans.Commit();
}catch(ex){
     trans.Rollback();//如果前面有異常則事務(wù)回滾
}
finally
{
     con.Close();
}

我一直以來(lái)都是使用下面這種方式在Dao來(lái)處理事務(wù)纵散,其實(shí)怎么看都覺(jué)得他別扭梳码,就拿我做的審批功能來(lái)說(shuō),當(dāng)前審批人通過(guò)之后需要生成一條審批記錄(記作表ApprovalHistory)伍掀,同時(shí)將當(dāng)前申請(qǐng)單的當(dāng)前審批人指向下一個(gè)處理者(記作Apply)掰茶,而一般的三層架構(gòu)都會(huì)有自身的Service層,理想的情況應(yīng)該是在Service層用事務(wù)來(lái)處理相應(yīng)的邏輯蜜笤。
正常的處理邏輯應(yīng)該是將事務(wù)提取出來(lái)濒蒋,應(yīng)用層不應(yīng)該過(guò)多的去關(guān)心底層的實(shí)現(xiàn)。亂糟糟的代碼寫在一起分層感覺(jué)也沒(méi)啥意思了把兔。如果可以像Spring那樣使用Annotation的方式就一行代碼來(lái)實(shí)現(xiàn)事務(wù)這樣不是很好嗎沪伙。這個(gè)問(wèn)題確實(shí)想了挺久,后來(lái)有位boss和我說(shuō)了下他的實(shí)現(xiàn)县好,喜出望外围橡!先貼貼代碼實(shí)現(xiàn)

Boss的實(shí)現(xiàn)

public class ConnId
{
    private int mConnId = 0;
    private DateTime mCreateTime = DateTime.Now;
             
    public int MConnId
    {
        get { return this.mConnId; }
    }

    public ConnId(int connid)
    {
        this.mConnId = connid;
    }
}

public class DbConnection
{
    private static readonly string sConnStr = "從配置中讀取連接字符串";
    public static readonly long MAX_LAST_TIME_LEN = 10 * 1000 * 1000 * 60; 

    private ConnId mConnId = null;
    private SqlConnection mConn = null;
    private SqlCommand mSqlCmd= null;
    private DateTime mCreateTime = DateTime.Now;
    private string mTransName = "";

    public ConnId MConnId
    {
        get { return this.mConnId; }
    }

    public SqlConnection Connection
    {
        get { return this.mConn; }
    }

    public SqlCommand SqlCmd
    {
        get { return this.mSqlCmd; }
    }

    public ConnectionState State
    {
        get
        {
            if (mConn == null)
            {
                return ConnectionState.Broken;
            }
            else
            {
                return mConn.State;
            }
        }
    }

    public DbConnection()
    {
    }

    internal ConnId ConnOpen(HttpRequest request)
    {
        try
        {
            this.mConn = new SqlConnection(sConnStr);
            this.mSqlCmd = new SqlCommand();
            mSqlCmd.Connection = this.mConn;
            this.mConnId = new ConnId(this.GetHashCode());
            mConn.Open();
            if (request == null)
            {
                mTransName = "null";
            }
            else
            {
                mTransName = GetSrcFileName(request);
            }
            mSqlCmd.Transaction =
                mConn.BeginTransaction(System.Data.IsolationLevel.ReadCommitted,
                mTransName);
            //log

        }
        catch (Exception e)
        {
            //log
            if (this.mConn.State != System.Data.ConnectionState.Closed)
            {
                this.mConn.Close();
                this.mConn.Dispose();
            }
            this.mConnId = null;
        }
        return this.mConnId;
    }

    internal ConnId ConnOpen(string src)
    {
        try
        {
            this.mConn = new SqlConnection(sConnStr);
            this.mSqlCmd = new SqlCommand();
            mSqlCmd.Connection = this.mConn;
            this.mConnId = new ConnId(this.GetHashCode());
            mConn.Open();
            mTransName = src;
            mSqlCmd.Transaction =
                mConn.BeginTransaction(System.Data.IsolationLevel.ReadCommitted,
                mTransName);
            //log
        }
        catch (Exception e)
        {
            //log
            if (this.mConn.State != System.Data.ConnectionState.Closed)
            {
                this.mConn.Close();
                this.mConn.Dispose();
            }
            this.mConnId = null;
        }
        return this.mConnId;
    }

    internal Exception Cancel()
    {
        Exception ex = null;
        try
        {
            //log
            mSqlCmd.Transaction.Rollback();
        }
        catch (Exception e)
        {
            //log
            mSqlCmd.Transaction.Rollback();
            ex = e;
        }
        finally
        {
            if (mSqlCmd != null)
            {
                this.mSqlCmd.Dispose();
            }
            if (this.mConn.State != System.Data.ConnectionState.Closed)
            {
                this.mConn.Close();
            }

            memberClear();
        }
        return ex;
    }

    internal Exception Close()
    {
        Exception ex = null;
        try
        {
            //log
            mSqlCmd.Transaction.Commit();
        }
        catch (Exception e)
        {
            //log
            mSqlCmd.Transaction.Rollback();
            ex = e;
        }
        finally
        {
            if (mSqlCmd != null)
            {
                this.mSqlCmd.Dispose();
            }
            if (this.mConn.State != System.Data.ConnectionState.Closed)
            {
                this.mConn.Close();
                this.mConn.Dispose();
            }

            memberClear();
        }
        return ex;
    }

    internal bool IfExpried()
    {
        if (mConnId != null)
        {
            if (mCreateTime != DateTime.MinValue)
            {
                DateTime now = DateTime.Now;
                if (now.Ticks - this.mCreateTime.Ticks > MAX_LAST_TIME_LEN)
                {
                    return true;
                }
            }
        }
        return false;
    }

    private void memberClear()
    {
        mConnId = null;
        mConn = null;
        mSqlCmd= null;
        mCreateTime = DateTime.MinValue;
    }

    private string GetSrcFileName(HttpRequest r)
    {
        FileInfo fi = new FileInfo(r.PhysicalPath);
        string filename = fi.Name.Replace(fi.Extension, "");
        if (filename.Length > 32)
        {
            filename = filename.Substring(filename.Length - 32, 32);
        }
        return filename;
    }

    private string GetSrcFileName(string src)
    {
        return src.Substring(src.Length - 32, 32);
    }

    private string GetShortTime(DateTime t)
    {
        string str = t.Day.ToString() + "_" + t.Hour.ToString() + ":" +
            t.Minute.ToString() + ":" + t.Second.ToString();
        return str;
    }
}

public class ConnPool
{
    private static readonly object sLocker = new object();
    private static Dictionary<ConnId, DbConnection> sConnList = new Dictionary<ConnId, DbConnection>(MAX_CONCURRENT_CONN_COUNT);
    private static readonly int MAX_CONCURRENT_CONN_COUNT = 1000;

    public static int sCount
    {
        get { return sConnList.Keys.Count; }
    }

    public static ConnId CreateConn(HttpRequest request)
    {
        DbConnection dbconn = null;
        ConnId key = null;
        try
        {
            dbconn = new DbConnection();
            key = dbconn.ConnOpen(request);
            if (sConnList.ContainsKey(key))
            {
                return key;
            }
            if (sCount < MAX_CONCURRENT_CONN_COUNT)
            {
                lock (sLocker)
                {
                    if (sCount < MAX_CONCURRENT_CONN_COUNT)
                    {
                        sConnList.Add(key, dbconn);
                    }
                    else
                    {
                       //log
                    }
                }
            }
        }
        catch (Exception e)
        {
            //log
            key = null;
        }
        return key;
    }

    public static ConnId CreateConn(string src)
    {
        DbConnection dbconn = null;
        ConnId key = null;
        try
        {
            dbconn = new DbConnection();
            key = dbconn.ConnOpen(src);
            if (sCount < MAX_CONCURRENT_CONN_COUNT)
            {
                lock (sLocker)
                {
                    if (sCount < MAX_CONCURRENT_CONN_COUNT)
                    {
                        sConnList.Add(key, dbconn);
                    }
                    else
                    {
                        //log
                    }
                }
            }
        }
        catch (Exception e)
        {
            //log
            key = null;
        }
        return key;
    }

    public static int ReleaseConn(ConnId connid)
    {
        DbConnection conn = null;
        try
        {
            lock (sLocker)
            {
                conn = sConnList[connid];
                sConnList.Remove(connid);
            }
            if (conn != null)
            {
                conn.Close();
            }
        }
        catch (Exception e)
        {
            //log
        }
        return 0;
    }

    public static int CancelConn(ConnId connid)
    {
        DbConnection conn = null;
        try
        {
            lock (sLocker)
            {
                conn = sConnList[connid];
                sConnList.Remove(connid);
            }
            if (conn != null)
            {
                conn.Cancel();
            }
        }
        catch (Exception e)
        {
            //log
        }
        return 0;
    }

    public static DbConnection GetDbConn(ConnId connid)
    {
        return sConnList[connid];
    }
}

然后使用的時(shí)候呢就像這樣就可以:

namespace MyTest
{
    class Program
    {
        static void Main(string[] args)
        {
            ConnId conn = ConnPool.CreateConn("123");
            try
            {
                var aservice = new ApplyService();
                var historyService = new ApprovalHistoryService();
                aservice.Update(new object(), conn);
                historyService.Insert(new object(), conn);

                ConnPool.ReleaseConn(conn);
            }
            catch (Exception ex)
            {
                ConnPool.CancelConn(conn);
            }
        }
    }
}

可以看到這里主要是在ConnPool中使用一個(gè)Dictionary加雙重檢查鎖定來(lái)實(shí)現(xiàn)一個(gè)并發(fā)連接的處理,用于記錄當(dāng)前的數(shù)據(jù)庫(kù)連接缕贡。而到執(zhí)行Insert和Update時(shí)他就通過(guò)connid在ConnPool中取出對(duì)應(yīng)事務(wù)中的SqlCommand來(lái)執(zhí)行相應(yīng)的Sql 翁授。也就是說(shuō)他將數(shù)據(jù)庫(kù)連接及事務(wù)抽離,放到了一個(gè)ConnPool中管理晾咪∈詹粒基本符合我預(yù)期。

問(wèn)題來(lái)了

如果認(rèn)真看著里面的實(shí)現(xiàn)大家應(yīng)該會(huì)發(fā)現(xiàn)這里存在一些問(wèn)題谍倦。

1 并發(fā)字典的實(shí)現(xiàn)

頭一個(gè)我想到的就是雙重檢查鎖定的問(wèn)題塞赂,雖然老總說(shuō)他們用了那么久一直沒(méi)有什么問(wèn)題,但我想說(shuō)那是因?yàn)椴l(fā)量不大所以沒(méi)有發(fā)現(xiàn)問(wèn)題昼蛀,并發(fā)量大的情況下使用lock的性能是明顯下降的宴猾,這就讓我想起了Java中從HashMap 到 HashTable 再到 ConcurrentHashMap的轉(zhuǎn)變圆存。HashMap是非線程安全類,所以用在多線程環(huán)境下就很可能出現(xiàn)意想不到的結(jié)果仇哆。所以才有了HashTable辽剧,我印象當(dāng)中HashTale的實(shí)現(xiàn)是在HashMap的基礎(chǔ)上為每個(gè)方法加了synchronized關(guān)鍵字,所以每次Add或Remove都會(huì)鎖住整個(gè)內(nèi)部的數(shù)組税产,可以想象一下在多線程環(huán)境下這里面的操作會(huì)有多慢。所以才有了ConcurrentHashMap的實(shí)現(xiàn)偷崩,其內(nèi)部使用的是可重入鎖辟拷,而鎖住的是每一個(gè)segment段而不是整個(gè)數(shù)組。更重要的是鎖的實(shí)現(xiàn)(基礎(chǔ)框架是隊(duì)列同步器AbstractQueuedSynchronizer)阐斜,追究到最底層實(shí)現(xiàn)是使用CAS加自旋衫冻,一種樂(lè)觀鎖的方式來(lái)實(shí)現(xiàn),從而保證了并發(fā)性谒出。從HashMap 到 HashTable 再到 ConcurrentHashMap的轉(zhuǎn)變真讓我回味良久隅俘,從里面數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)到并發(fā)的處理真是妙不可言。學(xué)習(xí)Java的朋友應(yīng)該知道這可以從放騰飛中的《并發(fā)編程的藝術(shù)》中了解到笤喳,初學(xué)者看可能會(huì)暈为居,我第一次看了一小部分后是拒絕的,因?yàn)榭吹孟胪律苯疲睦镌谥淞R這他媽都寫的什么鬼蒙畴,哈哈!再后來(lái)慢慢看就有所體會(huì)了呜象,而且有些地方還要反復(fù)琢磨膳凝。從這本書可以了解到很多并發(fā)編程的底層實(shí)現(xiàn),極力推薦9Ф浮5乓簟!
??所以我也并不推薦自己去實(shí)現(xiàn)一個(gè)線程安全的Dictionary休玩,因?yàn)槔镞吷婕暗教嗟募?xì)節(jié)不是我們所能預(yù)料的著淆,除非自己真的非常熟悉底層的實(shí)現(xiàn),對(duì)并發(fā)編程了然于胸哥捕∧脸椋看過(guò).Net中的 Dictionary實(shí)現(xiàn)后會(huì)發(fā)現(xiàn)它與HashMap的實(shí)現(xiàn)大體無(wú)異,雖然沒(méi)有看過(guò).Net中ConcurrentDictionary的實(shí)現(xiàn)遥赚,但是個(gè)人感覺(jué)他們的實(shí)現(xiàn)大體上應(yīng)該相差無(wú)幾羔飞。所以可以考慮使用ConcurrentDictionary來(lái)替換老總ConnPool的內(nèi)部實(shí)現(xiàn),這部分代碼就不貼了拓颓。

2 非托管資源的釋放

對(duì)于非托管資源的釋放我建議是使用繼承接口IDisposable來(lái)實(shí)現(xiàn)其Dispose()方法,具體請(qǐng)參考.Net圣經(jīng) -《CLR via C# 第4版 》孕惜。

改進(jìn)

雖然老總的實(shí)現(xiàn)能抽離的底層的實(shí)現(xiàn),但是還不夠優(yōu)雅晨炕,因?yàn)樵诖a的最后都要去手動(dòng)實(shí)現(xiàn)事務(wù)的提交和回滾衫画。那有沒(méi)有更好的辦法來(lái)實(shí)現(xiàn)不用手動(dòng)提交和回滾呢,就像Spring中事務(wù)瓮栗,只需要在方法中加注解就可以達(dá)到指定功能削罩。當(dāng)然初期先來(lái)一個(gè)簡(jiǎn)單的實(shí)現(xiàn)。.Net中的Attribute對(duì)應(yīng)的就是Java中的注解费奸,但是這個(gè)Attribute還必須具備Aop的功能弥激。這樣才可以在方法執(zhí)行前開(kāi)啟一個(gè)事務(wù),方法執(zhí)行完成后提交或回滾事務(wù)愿阐。

1. Aop

一般使用的較多的是Autofac和Castle微服,當(dāng)然還可以使用Remoting代理方式或者從ContextBoundObject中派生來(lái)實(shí)現(xiàn)。剛好找到一篇文章說(shuō)到了.net實(shí)現(xiàn)Aop的七種方式缨历。

Approach Advantages Disadvantages
Remoting Proxies Easy to implement, because of the .Net framework support Somewhat heavyweight Can only be used on interfaces or MarshalByRefObjects
Derivingfrom ContextBoundObject Easiest to implement Native support for call interception Very costly in terms of performance
Compile-time subclassing ( Rhino Proxy ) Easiest to understand Interfaces or virtual methods only
Runtime subclassing ( Castle Dynamic Proxy ) Easiest to understand Very flexible Complex implementation (but alreadyexists) Interfaces or virtual methods only
Hooking into the profiler API ( Type Mock ) Extremely powerful Performance? Complex implementation (COM API, require separate runner, etc)
Compile time IL-weaving ( Post Sharp / Cecil ) Very powerful Good performance Very hard to implement
Runtime IL-weaving ( Post Sharp / Cecil ) Very powerful Good performance Very hard to implement

當(dāng)然這只是前人做的一個(gè)總結(jié)以蕴,具體的性能及優(yōu)缺點(diǎn)還需要自己去考量。
編譯時(shí)AOP工具有:PostSharp辛孵、LinFu丛肮、SheepAspect、Fody觉吭、CIL操作工具腾供。
運(yùn)行時(shí)AOP工具:Castle Windsor/DynamicProxy、StructureMap鲜滩、Unity伴鳖、Spring.NET。
?? 我用得比較多的是運(yùn)行時(shí)Aop徙硅,比如Castle榜聂、Autofac.他們都是使用動(dòng)態(tài)代理的方式來(lái)實(shí)現(xiàn)。來(lái)看看Castle是怎么實(shí)現(xiàn)的

 using Castle.DynamicProxy;
 using System;
 
class Program
{
    static void Main(string[] args)
    {
        ProxyGenerator generator = new ProxyGenerator();
        SimpleInterceptor interceptor = new SimpleInterceptor();

        Person person = generator.CreateClassProxy<Person>(interceptor);
        person.SayHello();

        Console.Read();
    }
}

public class Person
{
    public virtual void SayHello()
    {
        Console.WriteLine("hello world.");
    }

    public virtual void SayName(string hometown)
    {
        Console.WriteLine("I'm Lynch, I'm from {0}.", hometown);
    }

    public void SayOther()
    {
        Console.WriteLine("Yeah, I'm Chinese.");
    }
}
 
public class SimpleInterceptor : StandardInterceptor
{
    protected override void PreProceed(IInvocation invocation)
    {
        Console.WriteLine("before invocation , method : {0}.", invocation.Method.Name);
        base.PreProceed(invocation);
    }

    protected override void PerformProceed(IInvocation invocation)
    {
        Console.WriteLine("before performing ...");
        base.PerformProceed(invocation);
        Console.WriteLine("after performing...");
    }

    protected override void PostProceed(IInvocation invocation)
    {
        Console.WriteLine("after invacation , method : {0}.", invocation.Method.Name);
        base.PostProceed(invocation);
    }
} 

動(dòng)態(tài)代理的方式有個(gè)不好的地方就是每次都要生成指定類型的代理類嗓蘑,要實(shí)現(xiàn)Aop的方法還必須是virtual方法. 如果有很多類很多方法需要攔截那增加和改動(dòng)的代碼就有點(diǎn)多须肆。我想達(dá)到的目標(biāo)是只要一個(gè)Attribute類,不需要生成指定類型的代理類桩皿,讓代碼看起來(lái)更加的干凈豌汇。一直以來(lái)我只知道有運(yùn)行時(shí)Aop,就沒(méi)有想到編譯時(shí)Aop泄隔,比如postsharp拒贱。使用il注入的方式就非常的靈活,粒度夠細(xì),精確到每一條指令逻澳。然后就找到了Mono.Cecil 闸天,通過(guò)改寫中間IL代碼的方式來(lái)實(shí)現(xiàn),大體思路是

  1. 找到標(biāo)記有指定AopAttribute的方法
  2. 復(fù)制該方法并生成一個(gè)新的方法copy_method斜做,復(fù)制完成后清楚原有方法
  3. 改寫原有方法苞氮,首先調(diào)用AopAttribute的OnStart方法,接著調(diào)用copy_method 瓤逼,調(diào)用AopAttribute的OnSuccess方法笼吟,最后調(diào)用AopAttribute的OnEnd方法

postsharp 1.5 使用注意事項(xiàng),.net Framework 必須是3.5版本霸旗,需要在csproj中增加以下內(nèi)容(1.5之后的版本需要收費(fèi))

  <PropertyGroup>
    <PostSharpUseCommandLine>True</PostSharpUseCommandLine>
    <DontImportPostSharp>True</DontImportPostSharp>
    <PostSharpDirectory>libs</PostSharpDirectory>
  </PropertyGroup>
  
 <Import Project="$(PostSharpDirectory)\PostSharp-1.5.targets" />
2. IL

說(shuō)到IL指令就要先知道什么是evaluation stack赞厕。而這個(gè)evaluation stack卻不同于我們平時(shí)理解的Call Stack(調(diào)用棧),即在調(diào)用一個(gè)方法時(shí)首先會(huì)將所有參數(shù)壓棧定硝,壓棧完成后調(diào)用指定方法,方法執(zhí)行完成清理剛剛?cè)霔5膮?shù)毫目。我寫這個(gè)IL指令的時(shí)候我也納悶了蔬啡,我就在想我大學(xué)的時(shí)候用Intel x86匯編自己實(shí)現(xiàn)小型操作系統(tǒng)的時(shí)候也沒(méi)有遇到這個(gè)東西啊,說(shuō)到棧就Call Stack镀虐,這evaluation stack(以下簡(jiǎn)稱EStack)他媽是什么鬼箱蟆。后來(lái)向朋友了解了一下才知道這個(gè)是CIL特有的東西,這個(gè)是個(gè)寄存器刮便,即Stack< Register > 空猜。這樣就說(shuō)通了,我忘了操作結(jié)果的存放恨旱,學(xué)過(guò)匯編或了解一些底層的同學(xué)應(yīng)該了解辈毯,匯編語(yǔ)言的操作結(jié)果都是存放的寄存器中,如32位的ax , bx搜贤,64位 eax谆沃、ebx等通用寄存器。而不同的CPU又有不同的指令集仪芒,如PC機(jī)使用的是x86復(fù)雜指令集唁影,而Arm使用的是Arm的精簡(jiǎn)指令集,而CLR直接將兼容不同的寄存器的工作交給使用者處理的話那使用者勢(shì)必想瘋掉掂名,所以VM做一個(gè)通用的寄存器來(lái)存放操作結(jié)果据沈,至于該使用哪個(gè)寄存器來(lái)存放使用者不需要關(guān)心。
??至于為什么設(shè)計(jì)成棧的結(jié)構(gòu)饺蔑,個(gè)人理解一個(gè)是棧有內(nèi)存限制锌介,我們一般使用到的臨時(shí)變量局部變量或者方法參數(shù)都不會(huì)太多,當(dāng)然也可能很多膀钠,太多參數(shù)的話就該考慮封裝了掏湾。二個(gè)我覺(jué)得更重要的是它非常符合調(diào)用方法前將參數(shù)出入Call Stack的操作裹虫,例如我們來(lái)執(zhí)行一下偽代碼:

            int a = 123;
            Service service = new Service("lynch");
            var b = service.GetNumber();
            var result = service.Add(a,b);

在執(zhí)行Add方法前會(huì)先調(diào)用GetNumber來(lái)獲取b的值,整個(gè)代碼的執(zhí)行指令是

IL_0001:  ldc.i4.s  123   將123賦值給寄存器即放到EStack中 
IL_0003:  stloc.0         將EStack中索引為0的值出棧融击,并將出棧的值push到Call Stack作為Add方法的入?yún)?

限于篇幅剩下的IL代碼就不貼了筑公,從第二行的st前綴指令大家應(yīng)該可以發(fā)現(xiàn) : 這個(gè)指令包含了兩個(gè)操作,一個(gè)從EStack出棧尊浪,二個(gè)將出棧的值入棧Call Stack匣屡。EStack是通過(guò)ld入棧而st出棧,就是說(shuō)使用到某個(gè)參數(shù)的時(shí)候就將其從EStack出棧拇涤,而無(wú)需再占用椀纷鳎空間,也就釋放了棧內(nèi)存鹅士,是不是有點(diǎn)像Call Stack的操作券躁。個(gè)人一些見(jiàn)解,不足之處還望指正掉盅。

在IL指令中我們會(huì)頻繁用到如ld ( load )也拜、st(store)等前綴指令,ld前綴指令的意思就是將寄存器的值壓棧趾痘,也就是將EStack中的值push到Call Stack慢哈,而st前綴指令就是將Call Stack中的操作結(jié)果存放到寄存器EStack中。大家可以通過(guò)這篇文章做個(gè)基本了解

如果想深入了解的可以看《Inside Microsoft .NET IL Assembler》永票,中文版是《Microsoft.NET IL匯編語(yǔ)言程序設(shè)計(jì)》卵贱,不過(guò)中文版已經(jīng)絕版,網(wǎng)上可以找到很多影印版pdf侣集。

3. Aop的實(shí)現(xiàn)

這個(gè)應(yīng)該算是postsharp的簡(jiǎn)單實(shí)現(xiàn)键俱,源碼放在了Github LeoxAop上。代碼我就不貼了世分,很多地方都有注釋方妖,而且邏輯還算清晰。這里的實(shí)現(xiàn)部分參考了MSBuild + MSILInect實(shí)現(xiàn)編譯時(shí)AOP之預(yù)覽這位博主的實(shí)現(xiàn)罚攀,不過(guò)他寫的應(yīng)該過(guò)于匆忙党觅,所以代碼結(jié)構(gòu)有些凌亂,不太容易看懂斋泄,還用了很多的linq杯瞻。
??這里有一個(gè)待解決的問(wèn)題是將代碼注入到指定項(xiàng)目exe或dll后怎么讓VS調(diào)試到指定的AopAttribute代碼,也就是說(shuō)怎么生成對(duì)應(yīng)的pdb文件讓VS感知到炫掐。就像.net reflector 一樣魁莉,反編譯dll后自動(dòng)生成對(duì)應(yīng)的pdb文件,然后就可以順利的調(diào)試。我目前想到的較為簡(jiǎn)單的方法是在開(kāi)發(fā)者命令中使用ildasm 將文件反編譯為il代碼旗唁,然后再使用ilasm生成對(duì)應(yīng)的pdb文件 :

ildasm test.exe /out=test.il
ilasm test.il /pdb

不過(guò)我試過(guò)發(fā)現(xiàn)并沒(méi)有起作用畦浓,哪位朋友知道的麻煩告訴我一下,萬(wàn)分感激检疫。就因?yàn)檫@個(gè)也花了不少時(shí)間讶请,搜google搜codeproject都沒(méi)有找到相關(guān)的文章,實(shí)在沒(méi)辦法先擱放在這里屎媳,太過(guò)糾結(jié)容易崩潰夺溢。本以為很快能結(jié)篇,還不料涉及的東西有點(diǎn)多烛谊,寫代碼調(diào)試解決遇到的bug也花不少時(shí)間风响。這里是.net 的實(shí)現(xiàn),其實(shí)java也可以這么實(shí)現(xiàn)丹禀,只是要了解java的字節(jié)碼状勤,有時(shí)間再寫吧。

擴(kuò)展閱讀

4. Transaction 實(shí)現(xiàn)

終于寫到了這了荧降,迫不及待啊。感嘆時(shí)間飛快攒读。寫完這個(gè)接下來(lái)我想看的東西就是node里邊Promise和Async的實(shí)現(xiàn)。既然Aop功能已經(jīng)實(shí)現(xiàn)辛友,那我們就可以在OnStart方法開(kāi)始一個(gè)事務(wù)薄扁,在OnSuccess和OnException提交或回滾事務(wù),但是這里還有幾個(gè)問(wèn)題需要考慮:

  1. 就是如果使用者的在自身業(yè)務(wù)代碼就已經(jīng)做了異常捕獲該如何處理废累,是該回滾還是該提交邓梅,這個(gè)還沒(méi)想出來(lái)好的解決辦法。
  2. 如果有部分連接未能及時(shí)釋放又該如何處理邑滨,對(duì)于這個(gè)問(wèn)題可以考慮啟動(dòng)一個(gè)線程來(lái)監(jiān)控日缨,根據(jù)連接開(kāi)始創(chuàng)建的時(shí)間來(lái)做判斷。
  3. 底層dao操作需要用到當(dāng)前連接創(chuàng)建的SqlCommand掖看,要獲取到這個(gè)那就需要記錄連接Id匣距,問(wèn)題是這個(gè)Id只有在相應(yīng)Aop的On事件時(shí)才能拿到。還有沒(méi)有其他辦法呢哎壳,因?yàn)槊總€(gè)線程執(zhí)行時(shí)用的連接Id都不一樣所以我想到一個(gè)辦法就是將這個(gè)變量放到ThreadLocal中毅待,線程跑到哪里它就跟到哪,每個(gè)線程都維持著自己的連接Id归榕。如果這個(gè)問(wèn)題不解決那設(shè)計(jì)這一整個(gè)抽象事務(wù)就沒(méi)用了尸红。可能應(yīng)該還有更好的辦法,還沒(méi)來(lái)得及去看Spring中實(shí)現(xiàn)外里,如果有朋友想到更好的辦法麻煩告訴我一聲怎爵,感激不盡!
  4. 連接Id如何保證唯一性盅蝗。當(dāng)前我使用的是guid生成鳖链,只是一個(gè)臨時(shí)的策略》缈疲考慮到分布式架構(gòu)的話這種生成方式就不太好管理撒轮,也不穩(wěn)妥。我比較喜歡 Twitter 分布式自增Id的實(shí)現(xiàn)snowflake贼穆,說(shuō)喜歡是因?yàn)樗膶?shí)現(xiàn)粒度很細(xì)题山,但是沒(méi)有考慮到它強(qiáng)依賴機(jī)器時(shí)鐘,如果機(jī)器上時(shí)鐘回?fù)芄嗜瑫?huì)導(dǎo)致發(fā)號(hào)重復(fù)或者服務(wù)會(huì)處于不可用狀態(tài)顶瞳。當(dāng)然也可以參考MongoDb中ObjectId 的生成方式。后來(lái)有幸看到朋友轉(zhuǎn)發(fā)的一篇文章愕秫,里面有說(shuō)到唯一Id的多種生成方式慨菱,還介紹了snowflake及ObjectId的優(yōu)缺點(diǎn),最后講到一種新的生成算法Leaf戴甩,大家可以了解下
    Leaf——來(lái)自美團(tuán)點(diǎn)評(píng)的分布式ID生成系統(tǒng)
    ??當(dāng)然如果是分布式架構(gòu)那沒(méi)這么簡(jiǎn)單了符喝,需要考慮分布式事務(wù),涉及兩階段三階段提交甜孤、分布式一致性算法 paxos协饲。不過(guò)現(xiàn)在更多的應(yīng)該考慮放棄強(qiáng)一致性的分布式事務(wù)而使用最終一致性。如eBay的實(shí)現(xiàn)缴川,在設(shè)計(jì)上就不采用分布式事務(wù)茉稠,而是通過(guò)其它途徑來(lái)解決數(shù)據(jù)一致性問(wèn)題。其中使用的最重要的技術(shù)就是消息隊(duì)列和消息應(yīng)用狀態(tài)表把夸。至于阿里和京東怎么實(shí)現(xiàn)就沒(méi)有深入了解過(guò)而线。eBay 實(shí)現(xiàn)參考 :

最后 Transaction 的實(shí)現(xiàn)請(qǐng)參見(jiàn) Leox.Transaction

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恋日,隨后出現(xiàn)的幾起案子膀篮,更是在濱河造成了極大的恐慌,老刑警劉巖岂膳,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件各拷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡闷营,警方通過(guò)查閱死者的電腦和手機(jī)烤黍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門知市,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人速蕊,你說(shuō)我怎么就攤上這事嫂丙。” “怎么了规哲?”我有些...
    開(kāi)封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵跟啤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我唉锌,道長(zhǎng)隅肥,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任袄简,我火速辦了婚禮腥放,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绿语。我一直安慰自己秃症,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布吕粹。 她就那樣靜靜地躺著种柑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪匹耕。 梳的紋絲不亂的頭發(fā)上聚请,一...
    開(kāi)封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音稳其,去河邊找鬼驶赏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛欢际,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矾兜,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼损趋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了椅寺?” 一聲冷哼從身側(cè)響起浑槽,我...
    開(kāi)封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎返帕,沒(méi)想到半個(gè)月后桐玻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荆萤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年镊靴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铣卡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡偏竟,死狀恐怖煮落,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情踊谋,我是刑警寧澤蝉仇,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站殖蚕,受9級(jí)特大地震影響轿衔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜睦疫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一害驹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧笼痛,春花似錦裙秋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至刻坊,卻和暖如春枷恕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谭胚。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工徐块, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人灾而。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓胡控,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親旁趟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昼激,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,303評(píng)論 25 707
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司锡搜,掛了不少橙困,但最終還是拿到小米、百度耕餐、阿里凡傅、京東、新浪肠缔、CVTE夏跷、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,277評(píng)論 11 349
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法哼转,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法拓春,繼承相關(guān)的語(yǔ)法释簿,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 31,664評(píng)論 18 399
  • 那年我23歲硼莽,剛大學(xué)畢業(yè)庶溶,心不知何去何從,因朋友的牽線懂鸵,回到了故鄉(xiāng)偏螺,獲得了一份代課老師的工作,工作地點(diǎn)是一個(gè)部隊(duì)大...
    光燦閱讀 238評(píng)論 0 1
  • 請(qǐng)善待身邊那些愛(ài)講道理的人 我是一個(gè)程序猿匆光,喜歡思考套像,有強(qiáng)迫癥,看不得bug(漏洞)终息,說(shuō)話留有余地(兼顧異常cas...
    Kaelinda閱讀 6,467評(píng)論 2 4