结合ASP.NET Core,浅谈AOP的实现

asp.net core 学习 专栏收录该内容
1 篇文章 0 订阅

一、什么是AOP?

AOP是程序开发的一种热点,AOP是OOP的延续。

官方解释,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。是一种通过预编译方式和运行期动态代理实现程序功能统一维护的技术。利用AOP可以对业务逻辑的各个部分进行分割、隔离,从而降低业务逻辑的各个部分之间的耦合度,提高程序的可维护性,提高了开发效率。

个人理解,AOP是一种面向方面(模块)的编程,能够对业务逻辑进行分割、隔离。就好比在程序开发中,每个人的各司其职,前端开发工程师只需要关注前端部分,如何调用后台API、后端开发工程师只需要关注后端部分,提供API给前端、UI设计师只负责设计图形界面、美工只负责UI的美化等。即一个应用程序,也好比一个公司,每个职位每个人都是相对独立的,销售就负责跑业务,实施负责对接客户,HR做人力资源划分,开发部门做产品开发,测试部门做产品测试,维护部门负责产品上线、系统维护等。使用AOP,即可以将公共代码部分抽离出来,比如:每个方法都是需要记录日志,验证权限,捕捉异常。我们可以将这些业务,封装成独立的模块,将这部分的业务交给模块执行。则程序开发人员做开发工程中,我们就可以只关注核心业务逻辑的编写,不需要考虑日志的记录,权限认证和捕捉异常等。即将代码模块化。

二、为什么要用AOP?

现在有这么一个业务场景:

场景一:
普通顾客lyq来到一家餐厅吃饭,服务员在每次点单前后,都要记录一条log。

当我们拿到这个简单的需求,我们肯定很快就想到这么实现这个业务:

    public class OrderMenu
    {
        public string Name { get; set; }

        public decimal Price { get; set; }

        public string Remarks { get; set; }
    }
    
    public class Customer
    {
        public string Name { get; set; }

        public void PlaceOrder(OrderMenu order)
        {
            Console.WriteLine("点单开始");          
            Console.WriteLine($@"我需要一份{order.Name},价钱是{order.Price},备注:{order.Remarks}");
            Console.WriteLine("点单结束");
        }
    }

看似很简单的需求,看起来用不用AOP实现都一样。但是在程序开发中,一成不变的需求往往是不存在的,在开发过程中,我们往往会在这样的基础上,增添需求。比如:顾客分VIP,VIP消费能够累计积分,积分每满1000分能够兑换100元的餐券。这个时候,我们就要修改业务代码。

我们给Customer类加一个Role属性,用来记录当前顾客时VIP还是普通客户,修改如下:

    public class Customer 
    {
        public string Name { get; set; }

        public string Role { get; set; }

        public void PlaceOrder(OrderMenu order)
        {
            Console.WriteLine("点单开始");      
            
            if (Role == "VIP")            
                Console.WriteLine($@"我是VIP,我需要一份{order.Name},价钱是{order.Price},备注:{order.Remarks},点餐成功,积分累计加{order.Price}");
            else
                Console.WriteLine($@"我是普通顾客,我需要一份{order.Name},价钱是{order.Price},备注:{order.Remarks}");

             Console.WriteLine("点单结束");
        }
    }

可是,客户不会这么温柔啊。在这个的基础上,客户还要添加业务逻辑。每次点单时,要判断厨房是否还有这个菜品,如果有,则正常下单,如果没有,则提示顾客已经没有这个菜品了,让顾客重新点餐;每次点餐时,也要判断菜品的价格是否合理,如果太低了(比如:猪肉涨价),会提示本次下单失败等等。。。。。。

显然,当我们丰满这个业务的时候,我们都会修改这部分的代码。久而久之,这个业务类就会越来越臃肿,越来越难维护。因此,才有了本文开篇所说的使用AOP,将业务模块和记录Log模块分割开来,业务模块只专注业务部分,记录Log模块只做操作日志处理。这样,我们有利于项目后期的维护和扩展,当出现问题的时候,我们能够快速的定位问题和解决问题。下面,来说一下如何实现AOP。

三、用静态代理模式和装饰着模式实现AOP

我们先定义一个代理接口IOrder:

    public interface IOrder
    {
        void PlaceOrder(OrderMenu order);
    }

