国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C# > 正文

利用C#實現(xiàn)AOP常見的幾種方法詳解

2020-01-24 00:28:35
字體:
供稿:網(wǎng)友

前言

AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態(tài)代理實現(xiàn)程序功能的中統(tǒng)一處理業(yè)務邏輯的一種技術(shù),比較常見的場景是:日志記錄,錯誤捕獲、性能監(jiān)控等

AOP的本質(zhì)是通過代理對象來間接執(zhí)行真實對象,在代理類中往往會添加裝飾一些額外的業(yè)務代碼,比如如下代碼:

 class RealA { public virtual string Pro { get; set; }  public virtual void ShowHello(string name) { Console.WriteLine($"Hello!{name},Welcome!"); } }  //調(diào)用:  var a = new RealA(); a.Pro = "測試"; a.ShowHello("夢在旅途");

這段代碼很簡單,只是NEW一個對象,然后設(shè)置屬性及調(diào)用方法,但如果我想在設(shè)置屬性前后及調(diào)用方法前后或報錯都能收集日志信息,該如何做呢?可能大家會想到,在設(shè)置屬性及調(diào)用方法前后都加上記錄日志的代碼不就可以了,雖然這樣是可以,但如果很多地方都要用到這個類的時候,那重復的代碼是否太多了一些吧,所以我們應該使用代理模式或裝飾模式,將原有的真實類RealA委托給代理類ProxyRealA來執(zhí)行,代理類中在設(shè)置屬性及調(diào)用方法時,再添加記錄日志的代碼就可以了,這樣可以保證代碼的干凈整潔,也便于代碼的后期維護。(注意,在C#中若需被子類重寫,父類必需是虛方法或虛屬性virtual)

如下代碼:

class ProxyRealA : RealA {  public override string Pro { get { return base.Pro; } set { ShowLog("設(shè)置Pro屬性前日志信息"); base.Pro = value; ShowLog($"設(shè)置Pro屬性后日志信息:{value}"); } }  public override void ShowHello(string name) { try { ShowLog("ShowHello執(zhí)行前日志信息"); base.ShowHello(name); ShowLog("ShowHello執(zhí)行后日志信息"); } catch(Exception ex) { ShowLog($"ShowHello執(zhí)行出錯日志信息:{ex.Message}"); } }  private void ShowLog(string log) { Console.WriteLine($"{DateTime.Now.ToString()}-{log}"); } }  //調(diào)用: var aa = new ProxyRealA(); aa.Pro = "測試2"; aa.ShowHello("zuowenjun.cn");

這段代碼同樣很簡單,就是ProxyRealA繼承自RealA類,即可看成是ProxyRealA代理RealA,由ProxyRealA提供各種屬性及方法調(diào)用。這樣在ProxyRealA類內(nèi)部屬性及方法執(zhí)行前后都有統(tǒng)一記錄日志的代碼,不論在哪里用這個RealA類,都可以直接用ProxyRealA類代替,因為里氏替換原則,父類可以被子類替換,而且后續(xù)若想更改日志記錄代碼方式,只需要在ProxyRealA中更改就行了,這樣所有用到的ProxyRealA類的日志都會改變,是不是很爽。

上述執(zhí)行結(jié)果如下圖示:

以上通過定義代理類的方式能夠?qū)崿F(xiàn)在方法中統(tǒng)一進行各種執(zhí)行點的攔截代碼邏輯處理,攔截點(或者稱為:橫切面,切面點)一般主要為:執(zhí)行前,執(zhí)行后,發(fā)生錯誤,雖然解決了之前直接調(diào)用真實類RealA時,需要重復增加各種邏輯代碼的問題,但隨之而來的新問題又來了,那就是當一個系統(tǒng)中的類非常多的時候,如果我們針對每個類都定義一個代理類,那么系統(tǒng)的類的個數(shù)會成倍增加,而且不同的代理類中可能某些攔截業(yè)務邏輯代碼都是相同的,這種情況同樣是不能允許的,那有沒有什么好的辦法呢?答案是肯定的,以下是我結(jié)合網(wǎng)上資源及個人總結(jié)的如下幾種常見的實現(xiàn)AOP的方式,各位可以參考學習。

