• Autofac的切面编程实现


    面向切面编程:Autofac.Annotation扩展组件是我开源的一款利用打标签完成autofac容器的注入组件。

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

    我们之前介绍了利用Aspect标签来完成拦截器功能

    Aspect是一对一的方式,我想要某个class开启拦截器功能我需要针对每个class去配置。 详情请点击

    比如说 我有2个 controller 每个controller都有2个action方法,

    
        [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;
            }
        }
    
    

    如果我需要这2个controller的action方法都在执行方法前打log 在方法执行后打log 按照上一节Aspect的话 我需要每个controller都要配置。如果我有100个controller的画我就需要配置100次,这样我觉得太麻烦了。所以我参考了Spring的Pointcut切面编程的方式实现了一套类似的,下面看如何用Pointcut的方式方便的配置一种切面去适用于N个对象。

    定义一个切面:创建一个class 上面打上Pointcut的标签 如下:

    Pointcut标签类有如下属性:

    属性名说明
    Name 名称Pointcut切面的名称(默认为空,和拦截方法进行匹配,参考下面说明)
    RetType 匹配目标类的方法的返回类型(默认是%)
    NameSpace 匹配目标类的namespace(默认是%)
    ClassName 匹配目标类的类名称(必填)
    MethodName 匹配目标类的方法名称(默认是%)

    匹配算法

    image 举例:

    匹配结果匹配模板要匹配的字符串
    匹配结果:true "%" ""
    匹配结果:true "%" " "
    匹配结果:true "%" "asdfa asdf asdf"
    匹配结果:true "%" "%"
    匹配结果:false "_" ""
    匹配结果:true "_" " "
    匹配结果:true "_" "4"
    匹配结果:true "_" "C"
    匹配结果:false "_" "CX"
    匹配结果:false "[ABCD]" ""
    匹配结果:true "[ABCD]" "A"
    匹配结果:true "[ABCD]" "b"
    匹配结果:false "[ABCD]" "X"
    匹配结果:false "[ABCD]" "AB"
    匹配结果:true "[B-D]" "C"
    匹配结果:true "[B-D]" "D"
    匹配结果:false "[B-D]" "A"
    匹配结果:false "[^B-D]" "C"
    匹配结果:false "[^B-D]" "D"
    匹配结果:true "[^B-D]" "A"
    匹配结果:true "%TEST[ABCD]XXX" "lolTESTBXXX"
    匹配结果:false "%TEST[ABCD]XXX" "lolTESTZXXX"
    匹配结果:false "%TEST[^ABCD]XXX" "lolTESTBXXX"
    匹配结果:true "%TEST[^ABCD]XXX" "lolTESTZXXX"
    匹配结果:true "%TEST[B-D]XXX" "lolTESTBXXX"
    匹配结果:true "%TEST[^B-D]XXX" "lolTESTZXXX"
    匹配结果:true "%Stuff.txt" "Stuff.txt"
    匹配结果:true "%Stuff.txt" "MagicStuff.txt"
    匹配结果:false "%Stuff.txt" "MagicStuff.txt.img"
    匹配结果:false "%Stuff.txt" "Stuff.txt.img"
    匹配结果:false "%Stuff.txt" "MagicStuff001.txt.img"
    匹配结果:true "Stuff.txt%" "Stuff.txt"
    匹配结果:false "Stuff.txt%" "MagicStuff.txt"
    匹配结果:false "Stuff.txt%" "MagicStuff.txt.img"
    匹配结果:true "Stuff.txt%" "Stuff.txt.img"
    匹配结果:false "Stuff.txt%" "MagicStuff001.txt.img"
    匹配结果:true "%Stuff.txt%" "Stuff.txt"
    匹配结果:true "%Stuff.txt%" "MagicStuff.txt"
    匹配结果:true "%Stuff.txt%" "MagicStuff.txt.img"
    匹配结果:true "%Stuff.txt%" "Stuff.txt.img"
    匹配结果:false "%Stuff.txt%" "MagicStuff001.txt.img"
    匹配结果:true "%Stuff%.txt" "Stuff.txt"
    匹配结果:true "%Stuff%.txt" "MagicStuff.txt"
    匹配结果:false "%Stuff%.txt" "MagicStuff.txt.img"
    匹配结果:false "%Stuff%.txt" "Stuff.txt.img"
    匹配结果:false "%Stuff%.txt" "MagicStuff001.txt.img"
    匹配结果:true "%Stuff%.txt" "MagicStuff001.txt"
    匹配结果:true "Stuff%.txt%" "Stuff.txt"
    匹配结果:false "Stuff%.txt%" "MagicStuff.txt"
    匹配结果:false "Stuff%.txt%" "MagicStuff.txt.img"
    匹配结果:true "Stuff%.txt%" "Stuff.txt.img"
    匹配结果:false "Stuff%.txt%" "MagicStuff001.txt.img"
    匹配结果:false "Stuff%.txt%" "MagicStuff001.txt"
    匹配结果:true "%Stuff%.txt%" "Stuff.txt"
    匹配结果:true "%Stuff%.txt%" "MagicStuff.txt"
    匹配结果:true "%Stuff%.txt%" "MagicStuff.txt.img"
    匹配结果:true "%Stuff%.txt%" "Stuff.txt.img"
    匹配结果:true "%Stuff%.txt%" "MagicStuff001.txt.img"
    匹配结果:true "%Stuff%.txt%" "MagicStuff001.txt"
    匹配结果:true "?Stuff?.txt?" "1Stuff3.txt4"
    匹配结果:false "?Stuff?.txt?" "1Stuff.txt4"
    匹配结果:false "?Stuff?.txt?" "1Stuff3.txt"
    匹配结果:false "?Stuff?.txt?" "Stuff3.txt4"
    
        // *Controller 代表匹配 只要是Controller结尾的类都能匹配
        // Get* 代表上面匹配成功的类下 所以是Get打头的方法都能匹配
        [Pointcut(ClassName = "*Controller",MethodName = "Get*")]
        public class LoggerPointCut
        {
            
        }
    

    定义好了一个Pointcut切面后 需要定义这个切面的拦截方法

    配合Pointcut切面标签,可以在打了这个标签的class下定义拦截方法, 在方法上得打上特定的标签,有如下几种:

    切面执行方法上打标签种类说明
    Before标签 在匹配成功的类的方法执行前执行
    After标签 在匹配成功的类的方法执行后执行
    Around标签 承接了 匹配成功的类的方法的执行权(如果一个切面配置了Around又配置了Before或者After,那么会只执行Around)

    以上3种标签有一个可选的参数:Name (默认为空,可以和Pointcut的Name进行mapping)

    • 因为一个class上可以打多个Pointcut切面,一个Pointcut切面可以根据name去匹配对应拦截方法
        // *Controller 代表匹配 只要是Controller结尾的类都能匹配
        // Get* 代表上面匹配成功的类下 所以是Get打头的方法都能匹配
        [Pointcut(ClassName = "*Controller",MethodName = "Get*")]
        public class LoggerPointCut
        {
            /// <summary>
            /// 打上Before标签 代表满足匹配的方法 在执行之前会执行下面的Before()方法
            /// </summary>
            [Before]
            public void Befor()
            {
                Console.WriteLine("before");
            }
    
            /// <summary>
            /// 打上After标签 代表满足匹配的方法 在执行之前会执行下面的After()方法
            /// </summary>
            [After]
            public void After()
            {
                Console.WriteLine("after");
            }
        }
    

    如果是用Around环绕的话

    
        // *Controller 代表匹配 只要是Controller结尾的类都能匹配
        // Get* 代表上面匹配成功的类下 所以是Get打头的方法都能匹配
        [Pointcut(ClassName = "*Controller",MethodName = "Get*")]
        public class LoggerPointCut
        {
            
            /// <summary>
            /// 打上Around标签 承接了 匹配成功的类的方法的执行权
            /// </summary>
            /// <param name="context"></param>
            [Around]
            public void Around(AspectContext context)
            {
                //执行原目标方法前
                Console.WriteLine(context.InvocationContext.MethodInvocationTarget.Name + "-->Start");
                //执行原目标方法
                context.InvocationProceedInfo.Invoke();
                //执行原目标方法后
                Console.WriteLine(context.InvocationContext.MethodInvocationTarget.Name + "-->End");
            }
        }
    

    执行方法的参数说明:

    执行方法的参数可以是DI容器的类型,会在执行时自动注入进来,可以使用Autowired,Value等标签来修饰参数。

    还有一个特殊的执行参数可以注入,那就是AspectContext,这个类型里面你可以获取到被拦截的方法的信息,以及执行原方法的委托。

    注意:这个执行后 有2种

    • 正常执行成功
    • 有异常,若方法参数注入了AspectContext 那么可以通过Exception属性值获得异常内容

    按照上面的配置

    • ProductController.GetProduct 会被匹配
    • UserController.GetUser 会被匹配

    在执行上面这2个方法的时候会

    • 先执行 LoggerPointCut.Before方法
    • 再执行 LoggerPointCut.After方法

    利用Autofac的这个开源扩展组件很方便的实现切面编程,总结切面编程三步骤

    • 1.定义一个切面,编写要匹配的目标类的方法(采用sql的like方式匹配),所以一个切面可以拦截n个目标
    • 2.定义对应的拦截方法
    • 3.执行被匹配的方法时会执行对应的拦截方法

    利用切面编程来自动加事物的后台系统例子

    https://github.com/yuzd/AntMgr/wiki

  • 相关阅读:
    设计模式之 观察者模式
    设计模式之 模板方法模式
    设计模式之 状态模式
    设计模式之 策略模式
    设计模式之 桥接模式
    设计模式之 外观模式
    设计模式之 代理模式
    设计模式之 装饰者模式
    设计模式之 适配器模式
    设计模式之 组合模式
  • 原文地址:https://www.cnblogs.com/yudongdong/p/12819866.html
Copyright © 2020-2023  润新知