然后我们要再添加一个代理类OrderMenuProxy,并且继承这个IOrder的接口:

    public class MenuOrderLogProxy : IOrder
    {
        private IOrder _order;

        public MenuOrderLogProxy(IOrder order)
        {
            _order = order;
        }

        public void PlaceOrder(OrderMenu order)
        {

            Console.WriteLine("点单开始");
          
            _order.PlaceOrder(order);
            
            Console.WriteLine("点单结束");
        }
    }

然后,我们再修改一下业务类,让他们也继承这个IOrder的接口:

    public  class Customer : IOrder
    {
        public string Name { get; set; }

        public string Role { get; set; }

        public void PlaceOrder(OrderMenu order)
        {
            if (Role == "VIP")
            {
                Console.WriteLine($@"我是VIP,我需要一份{order.Name},价钱是{order.Price},备注:{order.Remarks},点餐成功,积分累计加20");
            }

            Console.WriteLine($@"我是普通顾客,我需要一份{order.Name},价钱是{order.Price},备注:{order.Remarks}");
        }
     }
       OrderMenu order = new OrderMenu()
       {
            IsActive = true,
            Name = "小炒牛肉",
            Price = 25,
            Remarks = "微辣"
        };

        IOrder customer1 = new Customer();            
        OrderMenuProxy orderProxy = new OrderMenuProxy(customer1);
        
        orderProxy.PlaceOrder(order);

在这里插入图片描述
可以看出,AOP代码的维护性更高,当用户需要再修改需求的时候,我们只需要针对相对应的模块做修改就可以了,不必将某一模块的内容分散到各个业务模块中去。业务模块只要专注与业务方法的编写,日志模块只关注怎么日志的记录。

用户毕竟是用户,用户突然脑洞大开,想在以上的基础上,再加上一次异常捕获模块。如果还是使用静态代理的方法,我们可能会将异常处理的方法放到OrderMenuProxy这个代理类中:

    
    public class OrderMenuProxy : IOrder
    {
        private IOrder _order;

        public OrderMenuProxy(IOrder order)
        {
            _order = order;
        }

        public void PlaceOrder(OrderMenu order)
        {
            try
            {
                Console.WriteLine("记录异常开始.");

                Console.WriteLine("点单开始");

                if (order.Price > 0 && order.IsActive)
                {
                    _order.PlaceOrder(order);
                }
                Console.WriteLine("点单结束");
            }
            catch (Exception ex)
            {
                Console.WriteLine("下单异常:" + ex.Message);
            }
            finally
            {
                Console.WriteLine("记录异常结束.");
            }
        }
    }

很显然,日志模块和异常处理模块耦合在一起了。如果后面随着业务的增加,则这个代理类的方法将越来越复杂,这就违背开始我们的初衷,将整个业务的模块分割开来。

看一下我们用装饰者模式,如何解决这样的问题,我们再定义一个OrderMenuExceptionProxy类:

    
    public class OrderMenuExceptionProxy: IOrder
    {
        private IOrder _order;

        public OrderMenuExceptionProxy(IOrder order)
        {
            _order = order;
        }

        public void PlaceOrder(OrderMenu order)
        {
            try
            {
                Console.WriteLine("记录异常开始.");              
                _order.PlaceOrder(order);                
            }
            catch (Exception ex)
            {
                Console.WriteLine("下单异常:" + ex.Message);
            }
            finally
            {
                Console.WriteLine("记录异常结束.");
            }
        }
     }
          OrderMenu order = new OrderMenu()
            {
                IsActive = true,
                Name = "小炒牛肉",
                Price = 25,
                Remarks = "微辣"
            };

            IOrder customer1 = new Customer();

            IOrder orderMenuExceptionProxy = new OrderMenuExceptionProxy(customer1);

            IOrder orderMenuLogProxy = new OrderMenuLogProxy(orderMenuExceptionProxy);

            orderMenuLogProxy.PlaceOrder(order);

在这里插入图片描述
用装饰者模式,能够很好的实现我们想要的功能。可以将业务模块,日志模块,异常处理模块分割开来,降低了模块与模块之间的依赖性。

