• 使用Spring.net


    AOP 实现积分服务

    前言:


            AOP(Aspect Oriented Programming)的是面向方面编程,如您不了解可搜索之。AOP目的是将系统按照功能进行横向切分,被切分下来的功能也就是面向的方面,例如系统 的日志处理、安全、事物等,ASP.NET MVC中的Filters就是AOP的思想实现。AOP带来的好处是什么呢?AOP是面向对象设计原则中的 单一职责(SAP)的体现,可以有效降低各个模块间的耦合度,使整个系统健康有效的抵御各种需求变化。

            本文介绍的积分服务是在某团购网站中的一个模块,需求并不复杂,如下:

    • 在一些功能点上对用户的积分进行变更。如:用户注册时给用户增加积分、用户交易成功时给用户增加积分、用户退团时给用户减少积分。
    • 功能点与具体积分分值是可配置的。因为积分会根据产品的生命周期进行调整的,因此要能有效快速的控制积分策略。

            Spring.net 是应用程序开发框架,提供了一整套在应用程序开发中的解决方案,如作为基础的:IOC 、AOP 以及在其上扩展的数据访问层支持、WEB支持、服务支持 等等,如果要用AOP就必须使用IOC,也就是说所有的对象都要从Spring.net的IOC容器中获取,Spring.net才能得到控制权拦截切入 点实现AOP。

    实现方案:


    首先介绍本项目的架构及背景:

    • 标准的三层架构,并且每一层做了接口抽象,每一层只依赖于抽象也就是接口。
    • WEB端使用了ASP.NET MVC,由于MVC的特性,基本上每个功能点对应一个方法,这样的粒度有利于功能点与积分项的绑定。
    • 使用Spring.net 做IOC容器,对各个层的依赖关系做注入。

    有了以上先决条件后使用Spring.net AOP实现 积分服务需要进行以下几个步骤:

    • 创建积分服务类

    积分服务类职责是封装与积分相关的处理逻辑,包括积分变更、查询积分历史等。我们这里对其做了抽象

        public interface IScoreService
    {
    void Record(SiteUser user,int scoreValue, ScoreEvent scoreEvent,string data);
    }

    实现类:

     public class ScoreService:IScoreService
    {
    public ISiteUserDao SiteUserDao { get; private set; }
    public IScoreItemDao ScoreItemDao { get; set; }
    public ScoreService(ISiteUserDao siteUserDao, IScoreItemDao scoreItemDao)
    {
    SiteUserDao = siteUserDao;
    ScoreItemDao = scoreItemDao;
    }
    [Transaction]
    public void Record(SiteUser user, int scoreValue, ScoreEvent scoreEvent,string data)
    {
    if (user == null) throw new ArgumentNullException("user");
    user.Score += scoreValue;
    if (user.Score < 0) throw new Exception("积分不足");
    //todo:
    var si = new ScoreItem { ForUser = user, HappeningTime = DateTime.Now.ChinaTime(), Score = scoreValue, ScoreEvent = scoreEvent,Data=data};
    ScoreItemDao.Insert(si);
    
                SiteUserDao.Update(user);
    }

    其中SiteUserDao 与 ScoreItemDao 是数据访问层接口,具体实现类使用Spring.net在进行运行时注入进来。可能有人关注到了[Transaction] 这个Attribute,它是Spring.net对分布式事务的支持,详细请参考:http://www.springframework.net/doc-latest/reference/html/transaction.html

    • 创建积分策略实体类

    积分策略类是一个实体类,它指明了功能点与积分的映射关系。在本项目中将积分策略直接配置到了IOC容器中,当然根据需求的变化也可以将其推入数据库存储。

        /// <summary>
    /// 积分策略
    /// </summary>
    public class ScorePolicy
    {
    
            /// <summary>
    /// 策略名称
    /// </summary>
    public string PolicyName { get; set; }
    /// <summary>
    /// 积分事件
    /// </summary>
    public ScoreEvent ScoreEvent{get;set;}
    /// <summary>
    /// 分值
    /// </summary>
    public int ScoreValue { get; set; }
    //获取用户id表达式 note:spring.net expresstion
    public string GetUserIdExpr { get; set; }
    //产生附加数据表达式 note:spring.net expresstion
    public string GenDataExpr { get; set; }
    }

    需要解释的是后两个成员 GetUserIdExpr 与GenDataExpr ,这2个成员使用了Spring.net的表达式,详细请参考:http://www.springframework.net/doc-latest/reference/html/expressions.html,这2个成员的用途与业务逻辑无关,是支撑性成员,稍后会在通知类中看到它们。

    • 选择切入点

    为Spring.net指定某种方式来拦截执行流。Spring.net支持静态切入点与动态切入点,在静态切入中包含正则表达式切入点与特性切入点,详细请参考:http://www.springframework.net/doc-latest/reference/html/aop.html#aop-pointcuts,本项目选择了使用特性切入点,也就是利用.net 的Attribute特性,显式的在被拦截的方法前指定,以便于Spring.net AOP对其敏感。

       public class ScoreAttribute:Attribute
    {
    /// <summary>
    /// 策略名,默认值为当前方法名
    /// </summary>
    public string PolicyName { get; set; }
    }

    可以看到这是一个标准的Attribute,包含一个成员:策略名称,可以在显式插入Attribute时指定一个策略实体与之对应。

    • 创建通知类。

    当Spring.net拦截到指定的执行流中的消息后将按照指定的方式建立通知。Srping.net AOP支持4种方式通知,包括:

    •  
      • 环绕通知
      • 前置通知
      • 异常通知
      • 后置通知

    详细请参考:http://www.springframework.net/doc-latest/reference/html/aop.html#aop-advice-types。本项目选择后置通知方式对积分处理,也就是在被拦截方法之后产生通知,如下:

     public class ScoreAfterAdvice :
    
    Spring.Aop.IAfterReturningAdvice
    {
    public IScoreService ScoreService { get; set; }
    /// <summary>
    /// 积分策略表,
    /// </summary>
    private static IDictionary<string , ScorePolicy> _policies;
    public ScoreAfterAdvice(IDictionary<string, ScorePolicy> policies)
    {
    _policies = policies;
    }
    public void AfterReturning(object returnValue, MethodInfo method, object[] args, object target)
    {
    var attr = method.GetCustomAttributes(typeof(ScoreAttribute), false).FirstOrDefault() as ScoreAttribute;
    if (attr == null) throw new NullReferenceException();
    var keyname=string.IsNullOrEmpty(attr.PolicyName) ? method.Name : attr.PolicyName;
    if(!_policies.ContainsKey(keyname)) return; //todo:record log
    //加载积分策略
    var policy = _policies[keyname];
    try
    {
    //获取目标用户ID
    var user = ExpressionEvaluator.GetValue(args, policy.GetUserIdExpr);
    if (user != null)
    {
    var data = string.IsNullOrEmpty(policy.GenDataExpr) ? string.Empty : ExpressionEvaluator.GetValue(args, policy.GenDataExpr);
    //处理积分
    ScoreService.Record((SiteUser) user, policy.ScoreValue, policy.ScoreEvent,data.ToString() );
    }
    }
    catch (Exception e)
    {
    //todo:log
    }
    }
    }

    本类实现了String.Aop.IAfterReturningAdvice类,在产生通知时会调用 AfterReturning方法,其中有几个关键参数:

    •  
      • returnValue, 被拦截方法的返回值,不能修改。
      • method,被调用的方法。
      • args ,被调用方法参数

    代码并不复杂,可以看到如何利用这些参数进行协作的。

    • 配置(集成)

    万事俱备只欠东风,我们需要将之前的组成部分进行集成,让其运转起来。这个集成主要通过Spring.net配置来完成,其片段如下:

     <aop:config >
    <aop:advisor advice-ref="ScoreAfterAdvice" pointcut-ref="ScoreAttributePointcut"/>
    </aop:config>
    <object id="ScoreAfterAdvice" type="GroupPurchase.Services.Score.Handler.ScoreAfterAdvice,GroupPurchase.Services">
    <description>积分 afteradvice</description>
    <constructor-arg name="policies">
    <dictionary key-type="string" value-type="GroupPurchase.Services.Score.Handler.ScorePolicy,GroupPurchase.Services">
    <entry key="CreateSiteUser"><!--注册用户-->
    <object type="GroupPurchase.Services.Score.Handler.ScorePolicy,GroupPurchase.Services">
    <property name="GetUserIdExpr" value="[0]"/>
    <property name="GenDataExpr" value=""/>
    <property name="ScoreValue" value="10"/>
    <property name="ScoreEvent" value="1"/>
    </object>
    </entry>
    </dictionary>
    </constructor-arg>
    <property name="ScoreService" ref="ScoreService"/>
    </object>
    <object id="ScoreAttributePointcut" type="Spring.Aop.Support.AttributeMatchMethodPointcut">
    <property name="Attribute" value="GroupPurchase.Services.Score.Handler.ScoreAttribute,GroupPurchase.Services" />
    </object>

    可以看到我们定义了一个积分策略,在用户注册成功后将为用户增加10个积分,为ScoreAfterAdvice类注入一个积分策略字典与积分服务;另外配置了一个advisor 就是切入点和通知的映射关系。细节看代码,不做详细解释了。

    • 使用

    经过以上几步的折腾,我们算是搭建完毕了积分系统,如何使用呢? 分两步:

    1. 根据产品需求,对积分策略进行配置,参照上一步。
    2. 在对应的功能点插入Attribute,例如在服务层中的用户注册的代码片段:
            [Notify(EventId = "AccountRegistered")]
    [Score]
    [Transaction]
    public void CreateSiteUser(SiteUser user)
    {
    SiteUserDao.Insert(user);
    }

    结尾:


    除了积分服务外,这个项目的通知服务也是用AOP方式实现的,总的来说AOP为我们带来了一定的好处,但目前注入对框架依赖性较强,如果我将Spring.net换成EntLib势必有部分代码要重写。欢迎各位朋友批评指正,共同进步:)

  • 相关阅读:
    移动运营四:如何优化页面布局
    移动运营三:如何进行场景洞察
    移动运营二:如何更加了解你的用户
    移动运营一:如何进行运营效果分析
    操作系统的系统调用
    从图灵机到操作系统的启动
    伸展树(splay tree)
    AVL树
    二叉搜索树
    表达式树
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2086568.html
Copyright © 2020-2023  润新知