openfeign拦截响应结果_1分钟教你引用MrAdvice.dll文件实现AOP拦截

本文介绍了如何在断网条件下,通过引入MrAdvice.dll实现C#的AOP拦截功能,以自动处理业务方法的缓存。详细讲解了问题的由来、解决方案及完整演示程序的源码,包括项目配置和关键的拦截代码。

af8ae6dcb18959301e09c8b960ce64ed.png

近日工作中,要实现一个功能,那就是业务层方法里面实现自动缓存。编写业务的C#开发人员只关注如何将业务代码编写正确就可以了,而缓存的代码,大多类似,无非就是判断是否有缓存,有就取出返回,没有就调用数据库代码获取数据再缓存起来而已,于是这部分代码通过使用AOP的方式自动接管掉这种重复性代码。

MrAdvice开源项目github地址:https://github.com/ArxOne/MrAdvice

直接引用MrAdvice.dll文件不能实现AOP拦截功能

因项目原因内外网隔离,且是断网开发的,就只能在外网写个测试程序,然后将MrAdvice.dll文件复制到内网电脑,内网电脑通过引用dll的方式来使用该组件,结果是不会进入到拦截方法的。

近日工作中,要实现一个功能,那就是业务层方法里面实现自动缓存。编写业务的C#开发人员只关注如何将业务代码编写正确就可以了,而缓存的代码,大多类似,无非就是判断是否有缓存,有就取出返回,没有就调用数据库代码获取数据再缓存起来而已,于是这部分代码通过使用AOP的方式自动接管掉这种重复性代码。

MrAdvice开源项目github地址:https://github.com/ArxOne/MrAdvice

直接引用MrAdvice.dll文件不能实现AOP拦截功能

因项目原因内外网隔离,且是断网开发的,就只能在外网写个测试程序,然后将MrAdvice.dll文件复制到内网电脑,内网电脑通过引用dll的方式来使用该组件,结果是不会进入到拦截方法的。

d7e7b61450040bf9dd81ceaa71cc7f23.png

通过下图可以看到,成功解决后,可以实现自动缓存了。

f6c2ec52b65b754fe816a04d554f5e7f.png

实现AOP拦截

下面是全部的演示程序源码。

演示程序解决方案目录一览

该项目是一个控制台项目,解决方案如下图所示:

1b94646bc629863217947664f274ceee.png

演示程序解决.

MrAdvice.dll是直接引用的,不是通过nuget安装的,至于这个dll文件的获取,你可以通过nuget获取了找到它即可。

演示程序的源码

控制台入口的代码比较简单,单纯的调用接口。

程序入口代码

22ee00aded3100da1270268f22011600.png

程序接口代码

程序接口代码主要是模拟业务方法里面的一些类,定义了一个接口,一个实现类,另外实现类上面是标注了一个自动缓存的特性(AutoCache),该特性的实现代码即为下面所述的核心的AOP拦截代码,具体下面会给出的;另外还有一个输出结果(响应消息)的类。整个源码是放到一个文件里面的,如下所示:

