当前位置:网站首页>Implement AOP and interface caching on WPF client

Implement AOP and interface caching on WPF client

2022-06-11 20:40:00 Duck bully among geese

As the business gets more complex , Recently, I decided to cache some interfaces that are frequently queried but the data will not change much , This function is generally used AOP You can do it , After looking for a client, there is no ready-made one that can be used directly , A kind of , You can only develop it yourself .

Agent mode and AOP

After understanding the proxy pattern , Yes AOP It's natural to get caught , So let's start with some pre knowledge .
The proxy pattern is an example code that uses one class to control method calls from another class .
The proxy pattern has three roles :

  • ISubject Interface , Responsibility is to define behavior .
  • ISubject Implementation class of RealSubject, Responsibility is to achieve behavior .
  • ISubject Proxy class ProxySubject, Responsibility is to control RealSubject The interview of .

There are three implementations of the proxy pattern :

  • General agent .
  • Compulsory agency , Coercion means not having direct access to RealSubject Methods , Must be accessed through a proxy class .
  • A dynamic proxy , Dynamic means to generate proxy classes through reflection ,AOP It is generally based on dynamic proxy .

AOP There are four key knowledge points :

  • The breakthrough point JoinPoint. Namely RealSubject The method of controlled access in .
  • notice Advice, Is the method in the proxy class , Can control or enhance RealSubject Methods , With advance notice 、 The rear notice 、 Surround notifications, etc
  • Weaving Weave, Is to call the notification and in order RealSubject Method process .
  • section Aspect, Multiple entry points will form a facet .
public interface ISubject
{
    void DoSomething(string value);

    Task DoSomethingAsync(string value);
}

public class RealSubject : ISubject
{
    public void DoSomething(string value)
    {
        Debug.WriteLine(value);
    }

    public async Task DoSomethingAsync(string value)
    {
        await Task.Delay(2000);
        Debug.WriteLine(value);
    }
}

public class Proxy : ISubject
{
    private readonly ISubject _realSubject;

    public Proxy()
    {
        _realSubject = new RealSubject();
    }

    /// <summary>
    ///  That's the entry point 
    /// </summary>
    /// <param name="value"></param>
    public void DoSomething(string value)
    {
        //  This process is weaving in 
        Before();
        _realSubject.DoSomething(value);
        After();
    }

    public Task DoSomethingAsync(string value)
    {
        throw new NotImplementedException();
    }

    public void Before()
    {
        Debug.WriteLine(" General proxy class pre notification ");
    }

    public void After()
    {
        Debug.WriteLine(" General proxy class post notification ");
    }
}

I'm using Castle.Core This library is used to implement dynamic proxy . But the asynchronous method with return value of this proxy is hard to write , however github There are already many libraries that encapsulate the implementation process , Here I use Castle.Core.AsyncInterceptor To implement an asynchronous method proxy .

public class CastleInterceptor : StandardInterceptor
{
    protected override void PostProceed(IInvocation invocation)
    {
        Debug.WriteLine("Castle  Proxy class pre notification ");

    }

    protected override void PreProceed(IInvocation invocation)
    {
        Debug.WriteLine("Castle  Proxy class post notification ");
    }
}

public class AsyncCastleInterceptor : AsyncInterceptorBase
{
    protected override async Task InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task> proceed)
    {
        Before();
        await proceed(invocation, proceedInfo);
        After();
    }

    protected override async Task<TResult> InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed)
    {
        Before();
        var result = await proceed(invocation, proceedInfo);
        After();
        return result;
    }

    public void Before()
    {
        Debug.WriteLine(" asynchronous  Castle  Proxy class pre notification ");
    }

    public void After()
    {
        Debug.WriteLine(" asynchronous  Castle  Proxy class post notification ");
    }
}

Implement facet class and interface caching

Implementation process :

  1. Definition CacheAttribute Feature to mark the methods that need to be cached .
  2. Definition CacheInterceptor section , Implement the logic of caching data in memory .
  3. Using facets , Generate dynamic proxy classes for interfaces , And inject the proxy class into IOC In the container .
  4. Interface by IOC Get the interface implementation class to access the implementation .

The client uses Prism Of IOC To achieve inversion of control ,Prism Support for multiple IOC, I'm going to use DryIoc, Because the others IOC No more updates .
Client memory cache usage Microsoft.Extensions.Caching.Memory, This is the most commonly used .

  • Definition CacheAttribute Feature to mark the methods that need to be cached .
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
    public string? CacheKey { get; }
    public long Expiration { get; }

    public CacheAttribute(string? cacheKey = null, long expiration = 0)
    {
        CacheKey = cacheKey;
        Expiration = expiration;
    }

    public override string ToString() => $"{{ CacheKey: {CacheKey ?? "null"}, Expiration: {Expiration} }}";
}

  • Definition CacheInterceptor Section class , Implement the logic of caching data in memory
