好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

使用Spring.net

使用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 就是切入点和通知的映射关系。细节看代码,不做详细解释了。

使用

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

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

结尾:

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

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于使用Spring.net的详细内容...

  阅读:49次