Implement interceptor and section programming with Autofac

Autofac. The annotation framework is for me netcore writes an annotated DI framework based on Autofac reference Spring annotation. All container registration and assembly, facets, interceptors, etc. are completed by relying on tags.

Open source address: https://github.com/yuzd/Autofac.Annotation

This issue is about the latest refactoring function, which also gives unlimited possibilities to the framework. It is also a good place for me to design. Today, let's talk about how I designed it

Section and interceptor introduction

What is the interceptor?

It can help us facilitate the implementation of the target method

  • Before
  • After
  • When a value is returned (AfterReturn)
  • After throwing error
  • Surround

Simple example:

    //Implement an interceptor yourself
    public class TestHelloBefore:AspectBefore
    {
        public override Task Before(AspectContext aspectContext)
        {
            Console.WriteLine("TestHelloBefore");
            return Task.CompletedTask;
        }
    }
    
    [Component]
    public class TestHello
    {

        [TestHelloBefore]//Put on the interceptor
        public virtual void Say()
        {
            Console.WriteLine("Say");
        }
    }

Execute the Before method of TestHelloBefor first, and then execute your Say method

For more usage examples, see Aspect interceptor

What is the cut?

Define a "interceptor" that implements multiple methods of multiple classes that meet the conditions according to a filter

Simple example:

    [Component]
    public class ProductController
    {
        public virtual string GetProduct(string productId)
        {
            return "GetProduct:" + productId;
        }
        
        public virtual string UpdateProduct(string productId)
        {
            return "UpdateProduct:" + productId;
        }
    }
    
    [Component]
    public class UserController
    {
        public virtual string GetUser(string userId)
        {
            return "GetUser:" + userId;
        }
        
        public virtual string DeleteUser(string userId)
        {
            return "DeleteUser:" + userId;
        }
    }
    
    //* Controller » for match » any class ending in Controller can match
    //Get * means that the above matching is successful, so all methods starting with get can be matched
    [Pointcut(Class = "*Controller",Method = "Get*")]
    public class LoggerPointCut
    {
        [Around]
        public async Task Around(AspectContext context,AspectDelegate next)
        {
            Console.WriteLine("PointcutTest1.Around-start");
            await next(context);
            Console.WriteLine("PointcutTest1.Around-end");
        }

        [Before]
        public void Before()
        {
            Console.WriteLine("PointcutTest1.Before");
            
        }
        
        [After]
        public void After()
        {
            Console.WriteLine("PointcutTest1.After");
            
        }
        
        [AfterReturn(Returing = "value1")]
        public void AfterReturn(object value1)
        {
            Console.WriteLine("PointcutTest1.AfterReturn");
        }
        
        [AfterThrows(Throwing = "ex1")]
        public void Throwing(Exception ex1)
        {
            Console.WriteLine("PointcutTest1.Throwing");
        }       
    }

See more examples Pointcut section programming

How

There are three steps

  • 1. Collect interception operators (for example, Before/After, which we call operators)
  • 2. Construct interceptor chain (link operators according to the above figure)
  • 3. Generate the proxy class proxy target method to execute the interceptor chain constructed above

1. Collect interceptor

Because the use of interceptors is agreed to inherit AspectInvokeAttribute

    /// <summary>
    ///AOP interceptors} include inheritance relationships by default
    /// </summary>
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public class AspectInvokeAttribute : Attribute
    {
        /// <summary>
        ///The lower the sort} value, the higher the priority
        /// </summary>
        public int OrderIndex { get; set; }

        /// <summary>
        ///Group name
        /// </summary>
        public string GroupName { get; set; }
    }

image

This set of annotations is exposed for external use to collect which methods of which classes need to be enhanced

Next, we need to focus on what each intensifier should do

Define an intensifier interface IAdvice

    internal interface IAdvice
    {
        /// <summary>
        ///Interceptor method
        /// </summary>
        ///< param name = "aspectcontext" > execution context < / param >
        ///< param name = "next" > next enhancer < / param >
        /// <returns></returns>
        Task OnInvocation(AspectContext aspectContext, AspectDelegate next);
    }
image

Before intensifier

    /// <summary>
    ///Pre intensifier
    /// </summary>
    internal class AspectBeforeInterceptor : IAdvice
    {
        private readonly AspectBefore _beforeAttribute;

        public AspectBeforeInterceptor(AspectBefore beforeAttribute)
        {
            _beforeAttribute = beforeAttribute;
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            //Execute Before logic first
            await this._beforeAttribute.Before(aspectContext);
            //On the way to the next intensifier
            await next.Invoke(aspectContext);
        }
    }

After intensifier

    /// <summary>
    ///Post intensifier
    /// </summary>
    internal class AspectAfterInterceptor : IAdvice
    {
        private readonly AspectAfter _afterAttribute;
        private readonly bool _isAfterAround;

        public AspectAfterInterceptor(AspectAfter afterAttribute, bool isAfterAround = false)
        {
            _afterAttribute = afterAttribute;
            _isAfterAround = isAfterAround;
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            try
            {
                if (!_isAfterAround) await next.Invoke(aspectContext);
            }
            finally
            {
                //It will be executed regardless of success or failure
                 await this._afterAttribute.After(aspectContext, aspectContext.Exception ?? aspectContext.ReturnValue);
            }
        }
    }