总结:静态代理模式和装饰者模式这两种方法都能实现AOP,其实这两种方法在使用的时候区别并不是很大。但是静态代理模式讲究的是将业务功能交给代理类去实现;而装饰者模式讲究的是组合方法,为这个业务类添加各种装饰品(功能)。

上述的两种方法都是能够实现AOP,但有一点很麻烦的是,当我们每次调用这个下单方法的时候,我们都要实例化每一个代理类。如果我们要调用100次这个方法方法,我们就要实例化100个代理类,这无疑会对我们的程序开发增加负担,那我们如何减轻这负担呢?我们可以使用动态代理的方法,实现AOP。

四、用动态代理的方法实现AOP

先引用Castle.Core的NuGet包。Castle.Core是第三方的开源框架,用于写拦截器,实现AOP。何谓拦截器,就是每次再调用这个方法的时候,拦截器会将这个方法捕获,然后让我们的程序先去运行我们所指定的方法,再去执行我们所请求的方法。
在这里插入图片描述
我们分别定位两个拦截器LogInterceptor,ExceptionInterceptor:

    public class LogInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            Console.WriteLine("点单开始");

            invocation.Proceed();

            Console.WriteLine("点单结束");
        }
    }


    public class ExceptionInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            try
            {
                Console.WriteLine("记录异常开始。");

                invocation.Proceed();
            }
            catch (Exception ex)
            {
                Console.WriteLine("下单异常:" + ex.Message);
            }
            finally
            {
                Console.WriteLine("记录异常结束。");
            }

        }
    }

然后我们重写一下Customer下单的方法,将这个方法设置为一个虚方法(virtual ):

      public virtual void PlaceOrder(OrderMenu order)
      {
            if (Role == "VIP")
            {
                Console.WriteLine($@"我是VIP,我需要一份{order.Name},价钱是{order.Price},备注:{order.Remarks},点餐成功,积分累计加{order.Price}");
            }

            Console.WriteLine($@"我是普通顾客,我需要一份{order.Name},价钱是{order.Price},备注:{order.Remarks}");
      }

接下来看怎么用,能够实现我们想要的功能:

   var proxyGenerator = new ProxyGenerator();//创建一个代理生成器
   var customerService = proxyGenerator.CreateClassProxy<Customer>(new ExceptionInterceptor(),new LogInterceptor());
   
   customerService.Role = "VIP";
   customerService.PlaceOrder(order);

在这里插入图片描述

五、ASP.NET Core 本身实现AOP的方式

1.Filter过滤器
ASP.NET CORE MVC一共有四种过滤器:分别是权限过滤器(AuthorizationFilter),资源过滤器(ResourceFilter),操作过滤器(ActionFilter),异常过滤器(ExceptionFilter).(优先级:权限>资源>操作>异常)

权限过滤器主要用作权限认证,每当用户请求这个方法时,会判断当前用户是否有权限,如果没有权限会返回401的错误。

资源过滤器:常用于获取缓存资源,当用户有权限访问请求方法时,会用来读取用户存储在服务器上的资源。

操作过滤器:针对请求方法前后所做的操作。

异常过滤器:针对的是程序在运行过程中抛出异常,捕获异常,常用于全局配置

   public class MyAuthorizeFilterAttribute : Attribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            throw new NotImplementedException();
        }
    }


    public class MyResourceFilter : Attribute, IResourceFilter
    {
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            throw new NotImplementedException();
        }

        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            throw new NotImplementedException();
        }
    }


    public class LogAFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext context)
        {

        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {

        }
    }

    public class MyExceptionFilterAttribute : ExceptionFilterAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            base.OnException(context);
        }
    }

然后,只要在请求的方法上贴上这个Attribute就好了。

[LogAFilterAttribute]
public IActionResult TestIndex()
{
     HttpContext.Session.SetString("userName", "Tim");
     var obj = HttpContext.Session.GetString("userName");
        
     return Json($"已经设置好值了:{obj}");
}

2. MiddleWare中间件
中间件是组装到应用程序管道中以处理请求和响应的软件。 每个组件都可以选择是否将请求传递给管道中的下一个组件,每一个组件都可以在调用管道中的下一个组件之前和之后执行工作。

中间件和Filter过滤器的讲解,请详细看下一篇文章。

  • 0
    点赞
  • 0
    评论
  • 3
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

i李泳谦谦谦谦谦

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值