您好,登錄后才能下訂單哦!
這篇文章給大家介紹.NET面向上下文、AOP架構模式的示例分析,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
1.上下文Context、面向切面編程AOP模型分析
在本人的.NET面向上下文、AOP架構模式(概述)一文中,我們大概了解了上下文如何輔助對象在運行時的管理。在很多時候我們急需在運行時能把對象控制在一定的邏輯范圍內,在必要的時候能讓他們體現出集中化的概念,如人群、車輛、動物等等。而Context與AOP有著密切的聯系,Context表示邏輯抽象的范圍而AOP描述了在這個邏輯范圍內如何進行控制。其實這兩者都是設計模式外的設計模式,與具體的技術實現無關。
那么Context與AOP兩者在邏輯上是一個怎樣的概念呢?似乎只有圖才能最貼切的表達人的理解思路。下圖展現Context與AOP緊密合作的概念模型。
Context圖:1
對象在運行時被上下文管理,在上下文中可以很方便的獲取到所有的受管理的對象,這為后面的AOP做了鋪墊。只有Context啟動后AOP管理器的爪子才能伸進對象的運行時內部與AOP的連接點建立起通訊關系,才能真正的使對象能面向切面成功。
在模型圖中,Context中心負責對所有Object進行管理,而Object體現出多面性屬性、多面性行為都將包括多面性的特點,通過與AOP管理器進行連接將控制對象的運行時行為。
AOP圖:2
通過合理的約定對象的AOP抽象接口,盡可能的***化將控制權移動到客戶所實現的“面”中去。比如對某類方法的調用,可能需要盡可能的控制方法的所有執行權。所以對具體的抽象定義有很大的難度。
在上圖中,我們通過AOP核心管理器與對象的“面”連接上,根據具體的“面”類型進行動態調用,比如屬性,可能需要在運行時根據業務環節進行呈現、動態綁定等等,都可以通過AOP去實現。對于方法,可能在面向SOA的架構中需要記錄下當前客戶端的調用信息,還有一些獨特的業務認證等等。不同的 “面”需要進行邏輯抽象,這也符合面向對象的設計原則。很多時候我們需要先“約定”而不是盡可能的提供擴展的機制,擴展點越多系統的復雜程度就越大,相對也就難以控制。
2.上下文的實現
對上下文、AOP模型我們大致分析了一下,通過模型圖也很形象的體現出上下文、AOP的主要的工作原理。下面我們來通過具體的分析來搭建物理的代碼模型。
面向對象是對抽象的邏輯模型進行物理性的建模,能否真正的體現出模型,需要我們細心的分析才行。
2.1上下文模型實現
我們對上下文模型進行抽象實現為代碼模型。那么我們需要一個邏輯上代表上下文的對象,在這里我將它命名為ContextRuntime,上下文的生命周期控制在Using()語句的代碼段中,那么ContextRuntime需要實現Idisposable接口,讓Using()語句能起作用。
using (ContextModule.ContextRuntime.BeginContextRuntime()) { //上下文的生命周期 }
為什么要這樣設計上下文的生命周期呢,這樣設計是最為靈活的。在諸如很多微軟的內部上下文生命周期的入口也是這樣設計的,最經典的就是System.Transaction事務處理。
當Using()代碼段結束后,我們需要釋放當前上下文實例,所以我們需要完成IDisposable接口的實現。
void IDisposable.Dispose() { _currentContextRuntime = null; }
_ currentContextRuntime表示上下文對象實例的全局私有對象。
由于多線程應用框架的入口點不是我們所能控制的,所以在使用上下文模式的時候需要使用線程本地存儲解決線程不安全訪問的問題。
[ThreadStatic]
private static ContextRuntime _currentContextRuntime;
我們看一下全部的ContextRuntime對象代碼:
View Code /*** * author:深度訓練 * blog:http://wangqingpei557.blog.51cto.com/ * **/ using System; using System.Collections.Generic; using System.Text; namespace ContextModule { /// <summary> /// 上下文運行時環境。 /// 上下文邏輯運行時環境,環境中的功能都是可以通過附加進來的。 /// </summary> public class ContextRuntime : IDisposable { #region IDisposable成員 void IDisposable.Dispose() { _currentContextRuntime = null; } #endregion protected ContextRuntime() { } private DateTime _initTime = DateTime.Now; /// <summary> /// 獲取運行時創建上下文的時間 /// </summary> public virtual DateTime InitTime { get { return _initTime; } } private Dictionary<object, object> _runTimeResource = new Dictionary<object, object>(); private ContextFilterHandlerMap _filterMap = new ContextFilterHandlerMap(); /// <summary> /// 獲取上下文中的方法、類過濾器映射表 /// </summary> public ContextFilterHandlerMap FilterMap { get { return _filterMap; } } private Guid _initPrimaryKey = Guid.NewGuid(); /// <summary> /// 獲取運行時創建上下文的唯一標識 /// </summary> public virtual Guid InitPrimaryKey { get { return _initPrimaryKey; } } /// <summary> /// 獲取上下文共享區域中的數據 /// </summary> /// <param name="key">數據Key</param> /// <returns>object數據對象</returns> public virtual object GetValue(object key) { return _runTimeResource[key]; } /// <summary> /// 設置上下文共享區域中的數據 /// </summary> /// <param name="key">數據Key</param> /// <param name="value">要設置的數據對象</param> public virtual void SetValue(object key, object value) { _runTimeResource[key] = value; } [ThreadStatic] private static ContextRuntime _currentContextRuntime; /// <summary> /// 獲取當前上下文運行時對象. /// </summary> public static ContextRuntime CurrentContextRuntime { get { return _currentContextRuntime; } } /// <summary> /// 開始運行時上下文 /// </summary> /// <returns>ContextRuntime</returns> public static ContextRuntime BeginContextRuntime() { //可以通過配置文件配置上下文運行時環境的參數。這里只是實現簡單的模擬。 _currentContextRuntime = new ContextRuntime(); return _currentContextRuntime; } } }
這里只為了實現基本的模型原型,不會涉及太多的功能。上下文主要是在當前線程中開啟,然后保持在靜態對象的多線程安全訪問,***就是對象的穩定釋放。
2.2上下文對象綁定實現
有了上下文之后,如何才能使對象在運行時動態的綁定到上下文中來。這個需要在前期編碼的時候就確定對象是否要綁定到當前上下文以便進行管理。
那么我們需要對客戶使用的對象進行一個抽象,讓所有需要綁定的對象實現我們高層定義的抽象。這里我將命名為ContextModuleBaseObject,由于需要向AOP提供對象的“面”的連接點,所以我們需要在運行時反射獲取到綁定對象的一些基本信息,如:屬性的“面”、行為的“面”、包括對象本身的“面”,這些面我們需要將其對應關系建立起來才能讓后面的AOP起作用。
所以我們將ContextModuleBaseObject定義為泛型類,并且需要加上Class的約束。對于綁定的對象在運行時一旦進入上下文的生命周期管理,就需要靈活的表示為ContextRuntime對象,這樣設計符合上下文一視同仁的觀念,更便于ContextModuleBaseObject對象在運行期動態的使用上下文的內部存儲區域。
這里我們需要將ContextModuleBaseObject對象繼承自ContextRuntime對象。使用經典的裝飾者模式,讓ContextModuleBaseObject對象使用ContextRuntime行為。
View Code /*** * author:深度訓練 * blog:http://wangqingpei557.blog.51cto.com/ * **/ using System; using System.Collections.Generic; using System.Text; using System.Reflection; namespace ContextModule { /// <summary> /// 上下綁定基類,強制派生類綁定到上下文。 /// 邏輯上下文的策略構造都在這里進行。 /// </summary> /// <typeparam name="T">受管理的上下文綁定對象類型,通常是ContextModuleBaseObject派生類。</typeparam> public class ContextModuleBaseObject<T> : ContextRuntime where T : class { /// <summary> /// 當前上下文綁定對象所綁定到的上下文物理對象實例。 /// </summary> private ContextRuntime _contextRunTime; public ContextModuleBaseObject() { if (typeof(T).GetCustomAttributes(typeof(ContextEveningBoundAttribute), false) != null) { _IsEvening = true; return; } //前期靜態綁定上下文 if (ContextRuntime.CurrentContextRuntime == null) throw new Exception("上下文環境未能初始化,請檢查您的代碼入口是否啟用了ContextRuntime對象。"); _contextRunTime = ContextRuntime.CurrentContextRuntime; _InitContextHandler<T>(); } /// <summary> /// 構造上下文的類過濾器、方法過濾器映射表。 /// </summary> private void _InitContextHandler<ChildType>() where ChildType : class { //構造類過濾器 ContextOperationBaseAttribute[] classattr = typeof(ChildType).GetCustomAttributes(typeof(ContextOperationBaseAttribute), false) as ContextOperationBaseAttribute[]; if (classattr.Length > 0) { ContextOperationBaseAttribute joinoper = _JoinOperation(classattr); _contextRunTime.FilterMap.MapOperation(typeof(T).FullName, joinoper); } //構造方法過濾器 foreach (MethodInfo method in typeof(ChildType).GetMethods()) { ContextOperationBaseAttribute[] methodattr = method.GetCustomAttributes(typeof(ContextOperationBaseAttribute), false) as ContextOperationBaseAttribute[]; if (methodattr.Length <= 0) continue; ContextOperationBaseAttribute joinoper = _JoinOperation(methodattr); _contextRunTime.FilterMap.MapOperation(string.Format("{0}.{1}", method.DeclaringType.FullName, method.Name), joinoper); } } internal bool _IsEvening { get; set; } /// <summary> /// 后期動態綁定上下文。 /// </summary> internal void _EveningBoundChildClass<ChildType>() where ChildType : class { if (_contextRunTime != null) return;//說明之前已經進行過動態調用 _contextRunTime = ContextRuntime.CurrentContextRuntime;//動態綁定當前運行時上下文 _InitContextHandler<ChildType>(); } private ContextOperationBaseAttribute _JoinOperation(ContextOperationBaseAttribute[] operationarray) { //必須對數組進行排序后才能連接 for (int i = 0; i < operationarray.Length; i++) { for (int j = 0; j < i; j++) { if (operationarray[j].OperationSort > operationarray[j + 1].OperationSort) { ContextOperationBaseAttribute oper = operationarray[j]; operationarray[j] = operationarray[j + 1]; operationarray[j + 1] = oper; } } } ContextOperationBaseAttribute opernext = operationarray[0]; for (int i = 1; i < operationarray.Length; i++) { opernext.NextOperation = operationarray[i]; opernext = operationarray[i];//保持對當前循環對象的上級對象的引用。 } return operationarray[0]; } public MethodInfo GetMethodInfo(string methodname) { return this.GetType().GetMethod(methodname); } public override Guid InitPrimaryKey { get { return _contextRunTime.InitPrimaryKey; } } public override DateTime InitTime { get { return _contextRunTime.InitTime; } } public override object GetValue(object key) { return _contextRunTime.GetValue(key); } public override void SetValue(object key, object value) { _contextRunTime.SetValue(key, value); } } }
ContextModuleBaseObject 類主要實現的功能就是將對象動態的添加到當前上下文中。然后為AOP做些輔助性的工作,包括對類、屬性、行為的特性元數據的緩存,這里只實現了行為的特性緩存。可以根據自己的需要擴展AOP的功能,在對象的屬性上標記特性讓屬性也發揮作用。這里的特性就是AOP公布的指定接口。
對_JoinOperation方法的解釋我們留在后面,這里是一個數據結構。將ContextOperationBaseAttribute
類型串成鏈表,讓方法的執行穿過所有的ContextOperationBaseAttribute處理類。
2.3上下文對象的后期綁定實現
為了讓綁定對象支持上下文后期綁定,需要一個特性作為表示。
View Code /*** * author:深度訓練 * blog:http://wangqingpei557.blog.51cto.com/ * **/ using System; using System.Collections.Generic; using System.Text; namespace ContextModule { /// <summary> /// 確定設置類是否需要后期動態綁定到上下文。 /// 使用該特性的類將是上下文活躍的,只有在使用的時候才確定當前上下文。 /// </summary> [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class ContextEveningBoundAttribute : Attribute { public ContextEveningBoundAttribute() { } private bool _isEvening; /// <summary> /// 指定對象是否需要后期動態綁定上下文。 /// </summary> public bool IsEvening { set { _isEvening = value; } get { return _isEvening; } } } }
僅僅為了標識后期綁定說明。在ContextModuleBaseObject 對象的構造函數中可以看到。
View Code public ContextModuleBaseObject() { if (typeof(T).GetCustomAttributes(typeof(ContextEveningBoundAttribute), false) != null) { _IsEvening = true; return; } //前期靜態綁定上下文 if (ContextRuntime.CurrentContextRuntime == null) throw new Exception("上下文環境未能初始化,請檢查您的代碼入口是否啟用了ContextRuntime對象。"); _contextRunTime = ContextRuntime.CurrentContextRuntime; _InitContextHandler<T>(); }
到這里我們已經實現對象的動態綁定到上下文來,下面我們來分析Context如何用AOP配合完成面向切面編程的機制。
2.4.AOP中的對象行為的契約設計實現
其實這里的契約設計也就是圖2中對AOP中的“面”的約定。
AOP全稱為“面向切面編程”。對象在運行時具備多個面,其實在.NET里面我們習慣性的用特性(Attribute)來表達這個概念。因為不需要改動任何代碼就可以將特性加到對象中的任何元素中去,在不同的業務環節或者說是功能環節就能動態的轉動元素體現出“切面”的優勢,當然具體的實現可能很多種,這里使用特性來完成。
在此約定任何處理對象方法的“面”都將被抽象。這里我將命名為ContextOperationBaseAttribute該特性表示所有附加到方法上的特性的基類,對“面”的抽象。
那么不同類型的面將有著不同的操作行為,比如:記錄日志的特性、計算性能的特性、認證安全的特性他們都有著不同的行為和屬性,所以這里我們還需要提取一個頂層接口作為行為類的特性處理抽象。這里我將命名為IContextOperationHandler該接口作為統一執行行為特性的高層依賴。其實這里也體現出依賴倒置原則,依賴抽象不依賴具體實現。
完整的ContextOperationBaseAttribute類:
View Code /*** * author:深度訓練 * blog:http://wangqingpei557.blog.51cto.com/ * **/ using System; using System.Collections.Generic; using System.Text; namespace ContextModule { /// <summary> /// 上下文操作動作特性化基類。 /// 所有對上下文中的類、方法的過濾操作都必須繼承此類。 /// </summary> public abstract class ContextOperationBaseAttribute : Attribute, IContextOperationHandler { /// <summary> /// 過濾器的處理順序,從小到大的方式進行處理。 /// </summary> public int OperationSort { get; set; } /// <summary> /// 下一次過濾操作類 /// </summary> internal ContextOperationBaseAttribute NextOperation { get; set; } /// <summary> /// 開始處理過濾對象 /// </summary> /// <typeparam name="Result">方法的返回值類型</typeparam> /// <param name="actionmethod">調用的方法包裝</param> /// <param name="paramarray">方法的有序參數</param> /// <returns></returns> public virtual Result ResultAction<Result>(ContextMethodInfo actionmethod, params object[] paramarray) { object result = null; if (!actionmethod.IsPost) { result = (this as IContextOperationHandler).Operation(actionmethod, paramarray); if (this.NextOperation != null) return this.NextOperation.ResultAction<Result>(actionmethod, paramarray); } if (result != null) return (Result)result; return default(Result); } public abstract object Operation(ContextMethodInfo contextmethod, params object[] paramarray); } }
作為抽象的頂層類需要完成派生類重復的勞動。這里實現了一個ResultAction泛型方法,該方法是外部調用綁定對象的方法時的入口點。但是具體的實現區別在于IContextOperationHandler 接口的定義。
由于行為的特性可能存在多個,所以對于***一個處理完的特性需要結束整個的調用鏈表,并且返回值。在ResultAction虛方法里面對IContextOperationHandler 接口的Operation方法執行調用,該方法將會在實現特定行為特性里面實現,這里又體現出“模板方法”設計模式。在抽象類中約定行為,在派生類中實現具體。
這里比較有意思的是,特性不在像大家實現ORM的那中簡單的標識了。其實特性真正強大的地方在于運行時能動態的獲取到,這得益于.NET元數據的功勞。并且動態實例化然后當作普通的對象實例使用。這個觀念很多.NET程序員不宜轉變。
在這里的ContextOperationBaseAttribute又描述了另外一種數據結構“單向鏈表”,為了將綁定對象的行為***化的在特性中實現,我們將方法的調用完全的傳遞到實現特性中去。那么對方法上多個作用的特性如何穿過呢,并且能保證數據的正常傳遞和返回。有兩點我們需要注意,一個是特性的作用順序,二個是特性對方法的執行是否完成。這兩點我們都要考慮進去,所以在ContextOperationBaseAttribute類中用public int OperationSort { get; set; }屬性表示特性的執行順序,記錄日志的特性和計算性能的特性我們很難在這里定死,需要根據后期程序的執行情況而定,如我要先記錄日志然后在執行方法。
那么我們又如何將ContextOperationBaseAttribute類型串聯起來呢?在ContextModuleBaseObject
泛型綁定類中我們在構造的時候就將通過ContextOperationBaseAttribute. OperationSort屬性初始化了特性處理鏈表。
那么我們如何將具體的對象與特性關聯建立起對應關系呢?一個行為可能有多個ContextOperationBaseAttribute的實現。所以這里我們需要一個能滿足行為與特性之間的數據結構。
這里我將它定義為ContextFilterHandlerMap該類繼承自Dictionary<string,ContextOperationBaseAttribute>泛型字典類,使用KEY—VALUE的方式存放行為與ContextOperationBaseAttribute處理特性的對應關系。
View Code /*** * author:深度訓練 * blog:http://wangqingpei557.blog.51cto.com/ * **/ using System; using System.Collections.Generic; using System.Text; namespace ContextModule { /// <summary> /// 特定于上下文的過濾器映射表。 /// 上下文中的任何方法如果需要進行上下文管理的,則使用ContextModule.ContextOperationBaseAttribute特性派生類進行管理。 /// 所有附加于方法、類上的特性管理類都將被映射到ContextModule.ContextFilterHandlerMap實例中。 /// </summary> public class ContextFilterHandlerMap : Dictionary<string, ContextOperationBaseAttribute> { public ContextFilterHandlerMap() { } /// <summary> /// 獲取方法對應的過濾器處理特性 /// </summary> /// <param name="mapname">映射Key</param> /// <returns>ContextOperationBaseAttribute特性實例</returns> public ContextOperationBaseAttribute MapOperation(string mapname) { return this[mapname]; } /// <summary> /// 設置過濾器與特定方法的映射 /// </summary> /// <param name="mapname">映射Key</param> /// <param name="operationlist">過濾器特性基類ContextOperationBaseAttribute</param> public void MapOperation(string mapname, ContextOperationBaseAttribute operationlist) { this.Add(mapname, operationlist); } } }
***只需要向外提供IContextOperationHandler接口就可以實現方法與處理特性的串聯了。
View Code /*** * author:深度訓練 * blog:http://wangqingpei557.blog.51cto.com/ * **/ using System; using System.Collections.Generic; using System.Text; using System.IO; namespace ContextModule { /// <summary> /// 上下文操作管理接口 /// </summary> public interface IContextOperationHandler { /// <summary> /// 開始上下文處理 /// </summary> /// <param name="contextmethod">CRL目前正在執行的上下文方法的信息。 /// 可以通過ContextMethodInfo實例獲取方法詳細信息。</param> ///<param name="paramarray">參數數組</param> object Operation(ContextMethodInfo contextmethod, params object[] paramarray); } }
通過對外公開接口,讓實現“面”的客戶端去完成對具體對象方法的執行。ContextMethodInfo類型是包裝System.Reflection. MethodInfo 方法元數據的,將通過調用切入到方法內部這里基本上實現了AOP對行為的多面支持,下面我們來看一下如果動態的切入到方法中。
2.5.動態入口的實現
對所有方法的調用將是比較頭疼的。由于一般面向上下文、面向切面都是有編寫者控制對方法的調用,可以很方便的通過后臺的隱式的調用。但是作為普通的方法的入口調用主要有三種方式實現。
1):委托實現入口
通過使用System.Delegate動態派生類型來完成對方法的調用,但是委托對于方法的簽名必須是強類型的,所以很難做到通用的調用入口。
2):反射實現入口(通過擴展方法在OBJECT基類中加入獲取MethodInfo對象的方法,使用時通過該方法獲取調用方法的信息)
通過擴展方法在System.Object中加入一個擴展方法用來獲取調用方法的信息,然后通過反射動態的調用,這種方法只比較常用的。但是如何框架是在.NET2.0中使用的擴展方法還不能實現,這里我是在ContextModuleBaseObject基類中加了一個類似擴展方法的方式。綁定對象可以很方便的獲取到調用方法的MethodInfo對象。
3):***的動態編譯(向抽象、多態敬禮)
最為***的是擴展代碼生成提供程序,在使用的對象里面在派生一個類,專門用來進行多態的轉移,讓高層的調用順利進入到派生的類中。不過比較復雜。
這里是使用第二種方式使用的:
View Code /*** * author:深度訓練 * blog:http://wangqingpei557.blog.51cto.com/ * **/ using System; using System.Collections.Generic; using System.Text; using System.Reflection; namespace ContextModule { /// <summary> /// 面向上下文的操作類。 /// 對上下文發起的方法調用需要通過該基類進行調用才能讓我們的擴展點使用成為可能。 /// </summary> public static class ContextAction { /// <summary> /// 在面向上下文的環境中進行方法的調用。 /// </summary> /// <typeparam name="PostObjectType">調用的上下文綁定對象類型</typeparam> /// <typeparam name="ResultType">方法的返回類型</typeparam> /// <param name="post">調用的上下文綁定對象的實例</param> /// <param name="method">方法的信息對象MethodInfo,通過Oject.GetContextMethodInfo方法自動獲取。</param> /// <param name="paramarray">方法的有序參數集合</param> /// <returns>ResultType泛型類型指定的返回實例</returns> public static ResultType PostMethod<PostObjectType, ResultType>(PostObjectType post, MethodInfo method, params object[] paramarray) where PostObjectType : ContextModuleBaseObject<PostObjectType> { _LockPostObejctIsEveningBound<PostObjectType>(post); string key = string.Format("{0}.{1}", method.DeclaringType.FullName, method.Name); if (!ContextRuntime.CurrentContextRuntime.FilterMap.ContainsKey(key)) { throw new Exception(string.Format("方法{0}未經過上下文進行管理。", key)); } ContextMethodInfo contextmethod = new ContextMethodInfo(method, post); return ContextRuntime.CurrentContextRuntime.FilterMap[key].ResultAction<ResultType>(contextmethod, paramarray); } /// <summary> /// 檢查調用實例類是否屬于后期綁定。 /// 通過使用ContextModule.ContextEveningBound(IsEvening = true)方式指定后期綁定上下文。 /// </summary> private static void _LockPostObejctIsEveningBound<PostObjectType>(PostObjectType post) where PostObjectType : ContextModuleBaseObject<PostObjectType> { ContextModuleBaseObject<PostObjectType> contextclass = post as ContextModuleBaseObject<PostObjectType>; if (contextclass._IsEvening) contextclass._EveningBoundChildClass<PostObjectType>(); } } }
所有的調用均使用PostMethod泛型方法啟動。_LockPostObejctIsEveningBound私有方法,判斷當前類型是否是后期綁定,如果是則需要切入到基類中調用_ EveningBoundChildClass方法進行ContextOperationBaseAttribute 類型的鏈表構造,然后直接通過頭對象進行調用。[王清培版權所有,轉載請給出署名]
3.實例上下文與靜態上下文
對于實例上下文同時也就存在靜態上下文的概念,對于靜態對象的邏輯歸納有點難度,由于靜態對象在面向對象設計方面很難抽象。只能通過特性注入的方式強制性的將靜態對象拉入上下文。但是在多線程的情況下,確實是可以研究的。將靜態對象全部進行線程本地存儲,強制性的進行類似實體對象的管理。
4.面向上下文的領域模型(DMM)
基于上下文的使用模式可以進行領域模型的初步構造,可以先向領域中的大比例結構靠近,將業務模型邏輯歸納到一定的Context中,對業務的模塊化梳理也是一種實現。
在分層架構中的業務邏輯層可能需要加入上下文的管理,將業務模型進行運行時控制。比如訂單處理,將訂單業務流程相關的模型對象歸納為一塊。比如用戶相關,將用戶管理的業務流程相關的模型對象歸納為一塊,確實是很有意思。
關于.NET面向上下文、AOP架構模式的示例分析就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。