前言:

记得N年前,就听过AOP,曾扫过几篇文章,不过看不懂,还是N年前,面试被问到AOP的切面,还是不懂!

中秋之假,有点闲,在博客园里搜了下AOP看了看,试图看懂些许文章,可惜文章都说的太中规中矩,没发现一篇能浅显看的易懂的。

 

AOP,全称Aspect Oriented Programming,中文名称叫面向方面编程,也叫面向切面编程

 

AOP,你出来的意图?

借用一图:

不就为解耦,分离出权限/操作日志/异常/事务等模块出来?

 

这里贴一段我项目中的代码,最常见的修改密码:

        protected void Page_Load(object sender, EventArgs e)
        {
            
if (!Page.IsPostBack)
            {
                SetPermission(SysModule.修改密码).HasUpdatePermission();
                lblUser.Text 
= CurrentUser.User_Name;
            }
        }
        
protected void btnOK_Click(object sender, EventArgs e)
        {
            
if (CurrentUser.Password == tbxPassword.Text)
            {
                UserListBean entity 
= new UserListBean();
                entity.ID 
= CurrentUser.ID.Value;
                entity.Password 
= tbxPwdNew.Text;
                
if (Factory<UserListBean>.Instance.Update(entity))
                {
                    LogWrite.Write(LogModule.用户管理, LogType.编辑, 
string.Format("修改密码[用户名称={0}]", CurrentUser.User_Name));
                    CommonHelper.ShowDoneMessage();
                }
            }
            Show(
"旧密码错误!",Request.RawUrl);
        }

 

问题说明:

几乎每个页面在PageLoad都有权限判断语句;在更新成功时,都会有操作日志记录,非常之常见。

AOP想让我们干什么?就是让我们,不要在每个页面都这样写权限判断和日志操作了。

 

不让我这样写,那咋写?AOP你让我咋写呢?

AOP说:

你可以独立实现权限/操作日志等模块,然后使用动态拦截调用的方法,

在方法调用之前,你先调用[Begin]函数实现权限判断;

在方法调用之后,再调用[End]函数来写日志.

 

那咋整呢?

AOP说:

这个,两种方式

说起来有点复杂,借用别人的话说一下好了:

目前在.Net下实现AOP的方式分为两大类:
一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代或修饰原有对象行为的执行;
二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
动态代理实现方式利用.Net的Attribute和.Net Remoting的代理技术,对对象执行期间的上下文消息进行截取,并以消息传递的方式执行,从而可以在执行期间加入相关处理逻辑实现面向方面的功能(请参考:http://www.cnblogs.com/wayfarer/articles/256909.html);
而静态织入的方式实现一般是要依靠一些第三方框架提供特定的语法,例如PostSharp,它的实现方式是采用 MSIL Injection和MSBuild Task在编译时置入方面的代码,从而实现AOP。

说白一点:

在一些类和方法上面标属性,然后继承特定接口或类,之后便可进行消息拦截,拦截后还是根据属性来调用其它方法

有源码呢:

我这里有一份动代代理实现拦截的代码,可点击下载

 

看完源码我说说:

我本来就两行代码,一行权限,一行操作日志,
为了解耦,你弄了一堆鬼都看不懂的代码,好吧,算是能正常运行,可是这咋应用上?
权限的类型我到哪传?操作日志的类型,操作的用户名称,我去哪拿去?你说,唉,越来越复杂了!
还有,大伙到处说你性能低呢,还有你解耦还有不就为了维护方便么,可是你那一堆鬼都难看懂的代码,要是换另一个人来维护,那不是天天要叫太阳。

 

AOP说:

我的想法很好的,让你分离下权限/操作日志,独立出去统一管理,以后改代码不用跑每个页面改去。

至于实现,是相对复杂了点,为能了拦截每个方法的调用,在调用的前后插入其它方法,得底层一点,所以不平易近人了。

要简单实现,你们还是找IOC好了,它简单一些。

 

IOC:英文为 Inversion of Control,即反转模式又称DI:Dependency Injection,即依赖注入

IOC说:

我可不像AOP那些实现一样,能动态拦截方法,遍历标志的属性再执行相应的方法那么强大而复杂。

其实我很简单,就是在类里随便挖个洞,能让我钻进去就行了。

 

来个简单示例

一个接口:

    public interface IAop
    {
        
void Begin();
        
void End();
    }

注入到人家的构造函数里:

    public class MAction
    {
        IAop _Aop;
        
public MAction(IAop aop)
        {
            _Aop 
= aop;
        }
        
public void Add()
        {
            _Aop.Begin();
            Console.WriteLine("
http://cyq1162.cnblogs.com/");
            _Aop.End();
        }
    }

看看

现在如果你调用Add,是不是就要先执行Begin方法,执行完内容之后,又要执行End方法了。

接下来,实现接口:

    public class AopAction:IAop
    {
        
#region IAop 成员

        
public void Begin()
        {
  //其实我是很茫然的,我不知道我应该干什么,我被很多方法调用,可是,我咋知道是谁调用我呢?我要怎么写分支语句呢?啥都没,只好输出begin...
            Console.WriteLine(
"begin......");
        }

        
public void End()
        {
          //同样我也很困惑,我是在人家操作完后调用的,我都不知你是更新/删除/插入还是其它,或者写日志你得给我个ID啊,啥都没,我只能输出end....
            Console.WriteLine(
"end......");
        }

        
#endregion
    }

最后调用:

MAction action = new MAction(new AopAction());
action.Add();

结果当然是:

end......

 

当然了,IOC强大的东西不仅仅只是在构造函数注入了,不过在哪注入,还是通过反射注入,都不是本节的重点,也不是现在关注的问题。

 

现在的问题是:

我上面的代码要咋整改呢?说了一堆,应用不上,不白说了?

 

好吧,说正事了,咋改呢?

其实数据库操作不外乎增删改查了。只要在增删改查上做足功夫,基本上应用是没问题了,复杂的应用就另外找大仙去吧。

我们只要在每个Insert/Update/Delete/Select的方法之前与之后插入Begin与End函数,同样使用IOC注入,即可,方法不多吧。

分析下:

比如上面的权限,说到底内部还是调用select来决定有没有权限;
上面的操作日志,也就是Update后的事情;
所以,具体代码怎么改,不同的框架实现改起来是不同了。

还有最重要的是,统一管理后,你怎么传参获到分类与更新后的用户名称。
说白了Begin和End函数,你得给点参数,不然叫人家方法写啥呢?
难道页面放个隐藏域,再Request一下或取当前Url来判断分支与使用?是个方法,不过有点悬!!!

 

举例说明使用:这里使用 CYQ.Data 框架 来说明

1:定义操作枚举

    public enum AopEnum
    {
        Select,
        Insert,
        Update,
        Delete,
        Fill,
        GetCount,
        ExeMDataTable,
        ExeNonQuery,
        ExeScalar
    }

 

2:定义IAop接口

    public interface IAop
    {
        
void Begin(AopEnum action,string objName, params object[] aopInfo);
        
void End(AopEnum action, bool success, object id, params object[] aopInfo);
        
void OnError(string msg);
    }

 

3:预先实现

internal class Aop:IAop
{
   
#region IAop 成员
    
public void Begin(AopEnum action, string objName, params object[] aopInfo){}
    
public void End(AopEnum action, bool success, object id, params object[] aopInfo){}
    
public void OnError(string msg){}
    
#endregion
}

 

4:内部用Setter方式开个洞,不使用构造函数注入,方法里增加Begin调用与End调用

    public class MAction : IDisposable
    {
        
private Aop.IAop _Aop=new Aop.Aop();//切入点
         public void SetAop(Aop.IAop aop)
        {
            _Aop 
= aop;
        }
        
public bool Fill(object whereparams object[] aopInfo)
        {
            _Aop.Begin(Aop.AopEnum.Fill,_TableName, aopInfo);

            
bool result = 操作结果

            _Aop.End(Aop.AopEnum.Fill, result, aopInfo);
            
return result;
        }
     }

 

说明:

使用预先实现,可以不用判断_Aop是不是为null

不使用构造函数实现注入,是为了使用继承来一次性使用SetAop,从而避免到处使用SetAop

 

 

框架的代码基本就到此结束,那如何使用框架呢?

 

1:继承IAop接口,如

接口实现统一管理处

/// <summary>
/// 作者:路过秋天
/// 博客:http://cyq1162.cnblogs.com
/// </summary>
public class MyAop:CYQ.Data.Aop.IAop
{

    
#region IAop 成员

    
public void Begin(CYQ.Data.Aop.AopEnum action, string objName, params object[] aopInfo)
    {
        
switch (action)
        {
            
case CYQ.Data.Aop.AopEnum.Select:
                HttpContext.Current.Response.Write(
string.Format("Begin方法:权限判断执行:[{0}表查询]<br>",objName));
                
break;
        }
    }

    
public void End(CYQ.Data.Aop.AopEnum action, bool success, object id, params object[] aopInfo)
    {
        
switch (action)
        {
            
case CYQ.Data.Aop.AopEnum.GetCount:
                
if (aopInfo.Length > 0)
                {
                    
switch (aopInfo[0].ToString())
                    {
                        
case "Login":
                            HttpContext.Current.Response.Write(
"End  方法:操作日志:登陆结果:" + (success ? "成功" : "失败"+ "<br>");
                            
break;
                    }
                }
                
break;
        }
    }

    
public void OnError(string msg)
    {
        
throw new Exception("The method or operation is not implemented.");
    }

    
#endregion
}

 

说明:

对于权限判断/操作日志等操作,这里集中操作了,当然也是在这里调用其它独立模块就行了,至于界面,就不用写权限判断和日志记录了。

 

2:界面调用

      MAction action = new MAction(TableNames.Users);
      action.SetAop(
new MyAop());//这句话怎么隐掉请看下面的MyAction的实现。
      action.Select();
//权限操作
       action.GetCount("UserName='路过秋天' and Password='http://cyq1162.cnblogs.com'""Login");//登陆操作
       action.Close();

 

3:输出结果

Begin方法:权限判断执行:[Users表查询]
End 方法:操作日志:登陆结果:失败

 

说明:

1:对于GetCount,我们在最后增加了附加信息"Login"来标识是个登陆操作,对于Select,也同样可以附加标识,当然定义成枚举来标识就更便捷与清晰了。

2:好像界面调用里出现了SetAop方法,那每次操作都要来一次?能不能自动点,省的一行算一行?答案是可以的。

 

4:定义新的操作类MyAction,继承自MAction,如

public class MyAction : CYQ.Data.MAction
{
    MyAop aop;
    
public MyAction(object tableName): base(tableName)
    {
        SetAop();
    }
    
public MyAction(object tableName, string conn): base(tableName, conn)
    {
        SetAop();
    }
    
private void SetAop()
    {
        aop 
= new MyAop();
        
base.SetAop(aop);
    }
    
public void SetAopReuse()
    {
        
base.SetAop(aop);
    }
}

 

说明:

这个时候,你界面调用就可以省略那句SetAop函数,甚至:

在你不需要日志时可以使用:action.SetNoAop();

接着又要重新使用日志功能:action.SetAopReuse();

 

OK,本篇写到这里也算结束了。

 

结言:

昨天扫了一天的Aop文章,相对来说本篇算是一个简单的总结,同时将其应用到 CYQ.Data 框架 里面,使用权限/操作日志/异常处理等的分离。

希望本篇对Aop或Ioc一无所知的人,看了也能略懂那么一点,深层点的请看博园的其它文章,免说我误导初学者。

本框架对Aop的最新应用请到 CYQ.Data 轻量数据层之路 bug反馈、优化建议、最新框架下载 下载抢先体验版本。

最后欢迎大家指导与交流,拒绝人参公鸡
~~

 

 

posted on 2010-09-24 14:53  路过秋天  阅读(12100)  评论(76编辑  收藏  举报
路过秋天