第一種:靜態(tài)織入,即:在編譯時,就將各種涉及AOP攔截的代碼注入到符合一定規(guī)則的類中,編譯后的代碼與我們直接在RealA調(diào)用屬性或方法前后增加代碼是相同的,只是這個工作交由編譯器來完成。

PostSharp:PostSharp的Aspect是使用Attribute實現(xiàn)的,我們只需事先通過繼承自O(shè)nMethodBoundaryAspect,然后重寫幾個常見的方法即可,如:OnEntry,OnExit等,最后只需要在需要進行AOP攔截的屬性或方法上加上AOP攔截特性類即可。由于PostSharp是靜態(tài)織入的,所以相比其它的通過反射或EMIT反射來說效率是最高的,但PostSharp是收費版本的,而且網(wǎng)上的教程比較多,我就不在此重復說明了。

第二種:EMIT反射,即:通過Emit反射動態(tài)生成代理類,如下Castle.DynamicProxy的AOP實現(xiàn)方式,代碼也還是比較簡單的,效率相對第一種要慢一點,但對于普通的反射來說又高一些,代碼實現(xiàn)如下:

using Castle.Core.Interceptor;using Castle.DynamicProxy;using NLog;using NLog.Config;using NLog.Win32.Targets;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace ConsoleApp{ class Program { static void Main(string[] args) { ProxyGenerator generator = new ProxyGenerator(); var test = generator.CreateClassProxy<TestA>(new TestInterceptor()); Console.WriteLine($"GetResult:{test.GetResult(Console.ReadLine())}"); test.GetResult2("test"); Console.ReadKey(); } }  public class TestInterceptor : StandardInterceptor { private static NLog.Logger logger;  protected override void PreProceed(IInvocation invocation) { Console.WriteLine(invocation.Method.Name + "執(zhí)行前,入?yún)ⅲ? + string.Join(",", invocation.Arguments)); }  protected override void PerformProceed(IInvocation invocation) { Console.WriteLine(invocation.Method.Name + "執(zhí)行中"); try { base.PerformProceed(invocation); } catch (Exception ex) { HandleException(ex); } }  protected override void PostProceed(IInvocation invocation) { Console.WriteLine(invocation.Method.Name + "執(zhí)行后,返回值:" + invocation.ReturnValue); }  private void HandleException(Exception ex) { if (logger == null) { LoggingConfiguration config = new LoggingConfiguration();  ColoredConsoleTarget consoleTarget = new ColoredConsoleTarget(); consoleTarget.Layout = "${date:format=HH//:MM//:ss} ${logger} ${message}"; config.AddTarget("console", consoleTarget);  LoggingRule rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget); config.LoggingRules.Add(rule1); LogManager.Configuration = config;  logger = LogManager.GetCurrentClassLogger(); //new NLog.LogFactory().GetCurrentClassLogger(); } logger.ErrorException("error",ex); } }  public class TestA { public virtual string GetResult(string msg) { string str = $"{DateTime.Now.ToString("yyyy-mm-dd HH:mm:ss")}---{msg}"; return str; }  public virtual string GetResult2(string msg) { throw new Exception("throw Exception!"); } }}

簡要說明一下代碼原理,先創(chuàng)建ProxyGenerator類實例,從名字就看得出來,是代理類生成器,然后實例化一個基于繼承自StandardInterceptor的TestInterceptor,這個TestInterceptor是一個自定義的攔截器,最后通過generator.CreateClassProxy<TestA>(new TestInterceptor())動態(tài)創(chuàng)建了一個繼承自TestA的動態(tài)代理類,這個代理類只有在運行時才會生成的,后面就可以如代碼所示,直接用動態(tài)代理類對象實例Test操作TestA的所有屬性與方法,當然這里需要注意,若需要被動態(tài)代理類所代理并攔截,則父類的屬性或方法必需是virtual,這點與我上面說的直接寫一個代理類相同。

上述代碼運行效果如下:

第三種:普通反射+利用Remoting的遠程訪問對象時的直實代理類來實現(xiàn),代碼如下,這個可能相比以上兩種稍微復雜一點:

以上代碼實現(xiàn)步驟說明:

1.這里定義的一個真實類AopClass必需繼承自ContextBoundObject類,而ContextBoundObject類又直接繼承自MarshalByRefObject類,表明該類是上下文綁定對象,允許在支持遠程處理的應用程序中跨應用程序域邊界訪問對象,說白了就是可以獲取這個真實類的所有信息,以便可以被生成動態(tài)代理。

2.定義繼承自ProxyAttribute的代理特性標識類AopAttribute,以表明哪些類可以被代理,同時注意重寫CreateInstance方法,在CreateInstance方法里實現(xiàn)通過委托與生成透明代理類的過程,realProxy.GetTransparentProxy() 非常重要,目的就是根據(jù)定義的AopProxy代理類獲取生成透明代理類對象實例。

3.實現(xiàn)通用的AopProxy代理類,代理類必需繼承自RealProxy類,在這個代理類里面重寫Invoke方法,該方法是統(tǒng)一執(zhí)行被代理的真實類的所有方法、屬性、字段的出入口,我們只需要在該方法中根據(jù)傳入的IMessage進行判斷并實現(xiàn)相應的攔截代碼即可。

4.最后在需要進行Aop攔截的類上標注AopAttribute即可(注意:被標識的類必需是如第1條說明的繼承自ContextBoundObject類),在實際調(diào)用的過程中是感知不到任何的變化。且AopAttribute可以被子類繼承,也就意味著所有子類都可以被代理并攔截。

如上代碼運行效果如下:

這里順便分享微軟官方如果利用RealProxy類實現(xiàn)AOP的,詳見地址:https://msdn.microsoft.com/zh-cn/library/dn574804.aspx

第四種:反射+ 通過定義統(tǒng)一的出入口,并運用一些特性實現(xiàn)AOP的效果,比如:常見的MVC、WEB API中的過濾器特性 ,我這里根據(jù)MVC的思路,實現(xiàn)了類似的MVC過濾器的AOP效果,只是中間用到了反射,可能性能不佳,但效果還是成功實現(xiàn)了各種攔截,正如MVC一樣,既支持過濾器特性,也支持Controller中的Action執(zhí)行前,執(zhí)行后,錯誤等方法實現(xiàn)攔截

實現(xiàn)思路如下:

A.過濾器及Controller特定方法攔截實現(xiàn)原理:

1.獲取程序集中所有繼承自Controller的類型;

2.根據(jù)Controller的名稱找到第1步中的對應的Controller的類型:FindControllerType

3.根據(jù)找到的Controller類型及Action的名稱找到對應的方法:FindAction

4.創(chuàng)建Controller類型的實例;

5.根據(jù)Action方法找到定義在方法上的所有過濾器特性(包含:執(zhí)行前、執(zhí)行后、錯誤)

6.執(zhí)行Controller中的OnActionExecuting方法,隨后執(zhí)行執(zhí)行前的過濾器特性列表,如:ActionExecutingFilter

7.執(zhí)行Action方法,獲得結(jié)果;

8.執(zhí)行Controller中的OnActionExecuted方法,隨后執(zhí)行執(zhí)行后的過濾器特性列表,如:ActionExecutedFilter

9.通過try catch在catch中執(zhí)行Controller中的OnActionError方法,隨后執(zhí)行錯誤過濾器特性列表,如:ActionErrorFilter

10.最后返回結(jié)果;

B.實現(xiàn)執(zhí)行路由配置效果原理:

1.增加可設(shè)置路由模板列表方法:AddExecRouteTemplate,在方法中驗證controller、action,并獲取模板中的占位符數(shù)組,最后保存到類全局對象中routeTemplates;

2.增加根據(jù)執(zhí)行路由執(zhí)行對應的Controller中的Action方法的效果:Run,在該方法中主要遍歷所有路由模板,然后與實行執(zhí)行的請求路由信息通過正則匹配,若匹配OK,并能正確找到Controller及Action,則說明正確,并最終統(tǒng)一調(diào)用:Process方法,執(zhí)行A中的所有步驟最終返回結(jié)果。

需要說明該模擬MVC方案并沒有實現(xiàn)Action方法參數(shù)的的綁定功能,因為ModelBinding本身就是比較復雜的機制,所以這里只是為了搞清楚AOP的實現(xiàn)原理,故不作這方面的研究,大家如果有空可以實現(xiàn),最終實現(xiàn)MVC不僅是ASP.NET MVC,還可以是Console MVC,甚至是Winform MVC等。

以下是實現(xiàn)的全部代碼,代碼中我已進行了一些基本的優(yōu)化,可以直接使用:

public abstract class Controller{ public virtual void OnActionExecuting(MethodInfo action) {  }  public virtual void OnActionExecuted(MethodInfo action) {  }  public virtual void OnActionError(MethodInfo action, Exception ex) {  } } public abstract class FilterAttribute : Attribute{ public abstract string FilterType { get; } public abstract void Execute(Controller ctrller, object extData);} public class ActionExecutingFilter : FilterAttribute{ public override string FilterType => "BEFORE";  public override void Execute(Controller ctrller, object extData) { Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionExecutingFilter中攔截發(fā)出的消息!-{DateTime.Now.ToString()}"); }} public class ActionExecutedFilter : FilterAttribute{ public override string FilterType => "AFTER";  public override void Execute(Controller ctrller, object extData) { Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionExecutedFilter中攔截發(fā)出的消息!-{DateTime.Now.ToString()}"); }} public class ActionErrorFilter : FilterAttribute{ public override string FilterType => "EXCEPTION";  public override void Execute(Controller ctrller, object extData) { Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionErrorFilter中攔截發(fā)出的消息!-{DateTime.Now.ToString()}-Error Msg:{(extData as Exception).Message}"); }} public class AppContext{ private static readonly Type ControllerType = typeof(Controller); private static readonly Dictionary<string, Type> matchedControllerTypes = new Dictionary<string, Type>(); private static readonly Dictionary<string, MethodInfo> matchedControllerActions = new Dictionary<string, MethodInfo>(); private Dictionary<string,string[]> routeTemplates = new Dictionary<string, string[]>();   public void AddExecRouteTemplate(string execRouteTemplate) { if (!Regex.IsMatch(execRouteTemplate, "{controller}", RegexOptions.IgnoreCase)) {  throw new ArgumentException("執(zhí)行路由模板不正確,缺少{controller}"); }  if (!Regex.IsMatch(execRouteTemplate, "{action}", RegexOptions.IgnoreCase)) {  throw new ArgumentException("執(zhí)行路由模板不正確,缺少{action}"); }  string[] keys = Regex.Matches(execRouteTemplate, @"(?<={)/w+(?=})", RegexOptions.IgnoreCase).Cast<Match>().Select(c => c.Value.ToLower()).ToArray();  routeTemplates.Add(execRouteTemplate,keys); }  public object Run(string execRoute) { //{controller}/{action}/{id} string ctrller = null; string actionName = null; ArrayList args = null; Type controllerType = null; bool findResult = false;  foreach (var r in routeTemplates) {  string[] keys = r.Value;  string execRoutePattern = Regex.Replace(r.Key, @"{(?<key>/w+)}", (m) => string.Format(@"(?<{0}>.[^///]+)", m.Groups["key"].Value.ToLower()), RegexOptions.IgnoreCase);   args = new ArrayList();  if (Regex.IsMatch(execRoute, execRoutePattern))  {  var match = Regex.Match(execRoute, execRoutePattern);  for (int i = 0; i < keys.Length; i++)  {   if ("controller".Equals(keys[i], StringComparison.OrdinalIgnoreCase))   {   ctrller = match.Groups["controller"].Value;   }   else if ("action".Equals(keys[i], StringComparison.OrdinalIgnoreCase))   {   actionName = match.Groups["action"].Value;   }   else   {   args.Add(match.Groups[keys[i]].Value);   }  }   if ((controllerType = FindControllerType(ctrller)) != null && FindAction(controllerType, actionName, args.ToArray()) != null)  {   findResult = true;   break;  }  } }  if (findResult) {  return Process(ctrller, actionName, args.ToArray()); } else {  throw new Exception($"在已配置的路由模板列表中未找到與該執(zhí)行路由相匹配的路由信息:{execRoute}"); } }  public object Process(string ctrller, string actionName, params object[] args) { Type matchedControllerType = FindControllerType(ctrller);  if (matchedControllerType == null) {  throw new ArgumentException($"未找到類型為{ctrller}的Controller類型"); }  object execResult = null; if (matchedControllerType != null) {  var matchedController = (Controller)Activator.CreateInstance(matchedControllerType);  MethodInfo action = FindAction(matchedControllerType, actionName, args);  if (action == null)  {  throw new ArgumentException($"在{matchedControllerType.FullName}中未找到與方法名:{actionName}及參數(shù)個數(shù):{args.Count()}相匹配的方法");  }    var filters = action.GetCustomAttributes<FilterAttribute>(true);  List<FilterAttribute> execBeforeFilters = new List<FilterAttribute>();  List<FilterAttribute> execAfterFilters = new List<FilterAttribute>();  List<FilterAttribute> exceptionFilters = new List<FilterAttribute>();   if (filters != null && filters.Count() > 0)  {  execBeforeFilters = filters.Where(f => f.FilterType == "BEFORE").ToList();  execAfterFilters = filters.Where(f => f.FilterType == "AFTER").ToList();  exceptionFilters = filters.Where(f => f.FilterType == "EXCEPTION").ToList();  }   try  {  matchedController.OnActionExecuting(action);   if (execBeforeFilters != null && execBeforeFilters.Count > 0)  {   execBeforeFilters.ForEach(f => f.Execute(matchedController, null));  }   var mParams = action.GetParameters();  object[] newArgs = new object[args.Length];  for (int i = 0; i < mParams.Length; i++)  {   newArgs[i] = Convert.ChangeType(args[i], mParams[i].ParameterType);  }   execResult = action.Invoke(matchedController, newArgs);   matchedController.OnActionExecuted(action);   if (execBeforeFilters != null && execBeforeFilters.Count > 0)  {   execAfterFilters.ForEach(f => f.Execute(matchedController, null));  }   }  catch (Exception ex)  {  matchedController.OnActionError(action, ex);   if (exceptionFilters != null && exceptionFilters.Count > 0)  {   exceptionFilters.ForEach(f => f.Execute(matchedController, ex));  }  }   }  return execResult;  }  private Type FindControllerType(string ctrller) { Type matchedControllerType = null; if (!matchedControllerTypes.ContainsKey(ctrller)) {  var assy = Assembly.GetAssembly(typeof(Controller));   foreach (var m in assy.GetModules(false))  {  foreach (var t in m.GetTypes())  {   if (ControllerType.IsAssignableFrom(t) && !t.IsAbstract)   {   if (t.Name.Equals(ctrller, StringComparison.OrdinalIgnoreCase) || t.Name.Equals($"{ctrller}Controller", StringComparison.OrdinalIgnoreCase))   {    matchedControllerType = t;    matchedControllerTypes[ctrller] = matchedControllerType;    break;   }   }  }  } } else {  matchedControllerType = matchedControllerTypes[ctrller]; }  return matchedControllerType; }  private MethodInfo FindAction(Type matchedControllerType, string actionName, object[] args) { string ctrlerWithActionKey = $"{matchedControllerType.FullName}.{actionName}"; MethodInfo action = null; if (!matchedControllerActions.ContainsKey(ctrlerWithActionKey)) {  if (args == null) args = new object[0];  foreach (var m in matchedControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public))  {  if (m.Name.Equals(actionName, StringComparison.OrdinalIgnoreCase) && m.GetParameters().Length == args.Length)  {   action = m;   matchedControllerActions[ctrlerWithActionKey] = action;   break;  }  } } else {  action = matchedControllerActions[ctrlerWithActionKey]; }  return action; }}

使用前,先定義一個繼承自Controller的類,如:TestController,并重寫相應的方法,或在指定的方法上加上所需的過濾器特性,如下代碼所示:

public class TestController : Controller{ public override void OnActionExecuting(MethodInfo action) { Console.WriteLine($"{action.Name}執(zhí)行前,OnActionExecuting---{DateTime.Now.ToString()}"); }  public override void OnActionExecuted(MethodInfo action) { Console.WriteLine($"{action.Name}執(zhí)行后,OnActionExecuted--{DateTime.Now.ToString()}"); }  public override void OnActionError(MethodInfo action, Exception ex) { Console.WriteLine($"{action.Name}執(zhí)行,OnActionError--{DateTime.Now.ToString()}:{ex.Message}"); }  [ActionExecutingFilter] [ActionExecutedFilter] public string HelloWorld(string name) { return ($"Hello World!->{name}"); }  [ActionExecutingFilter] [ActionExecutedFilter] [ActionErrorFilter] public string TestError(string name) { throw new Exception("這是測試拋出的錯誤信息!"); }  [ActionExecutingFilter] [ActionExecutedFilter] public int Add(int a, int b) { return a + b; }}

最后前端實際調(diào)用就非常簡單了,代碼如下:

class MVCProgram{ static void Main(string[] args) { try {  var appContext = new AppContext();  object rs = appContext.Process("Test", "HelloWorld", "夢在旅途");  Console.WriteLine($"Process執(zhí)行的結(jié)果1:{rs}");   Console.WriteLine("=".PadRight(50, '='));   appContext.AddExecRouteTemplate("{controller}/{action}/{name}");  appContext.AddExecRouteTemplate("{action}/{controller}/{name}");   object result1 = appContext.Run("HelloWorld/Test/夢在旅途-zuowenjun.cn");  Console.WriteLine($"執(zhí)行的結(jié)果1:{result1}");   Console.WriteLine("=".PadRight(50, '='));   object result2 = appContext.Run("Test/HelloWorld/夢在旅途-zuowenjun.cn");  Console.WriteLine($"執(zhí)行的結(jié)果2:{result2}");   Console.WriteLine("=".PadRight(50, '='));   appContext.AddExecRouteTemplate("{action}/{controller}/{a}/{b}");  object result3 = appContext.Run("Add/Test/500/20");  Console.WriteLine($"執(zhí)行的結(jié)果3:{result3}");   object result4 = appContext.Run("Test/TestError/夢在旅途-zuowenjun.cn");  Console.WriteLine($"執(zhí)行的結(jié)果4:{result4}"); } catch (Exception ex) {  Console.ForegroundColor = ConsoleColor.Red;  Console.WriteLine($"發(fā)生錯誤:{ex.Message}");  Console.ResetColor(); }  Console.ReadKey(); }}

可以看到,與ASP.NET MVC有點類似,只是ASP.NET MVC是通過URL訪問,而這里是通過AppContext.Run 執(zhí)行路由URL 或Process方法,直接指定Controller、Action、參數(shù)來執(zhí)行。

通過以上調(diào)用代碼可以看出路由配置還是比較靈活的,當然參數(shù)配置除外。如果大家有更好的想法也可以在下方評論交流,謝謝!

MVC代碼執(zhí)行效果如下:

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對武林網(wǎng)的支持。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 阳信县| 云林县| 淳安县| 吉安市| 镇江市| 循化| 泉州市| 新龙县| 淮南市| 吉木萨尔县| 汝城县| 青铜峡市| 济南市| 南京市| 汝南县| 建始县| 彭州市| 扎鲁特旗| 廊坊市| 汉源县| 莱州市| 阳谷县| 濉溪县| 榆林市| 和平县| 深圳市| 宜昌市| 凉山| 津市市| 葵青区| 安康市| 乐昌市| 永胜县| 滁州市| 贵港市| 汨罗市| 简阳市| 綦江县| 长沙市| 黎城县| 济宁市|