Surround intensifier

    /// <summary>
    ///Surround return intercept processor
    /// </summary>
    internal class AspectAroundInterceptor : IAdvice
    {
        private readonly AspectArround _aroundAttribute;
        private readonly AspectAfterInterceptor _aspectAfter;
        private readonly AspectAfterThrowsInterceptor _aspectThrows;

        public AspectAroundInterceptor(AspectArround aroundAttribute, AspectAfter aspectAfter, AspectAfterThrows chainAspectAfterThrows)
        {
            _aroundAttribute = aroundAttribute;
            if (aspectAfter != null)
            {
                _aspectAfter = new AspectAfterInterceptor(aspectAfter, true);
            }

            if (chainAspectAfterThrows != null)
            {
                _aspectThrows = new AspectAfterThrowsInterceptor(chainAspectAfterThrows, true);
            }
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            Exception exception = null;
            try
            {
                if (_aroundAttribute != null)
                {
                    await _aroundAttribute.OnInvocation(aspectContext, next);
                    return;
                }
            }
            catch (Exception ex)
            {
                exception = ex;
            }
            finally
            {
                if (exception == null && _aspectAfter != null) await _aspectAfter.OnInvocation(aspectContext, next);
            }

            try
            {
                if (exception != null && _aspectAfter != null)
                {
                    await _aspectAfter.OnInvocation(aspectContext, next);
                }

                if (exception != null && _aspectThrows != null)
                {
                    await _aspectThrows.OnInvocation(aspectContext, next);
                }
            }
            finally
            {
                if (exception != null) throw exception;
            }
        }
    }

Return value enhancer

    /// <summary>
    ///Post return value intensifier
    /// </summary>
    internal class AspectAfterReturnInterceptor : IAdvice
    {
        private readonly AspectAfterReturn _afterAttribute;

        public AspectAfterReturnInterceptor(AspectAfterReturn afterAttribute)
        {
            _afterAttribute = afterAttribute;
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            await next.Invoke(aspectContext);


            //Execute exception. Do not execute after # to execute Throw
            if (aspectContext.Exception != null)
            {
                return;
            }


            if (_afterAttribute != null)
            {
                await this._afterAttribute.AfterReturn(aspectContext, aspectContext.ReturnValue);
            }
        }
    }

Exception return enhancer

    /// <summary>
    ///Exception return enhancer
    /// </summary>
    internal class AspectAfterThrowsInterceptor : IAdvice
    {
        private readonly AspectAfterThrows _aspectThrowing;
        private readonly bool _isFromAround;
        public AspectAfterThrowsInterceptor(AspectAfterThrows throwAttribute, bool isFromAround = false)
        {
            _aspectThrowing = throwAttribute;
            _isFromAround = isFromAround;
        }

        public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next)
        {
            try
            {
                if (!_isFromAround) await next.Invoke(aspectContext);
            }
            finally
            {
                //Only when the target method is abnormal will it go. If the enhanced method is abnormal, do not go
                if (aspectContext.Exception != null)
                {
                    Exception ex = aspectContext.Exception;
                    if (aspectContext.Exception is TargetInvocationException targetInvocationException)
                    {
                        ex = targetInvocationException.InnerException;
                    }

                    if (ex == null)
                    {
                        ex = aspectContext.Exception;
                    }

                    var currentExType = ex.GetType();

                    if (_aspectThrowing.ExceptionType == null || _aspectThrowing.ExceptionType == currentExType)
                    {
                        await _aspectThrowing.AfterThrows(aspectContext, aspectContext.Exception);
                    }
                }
            }
        }
    }

2. Assemble the enhancers into a call chain

image

Each node has three messages, as follows

    /// <summary>
    ///Intercept node assembly
    /// </summary>
    internal class AspectMiddlewareComponentNode
    {
        /// <summary>
        ///Next
        /// </summary>
        public AspectDelegate Next;

        /// <summary>
        ///Actuator
        /// </summary>
        public AspectDelegate Process;

        /// <summary>
        ///Components
        /// </summary>
        public Func<AspectDelegate, AspectDelegate> Component;
    }

Using LinkedList to build our zipper call, we add each of the above enhancers as a middleware.

    internal class AspectMiddlewareBuilder
    {
        private readonly LinkedList<AspectMiddlewareComponentNode> Components = new LinkedList<AspectMiddlewareComponentNode>();

        /// <summary>
        ///Add interceptor chain
        /// </summary>
        /// <param name="component"></param>
        public void Use(Func<AspectDelegate, AspectDelegate> component)
        {
            var node = new AspectMiddlewareComponentNode
            {
                Component = component
            };

            Components.AddLast(node);
        }

        /// <summary>
        ///Build interceptor chain
        /// </summary>
        /// <returns></returns>
        public AspectDelegate Build()
        {
            var node = Components.Last;
            while (node != null)
            {
                node.Value.Next = GetNextFunc(node);
                node.Value.Process = node.Value.Component(node.Value.Next);
                node = node.Previous;
            }

            return Components.First.Value.Process;
        }

        /// <summary>
        ///Get next
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private AspectDelegate GetNextFunc(LinkedListNode<AspectMiddlewareComponentNode> node)
        {
            return node.Next == null ? ctx => Task.CompletedTask : node.Next.Value.Process;
        }
    }

The build method then builds a pipeline (a delegate) nested one layer at a time

image

For more information about this design pattern, please refer to my other article: Middleware pattern

The complete execution diagram built according to our requirements is as follows:

Single interceptor or section
image
Multiple interceptors or sections
image

Generate the proxy class proxy target method to execute the interceptor chain constructed above

This step is simple. If it is detected that the target has interceptor annotations, a proxy class will be dynamically created for this class,

The proxy class is generated using castle dynamic component of core

By default, the target method is intercepted in the way of Class+virtual

image

Note: in consideration of performance, cache the build when the project starts, and then use it in the interceptor

 

OK, this is the introduction of interceptors and aspects. For more tutorials, please refer to the project wiki (the tutorial is very detailed, don't forget to give a star)

https://github.com/yuzd/Autofac.Annotation/wiki

 

Pay attention to official account and study together

Keywords: Autofac

Added by depojones on Sun, 23 Jan 2022 04:53:21 +0200