public class CacheInterceptor : AsyncInterceptorBase
    {
        private readonly IMemoryCache _memoryCache;

        public CacheInterceptor(IMemoryCache memoryCache)
        {
            _memoryCache = memoryCache;
        }

        ...
        //  Intercept asynchronous methods 
        protected override async Task<TResult> InterceptAsync<TResult>(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed)
        {
            var attribute = invocation.Method.GetCustomAttribute<CacheAttribute>();
            if (attribute == null)
            {
                return await proceed(invocation, proceedInfo).ConfigureAwait(false);
            }

            var cacheKey = attribute.CacheKey ?? GenerateKey(invocation);
            if (_memoryCache.TryGetValue(cacheKey, out TResult cacheValue))
            {
                if (cacheValue is string[] array)
                {
                    Debug.WriteLine($"[Cache]  Key: {cacheKey}, Value: {string.Join(',', array)}");
                }

                return cacheValue;
            }
            else
            {
                cacheValue = await proceed(invocation, proceedInfo).ConfigureAwait(false);
                _memoryCache.Set(cacheKey, cacheValue);
                return cacheValue;
            }
        }
        //  Generate cached  Key
        private string GenerateKey(IInvocation invocation)
        {
            ...
        }
        //  Format it 
        private string FormatArgumentString(ParameterInfo argument, object value)
        {
            ...
        }
    }
  • Define extension classes to generate facets , And realize chain programming , You can easily add multiple facet classes to an interface .
public static class DryIocInterceptionAsyncExtension
{
    private static readonly DefaultProxyBuilder _proxyBuilder = new DefaultProxyBuilder();
    //  Generate section 
    public static void Intercept<TService, TInterceptor>(this IRegistrator registrator, object serviceKey = null)
        where TInterceptor : class, IInterceptor
    {
        var serviceType = typeof(TService);

        Type proxyType;
        if (serviceType.IsInterface())
            proxyType = _proxyBuilder.CreateInterfaceProxyTypeWithTargetInterface(
                serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
        else if (serviceType.IsClass())
            proxyType = _proxyBuilder.CreateClassProxyTypeWithTarget(
                serviceType, ArrayTools.Empty<Type>(), ProxyGenerationOptions.Default);
        else
            throw new ArgumentException(
                $"{serviceType}  Can't be intercepted ,  Only interfaces or classes can be intercepted ");

        registrator.Register(serviceType, proxyType,
            made: Made.Of(pt => pt.PublicConstructors().FindFirst(ctor => ctor.GetParameters().Length != 0),
                Parameters.Of.Type<IInterceptor[]>(typeof(TInterceptor[]))),
            setup: Setup.DecoratorOf(useDecorateeReuse: true, decorateeServiceKey: serviceKey));
    }
    //  Chain programming , Easy to add multiple slices 
    public static IContainerRegistry InterceptAsync<TService, TInterceptor>(
        this IContainerRegistry containerRegistry, object serviceKey = null)
        where TInterceptor : class, IAsyncInterceptor
    {
        var container = containerRegistry.GetContainer();
        container.Intercept<TService, AsyncInterceptor<TInterceptor>>(serviceKey);
        return containerRegistry;
    }
}
  • Define the target interface , And mark the method
public interface ITestService
{
    /// <summary>
    ///  An interface for querying large amounts of data 
    /// </summary>
    /// <returns></returns>
    [Cache]
    Task<string[]> GetLargeData();
}

public class TestService : ITestService
{
    public async Task<string[]> GetLargeData()
    {
        await Task.Delay(2000);
        var result = new[]{" Big "," The amount "," Count "," According to the "};
        Debug.WriteLine(" Query data from the interface ");
        return result;
    }
}
  • towards IOC The container injects facet classes and business interfaces .
public partial class App
{
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        //  Inject cache class 
        containerRegistry.RegisterSingleton<IMemoryCache>(_ => new MemoryCache(new MemoryCacheOptions()));
        //  Injection section class 
        containerRegistry.Register<AsyncInterceptor<CacheInterceptor>>();
        //  Inject interface and application facet classes 
        containerRegistry.RegisterSingleton<ITestService, TestService>()
            .InterceptAsync<ITestService, CacheInterceptor>();
        containerRegistry.RegisterSingleton<ITestService2, TestService2>()
            .InterceptAsync<ITestService2, CacheInterceptor>();
    }
    ...
}

effect

// AopView.xaml
<Button x:Name="cache" Content="Aop Cache interface data " />

// AopView.xaml.cs
cache.Click += (sender, args) => ContainerLocator.Container.Resolve<ITestService>().GetLargeData();

//  Output 
//  Click Print for the first time 
//  Query data from the interface 

//  Then click Print 
// [Cache]  Key: PrismAop.Service.TestService2.GetLargeData(), Value:  Big , The amount , Count , According to the 

Last

In fact, there are many details that can be improved , For example, cache refresh rules , The server refreshes the client cache, and so on , But the client AOP The implementation of is almost like this .

I think it's helpful for you to make a recommendation or leave a message !

Source code https://github.com/yijidao/blog/tree/master/WPF/PrismAop

原网站

版权声明
本文为[Duck bully among geese]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/03/202203011744226507.html