public interface IJhrscom{ResponseResult GetResult(string a, DateTime dateTime, int id);ResponseResult GetPatient(Guid id, ResponseResult t);}public class Jhrscom : IJhrscom{[AutoCache(10)]public ResponseResult GetPatient(Guid id, ResponseResult t){string key = GetKey(new object[] { id, t });ResponseResult result = new ResponseResult() { Code = 4444, Message = "第2个方法" };return result;}[AutoCache(cacheMinutes: 12, enableSliding: true)]public ResponseResult GetResult(string a, DateTime dateTime, int id){ResponseResult result = new ResponseResult() { Code = 1122, Message = "缓存测试消息" };string key = GetKey(new object[] { a, dateTime, id });return result;}/// <summary>/// 缓存key/// </summary>/// <param name="pars"></param>/// <returns></returns>private string GetKey(params object[] pars){var method = new StackFrame(1).GetMethod();var array = method.GetParameters();var key = array.Select(x => { return pars[x.Position].ToJson(); }).ToArray();var cacheKey = $"{method.DeclaringType.ToString()}|{method.Name.Replace("′", "")}|{string.Join("_", array.Select(x => x.Name))}|{string.Join("_", key)}".GetMd5();Console.WriteLine($"【{method.Name.Replace("′", "")}】实现类里面的缓存Key:" + cacheKey);return cacheKey;}}/// <summary>/// 输出结果/// </summary>public class ResponseResult{public int Code { get; set; }public string Message { get; set; }//.....其它属性

核心的AOP拦截代码

该代码是用于实现自动缓存功能,思路就是在调用业务方法前,根据缓存key,缓存key按一定规则生成,保证唯一就可以了,具体源码中有说明,从缓存里面取出数据,如果存在缓存就直接返回给调用者即可,并终止业务方法的执行(体现在不调用context.Proceed()方法上);如果不存在缓存数据或者缓存过期了,则调用业务方法获取数据后并缓存就可以了。

/// <summary>/// 用AOP来实现自动缓存/// </summary>public class AutoCacheAttribute : Attribute, IMethodAdvice{/// <summary>/// 滑动过期/// </summary>public bool EnableSliding { get; set; }/// <summary>/// 缓存时间,分钟/// </summary>public int CacheMinutes { get; set; }/// <summary>/// 构造函数/// </summary>/// <param name="cacheMinutes">缓存时间,分钟,默认5分钟,小于等于0永久缓存</param>/// <param name="enableSliding">使用滑动过期缓存控制策略</param>public AutoCacheAttribute(int cacheMinutes = 5, bool enableSliding = false){EnableSliding = enableSliding;CacheMinutes = cacheMinutes;}/// <summary>/// AOP组件拦截方法,用于实现自动缓存,有缓存时直接返回;/// 没有缓存时,调用被拦截方法后,有返回值则将数据自动缓存起来/// </summary>/// <param name="context"></param>public void Advise(MethodAdviceContext context){var key = GetKey(context);if (context.HasReturnValue && key.TryGetCache(out object m)){var r = m as ResponseResult;r.Message = "在拦截方法里面改了缓存里面取出来的数据!";context.ReturnValue = r;//context.ReturnValue = m; //context.Proceed(); //直接取出缓存返回,不用执行原来取数据方法。}else{context.Proceed();//执行被拦截的方法if (context.HasReturnValue && context.ReturnValue != null){//被拦截方法有返回值,并且返回值不为nullif (EnableSliding && CacheMinutes > 0)context.ReturnValue.SetCache(key, TimeSpan.FromMinutes(CacheMinutes));else if (CacheMinutes > 0)context.ReturnValue.SetCache(key, DateTime.Now.AddMinutes(CacheMinutes));elsecontext.ReturnValue.SetCache(key);}}}/// <summary>/// 获取缓存key,key的规则为: md5(类全名|方法名|参数列表拆分数组|参数值的json数组),这样可以保证唯一/// </summary>/// <param name="context"></param>/// <returns></returns>private string GetKey(MethodAdviceContext context){var array = context.TargetMethod.GetParameters();var key = array.Select(x => { return context.Arguments[x.Position].ToJson(); }).ToArray();var cacheKey = $"{context.Target.ToString()}|{context.TargetName}|{string.Join("_", array.Select(x => x.Name))}|{string.Join("_", key)}".GetMd5();return cacheKey;}}/// <summary>/// 缓存扩展方法,可使用其它缓存替代/// </summary>public static class CacheExtensions{private static MemoryCache cache = new MemoryCache("https://jhrs.com");/// <summary>/// 设置缓存,一直不过期/// </summary>/// <typeparam name="T"></typeparam>/// <param name="value"></param>/// <param name="key"></param>public static void SetCache<T>(this T value, string key){if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException($"缓存键参数{nameof(key)}不能为null或空");if (value == null) throw new ArgumentException($"缓存值参数{nameof(value)}不能为null");CacheItemPolicy policy = new CacheItemPolicy();cache.Set(key, value, policy);}/// <summary>/// 设置缓存,固定过期时间/// </summary>/// <typeparam name="T"></typeparam>/// <param name="value"></param>/// <param name="key"></param>/// <param name="absoluteExpiration"></param>public static void SetCache<T>(this T value, string key, DateTimeOffset? absoluteExpiration){if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException($"缓存键参数{nameof(key)}不能为null或空");if (value == null) throw new ArgumentException($"缓存值参数{nameof(value)}不能为null");CacheItemPolicy policy = new CacheItemPolicy() { AbsoluteExpiration = (DateTimeOffset)absoluteExpiration };cache.Set(key, value, policy);}/// <summary>/// 设置缓存,滑动过期/// </summary>/// <typeparam name="T"></typeparam>/// <param name="value"></param>/// <param name="key"></param>/// <param name="slidingExpiration"></param>public static void SetCache<T>(this T value, string key, TimeSpan? slidingExpiration){if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException($"缓存键参数{nameof(key)}不能为null或空");if (value == null) throw new ArgumentException($"缓存值参数{nameof(value)}不能为null");CacheItemPolicy policy = new CacheItemPolicy() { SlidingExpiration = (TimeSpan)slidingExpiration };cache.Set(key, value, policy);}/// <summary>/// 获取缓存数据/// </summary>/// <typeparam name="T">对象类型</typeparam>/// <param name="key"><缓存key/param>/// <param name="value">返回的缓存数据对名</param>/// <returns></returns>public static bool TryGetCache<T>(this string key, out T value){value = default(T);if (cache.Contains(key)){value = (T)cache.Get(key);return true;}return false;}/// <summary>/// 获取字符串MD5值/// </summary>/// <param name="value"></param>/// <returns></returns>public static string GetMd5(this string value){byte[] bytes = Encoding.UTF8.GetBytes(value);StringBuilder sb = new StringBuilder();MD5 hash = new MD5CryptoServiceProvider();bytes = hash.ComputeHash(bytes);foreach (byte b in bytes){sb.AppendFormat("{0:x2}", b);}return sb.ToString();}}

附加的JSON扩展类

该扩展类只是方便将对象转为JSON而已,代码不复如,如下所示:

public static class JsonExtensions{/// <summary>/// 将对象转换为JSON字符串/// </summary>/// <param name="obj">要转换的对象</param>/// <param name="camelCase">是否小写名称</param>/// <param name="indented"></param>/// <returns></returns>public static string ToJson(this object obj, bool camelCase = false, bool indented = false){JsonSerializerSettings settings = new JsonSerializerSettings();if (camelCase){settings.ContractResolver = new CamelCasePropertyNamesContractResolver();}if (indented){settings.Formatting = Formatting.Indented;}return JsonConvert.SerializeObject(obj, settings);}/// <summary>/// 把Json字符串转换为强类型对象/// </summary>public static T FromJson<T>(string json){if (string.IsNullOrWhiteSpace(json)) return default(T);json = JsonDateTimeFormat(json);return JsonConvert.DeserializeObject<T>(json);}/// <summary>/// 处理Json的时间格式为正常格式/// </summary>private static string JsonDateTimeFormat(string json){json = Regex.Replace(json,@"/Date((d+))/",match =>{DateTime dt = new DateTime(1970, 1, 1);dt = dt.AddMilliseconds(long.Parse(match.Groups[1].Value));dt = dt.ToLocalTime();return dt.ToString("yyyy-MM-dd HH:mm:ss.fff");});return json;}}

解决直接引用MrAdvice.dll不能拦截的问题

出现这个问题的根源是,MrAdvice这个组件是在编译时会给你的项目源码编织一些AOP拦截代码,熟悉PostSharp的应该对此了解,这也是在MrAdvice项目地址的issues处得到解答,地址是:https://github.com/ArxOne/MrAdvice/issues/140

所以我们需要在项目文件csproj里面添加一些配置,并且把MrAdvice的目录复制到断网开发项目的packages目录。通过完成这两个步骤就可以解决了。

You’ve missed the point: Mr Advice is a post-build weaver, which changes the assembly at build-time after the csc compiler has generated it. To achieve this, is inserts a task in the csproj. So if you want to do the same manually, you need to also add the build task in your csproj. If you have a VS2017 solution with a project working, you’ll only need to copy the lines that were added to the csproj into your own project.

解决步骤

  • 联网新建一个项目,通过nuget安装MrAdvice,然后在解决方案的packages目录里面将nuget下载的MrAdvice目录包,复制到你断网环境的解决方案的packages目录,如下图所示:

6a4a731cabdb94deca60daaf56498de1.png

MrAdvice 目录

  • 修改项目文件,即修改csproj文件,csproj文件可以使用记事本或者其它软件打开,增加以下节点,如下图所示:

d9db39c48754be590a19788715d6d1d9.png

csproj文件

<Import Project="..packagesMrAdvice.2.8.8buildMrAdvice.targets" Condition="Exists('..packagesMrAdvice.2.8.8buildMrAdvice.targets')" />

<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

<PropertyGroup>

<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>

</PropertyGroup>

<Error Condition="!Exists('..packagesMrAdvice.2.8.8buildMrAdvice.targets')" Text="$([System.String]::Format('$(ErrorText)', '..packagesMrAdvice.2.8.8buildMrAdvice.targets'))" />

</Target>

好了,通过以上步骤就可以在断网环境里面愉快的使用MrAdvice这个AOP拦截组件来省点体力劳动了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值