AOP优势
在这里就不介绍AOP是什么,不了解的同学可以自行百度。
使用AOP的优势有以下几点:
- 将通用功能从业务逻辑中抽离出来,就可以省略大量重复代码,有利于代码的操作和维护。
- 在软件设计时,抽出通用功能(切面),有利于软件设计的模块化,降低软件架构的复杂程度。也就是说通用的功能就是一个单独的模块,在项目的主业务里面是看不到这些通用功能的设计代码的。
目的
在不改变代码逻辑的情况下,缓存某些方法的结果
准备工作
本文是基于
Autofac
实现的AOP
- 创建.net core 3.1的项目
- 引入
Autofac.Extensions.DependencyInjection 6.0.0
- 引入
Autofac.Extras.DynamicProxy 5.0.0
定义特性
因为Autofac的拦截器只能作用与接口和类,无法指定具体的某一个方法。有这个特性即缓存,没有则跳过。该特性可以指定缓存的键,方便查找
using System;
namespace DesignPattern.Attributes
{
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
public string Name { get; set; }
public CacheAttribute(string name)
{
Name = name;
}
}
}
创建拦截器
需要实现 IInterceptor
接口,此处我创建了一个拦截器,用来实现方法缓存,即进入方法执行前,先判断该方法的结果是否已被缓存,如是,则直接返回缓存,否则继续执行方法,并将方法的结果缓存起来
public class CustomInterceptor : IInterceptor
{
private readonly ILogger<CustomInterceptor> _logger;
private readonly IMemoryCache _cache;
public CustomInterceptor(ILogger<CustomInterceptor> logger, IMemoryCache cache)
{
_logger = logger;
_cache = cache;
}
public void Intercept(IInvocation invocation)
{
try
{
var cacheAttr = invocation.Method.GetCustomAttributes<CacheAttribute>(true).FirstOrDefault();
if (cacheAttr != null)
{
_logger.LogInformation($"拦截方法:{invocation.Method.DeclaringType?.Name}.{invocation.Method.Name}");
var argument = invocation.Arguments.FirstOrDefault(); // 获取方法的参数
var value = _cache.Get($"{cacheAttr.Name}-{argument}");
if (value != null)
invocation.ReturnValue = value;
else
{
invocation.Proceed();
value = invocation.ReturnValue;
_cache.Set($"{cacheAttr.Name}-{argument}", value, TimeSpan.FromHours(1));
}
_logger.LogInformation($"{DateTime.Now:s},方法返回值:{JsonSerializer.Serialize(value)}");
}
else
{
invocation.Proceed(); // 方法执行中
}
}
catch (Exception e)
{
_logger.LogInformation($"出错了:{e.Message}");
invocation.ReturnValue = new {success = 1, message = e.Message};
}
}
}
注入拦截器
在ASP.NET Core 3.X托管方式发生了变化 并且需要不同的集成方式. 你不能在从 ConfigureServices 中返回 IServiceProvider, 也不能再将你的service provider factory加入到service collection.在这个方法中使用第三方依赖注入
下面是ASP.NET Core 3+ 和 .NET Core 3+ generic hosting support的集成方式:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.UseServiceProviderFactory(new AutofacServiceProviderFactory()); //
在你的Startup类中 (各版本ASP.NET Core基本一致) 你可以使用 ConfigureContainer 访问 Autofac container builder 并且直接使用Autofac注册东西.
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
// ......
}
//ConfigureContainer是你可以直接注册Autofac的东西。它在ConfigureServices之后运行,所以这里的内容将覆盖在ConfigureServices中进行的注册。
//不要在这里使用Build构建,ServiceProviderFactory会去做这件事
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<CustomInterceptor>();
}
创建服务类
创建一个接口及它的实现类,模拟从数据库中读取数据
[Intercept(typeof(CustomInterceptor))]
public interface ICustomerService
{
[Cache("test")]
object Get(int id);
object NoCache();
}
public class CustomerService : ICustomerService
{
public object Get(int id)
{
var data = new {id, name = "cook", age = 18, time = DateTime.Now.ToString("s")};
return data;
}
public object NoCache()
{
return new { time = DateTime.Now.ToString("s") };
}
}
在这里,使用Intercept
特性标记接口或者类使用哪一个拦截器,然后在Startup中注入服务
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<CustomInterceptor>();
builder.RegisterType<CustomerService>().As<ICustomerService>().InstancePerLifetimeScope().EnableInterfaceInterceptors();
}
启用拦截器主要有两个方法 EnableInterfaceInterceptors
和 EnableClassInterceptors
,分别对应接口和类,我这里使用的是接口,所以用的是EnableInterfaceInterceptors
方法,当然还有其他的方式可以指定拦截器,如:InterceptedBy(typeof(CustomInterceptor))
方法等,这里我不一一赘述,可以参考官方文档 https://autofaccn.readthedocs.io/zh/latest/advanced/interceptors.html
控制器中使用
[ApiController]
[Route("interceptor")]
public class InterceptorController : ControllerBase
{
private readonly ICustomerService _customerService;
public InterceptorController(ICustomerService customerService)
{
_customerService = customerService;
}
public IActionResult Index([FromQuery] int id)
{
var hasCache = _customerService.Get(id);
var noCache = _customerService.NoCache();
return Ok(hasCache);
}
}
运行项目,并多次访问 localhost:5000/interceptor?id=2
,得到的结果如下图
可以看到,多次运行,只有第一次会执行Get方法,之后会从缓存中读取数据,不会执行Get方法