【转】在.Net中关于AOP的实现

原文地址:http://www.uml.org.cn/net/201004213.asp

一、AOP实现初步

AOP将软件系统分为两个部分:核心关注点和横切关注点。核心关注点更多的是Domain Logic,关注的是系统核心的业务;而横切关注点虽与核心的业务实现无关,但它却是一种更Common的业务,各个关注点离散地分布于核心业务的多处。这意味着,如果不应用AOP,那么这些横切关注点所代表的业务代码,就会分散在系统各处,导致系统中的每个模块都与这些业务具有很强的依赖性。在这里,所谓横切关注点所代表的业务,即为“方面(Aspect)”,常见的包括权限控制、日志管理、事务处理等等。

                         

以权限控制为例,假设一个电子商务系统,需要对订单管理用户进行权限判定,只有系统用户才能添加、修改和删除订单,那么传统的设计方法是:

public class OrderManager

{

private ArrayList m_Orders;

public OrderManager()

{

       m_Orders = new ArrayList();

}

public void AddOrder(Order order)

{

    if (permissions.Verify(Permission.ADMIN))

    {

              m_Orders.Add(order);

    }

}

public void RemoveOrder(Order order)

{

    if (permissions.Verify(Permission.ADMIN))

    {

              m_Orders.Remove(order);

    }

}

}

这样的设计其缺陷是将订单管理业务与权限管理完全结合在一起,耦合度高。而在一个系统中,类似的权限控制会很多,这些代码就好像一颗颗毒瘤一般蔓延于系统中的各处,一旦需要扩展,则给程序员们带来的困难是不可估量的。

                         

让我们来观察一下订单管理业务中的权限管理。不管是添加订单,还是删除订单,有关权限管理的内容是完全相同的。那么,为什么我们不能将这些相同的业务,抽象为一个对象,并将其从订单管理业务中完全剥离出来呢?在传统的OO设计思想,这种设想是不能实现的。因为订单管理业务作为一个类对象,它封装了诸如添加、删除订单等行为。这种封装性,就决定了我们不可能切入到对象内部,通过获取方法消息的形式,对对象行为进行监控与操作。

                         

AOP的思想解决了这个问题,之所以称为“方面(Aspect)”,就是把这些对象剖开,仅获取其内部相一致的逻辑,并剥离出来,以“方面”的形式存在。要让这些方面能够对核心业务进行控制,就需要有一套获取方法消息的机制。在.Net中,其中一种技术称为动态代理。

                         

在.Net中,要实现动态代理,需要用到.Net Remoting中的消息机制,以及.Net Framework内部提供的ContextAttribute类来自定义自己的Attribute。另外,.Net还要求调用“Aspect”的核心业务类,必须继承ContextBoundObject类。只有这样,我们才能截取其内部传递的方法消息。以下,是相关接口和类的说明。

ContextAttribute类                                                   

该类继承了Attribute类,它是一个特殊的Attribute,通过它,可以获得对象需要的合适的执行环境,即Context(上下文)。它还实现了IContextAttribute和IContextProperty接口。我们自定义的Attribute将从ContextAttribute类派生。

构造函数:                                                   

ContextAttribute类的构造函数带有一个参数,用来设置ContextAttribute的名称。                                                   

公共属性:                                                   

Name:只读属性。返回ContextAttribute的名称                                                   

公共方法:                                                   

GetPropertiesForNewContext:虚拟方法。向新的Context添加属性集合。                                                   

IsContextOK虚拟方法。查询客户Context中是否存在指定的属性。                                                   

IsNewContextOK虚拟方法。默认返回true。一个对象可能存在多个Context,使用这个方法来检查新的Context中属性是否存在冲突。                                                   

Freeze:虚拟方法。该方法用来定位被创建的Context的最后位置。                                                   

                         

ContextBoundObject                                                   

这个类的对象通过Attribute来指定它所在的Context,凡是进入该Context的调用都可以被拦截。该类从MarshalByRefObject派生。                                                   

                         

IMessage:定义了被传送的消息的实现。一个消息必须实现这个接口。                                                   

                         

IMessageSink:定义了消息接收器的接口,一个消息接收器必须实现这个接口。

该接口主要提供了两个方法,分别进行同步和异步操作:

SyncProcessMessage(IMessage msg):接口方法,当消息传递的时候,该方法被调用;

AsyncProcessMessage(IMessage msg, IMessageSink replySink):该方法用于异步处理;

                         

下面是实现权限控制AOP的简单实现,首先我们自定义一个Attribute,它继承了ContextAttribute:

[AttributeUsage(AttributeTargets.Class)]                                                       

    public class AOPAttribute:ContextAttribute                                                         

    {                                                       

        public AOPAttribute()                                                       

            : base("AOP")                                                       

        {                                                       

        }                                                       

                         

        public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)                                                       

        {                                                       

            ctorMsg.ContextProperties.Add(new AOPProperty());                                                       

        }                                                       

    }

在GetPropertiesForNewContext()方法中,添加了AOPProperty对象,它是一个上下文环境属性:

    public class AOPProperty : IContextProperty, IContributeObjectSink                                                         

    {                                                       

        public AOPProperty()                                                       

        {                                                       

        }                                                       

                         

        #region IContributeObjectSink Members                                                       

                         

        public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)                                                       

        {                                                       

            return new AOPSink(nextSink);                                                       

        }                                                       

                         

        #endregion                                                       

                         

        #region IContextProperty Members                                                       

                         

        public void Freeze(Context newContext)                                                       

        {                                                                   

        }                                                       

                         

        public bool IsNewContextOK(Context newCtx)                                                       

        {                                                       

            return true;                                                       

        }                                                       

                         

        public string Name                                                       

        {                                                       

            get { return "AOP"; }                                                       

        }                                                       

                         

        #endregion

AOPProperty属性实现了接口IContextProperty,IContributeObjectSink。GetObjectSink()方法为IContributeObjectSink接口的方法,在其实现中,创建了一个IMessageSink对象AOPSink,该对象实现了IMessageSink接口:

    public class AOPSink : IMessageSink                                                         

    {                                                               

        private IMessageSink m_NextSink;                                                       

                         

        public AOPSink(IMessageSink nextSink)                                                       

        {                                                       

            m_NextSink = nextSink;                                                                  

        }                                                       

                         

        public IMessageSink NextSink                                                       

        {                                                       

            get { return m_NextSink; }                                                       

        }                                                       

                         

        public IMessage SyncProcessMessage(IMessage msg)                                                       

        {                                                       

            IMethodCallMessage call = msg as IMethodCallMessage;                                                       

            if (call == null)                                                       

{                                                       

    return null;                                                       

}                                                       

IMessage retMsg = null;                                                       

if (call.MethodName == "AddOrder" || call.MethodName == "DeleteOrder")                                                       

{                                                       

    if (permissions.Verify(Permission.ADMIN))                                                       

    {                                                       

          retMsg = m_NextSink.SyncProcessMessage(msg);                                                       

    }                                                       

}                                                                  

            return retMsg;                                                       

        }                                                       

                         

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)                                                       

        {                                                       

            return null;                                                       

        }                                                       

    }

在AOPSink中,最重要的是SyncProcessMessage()方法,在这个方法中,实现了权限控制,并通过IMessage,截取了需要权限控制的方法。在检验了权限之后,然后再执行OrderManager的AddOrder和DeleteOrder方法。

                         

通过AOP的实现,原来的OrderManager,就可以修改为:

[AOP]                                                       

public class OrderManager: ContextBoundObject

{

private ArrayList m_Orders;

public OrderManager()

{

       m_Orders = new ArrayList();

}

public void AddOrder(Order order)

{

    m_Orders.Add(order);

}

public void RemoveOrder(Order order)

{

    m_Orders.Remove(order);

}

}

在上述的OderManager类中,完全消除了permissions.Verify()等有关权限的代码,解除了订单管理与权限管理之间的耦合。

                         

二、与AspectJ比较                                                   

上述的方案虽然解除了订单管理与权限管理的耦合,但从SyncProcessMessage()方法可以看出,它的实现具有很大的局限性。试想一下这样的应用场景,在订单管理系统中,用户要求对修改订单的方法增加权限验证,同时要求在验证权限时,允许业务经理(Permission.Manager)也具备管理订单的权限,应该怎样做?仔细思考,我们会发觉以上的实现未免太过死板了。

                         

让我们来参考一下AspectJ在java中的实现。AspectJ提供了自己的一套语法,其中包括aspect、pointcut、before、after等。我们可以通过aspect定义一个“方面”,如上的权限管理:

private static aspect AuthorizationAspect{……}

                         

pointcut为切入点,在其中定义了需要截取上下文消息的方法,例如:

private pointcut authorizationExecution():

execution(public void OrderManager.AddOrder(Order)) ||

execution(public void OrderManager.DeleteOrder(Order)) ||

execution(public void OrderManager.UpdateOrder(Order));

                         

由于权限验证是在订单管理方法执行之前完成,因此在before中,定义权限检查:

before(): authorizationExecution()

{

if !(permissions.Verify(Permission.ADMIN))

{

    throw new UnauthorizedException();

}

}

从上述AspectJ的实现中,我们可以看到,要定义自己的aspect是非常容易的,而通过pointcut的方式,可以将需要截取消息的方法,集中在一起。before和after则是具体的方面执行的逻辑,它们就好像Decorator模式那样,对原有方法进行了一层装饰,从而达到将aspect代码植入的目的。

                         

另外,AspectJ还提供了更简单的语法,可以简化前面pointcut中一系列方法的列举:

private pointcut authorizationExecution():

execution (public * OrderManager.*(.))

                         

AspectJ在应用AOP领域,已经非常成熟。它提供了自成一体的特有AspectJ语法,并需要专门的java编译器,使用起来较为复杂。那么,在.Net下,可否实现类似AspectJ的功能呢?我想,由于.Net与java在很多技术的相似性,它们彼此之间在很多领域是相通的,因此要达到这一目标应该是可行的。事实上,开源项目中的Aspect#,就与AspectJ相似。

                         

事实上,如果我们利用前面描述的动态代理机制,辅以设计模式的OO设计方法,直接在代码中也可以实现AspectJ中的部分AOP特性。

                         

三、.Net中AOP的深入实现                                                   

我们先分析AspectJ中的pointcut和.Net中的SyncProcessMessage()方法。Pointcut可以添加一系列需要截取上下文的方法,那么在.Net中,我们也可以利用集合,动态地添加方法,并创建这些方法与“方面”的映射。同样的,AspectJ中的before和after,是“方面”的核心实现,那么在.Net中,我们也可以利用委托,使其对应相关的方法,来实现其核心逻辑。

                         

结合动态代理的知识,我们先定义两个委托,分别代表before和after操作:

public delegate void BeforeAOPHandle(IMethodCallMessage callMsg);                                                       

public delegate void AfterAOPHandle(IMethodReturnMessage replyMsg);

                         

BeforeAOPHandle中的参数callMsg,其值为要截取上下文的方法的消息;AfterAOPHandle中的参数replyMsg,则是该方法执行后返回的消息。

                         

接下来,定义一个抽象基类AOPSink,它实现了IMessageSink接口:

public abstract class AOPSink : IMessageSink                                                         

    {                                                       

        private SortedList m_BeforeHandles;                                                       

        private SortedList m_AfterHandles;                                                       

        private IMessageSink m_NextSink;                                                       

}                                                       

在类AOPSink中,定义了两个SortedList类型的字段:m_BeforeHandles和m_AfterHandles。它们负责存放方法名与BeforeAOPHandle和AfterAOPHandle对象之间的映射。添加这些映射的职责由如下两个方法完成:                                                   

protected virtual void AddBeforeAOPHandle(string methodName, BeforeAOPHandle beforeHandle)                                                       

{                                                       

     lock (this.m_BeforeHandles)                                                       

     {                                                       

         if (!m_BeforeHandles.Contains(methodName))                                                       

         {                                                       

             m_BeforeHandles.Add(methodName, beforeHandle);                                                       

         }                                                       

     }                                                       

}                                                       

protected virtual void AddAfterAOPHandle(string methodName, AfterAOPHandle afterHandle)                                                       

{                                                       

      lock (this.m_AfterHandles)                                                       

      {                                                       

          if (!m_AfterHandles.Contains(methodName))                                                       

          {                                                       

              m_AfterHandles.Add(methodName, afterHandle);                                                       

          }                                                       

      }                                                   

}                                                       

考虑到我们要截取的方法可能会有多个,因此在类AOPSink中,又定义了两个抽象方法,负责添加所有的映射关系:                                                   

protected abstract void AddAllBeforeAOPHandles();                                                       

protected abstract void AddAllAfterAOPHandles();                                                   

                         

然后在构造函数中,我们初始化两个SortedList对象,并调用上述的两个抽象方法:                                                   

        public AOPSink(IMessageSink nextSink)                                                       

        {                                                       

            m_NextSink = nextSink;                         

            m_BeforeHandles = new SortedList();                                                       

            m_AfterHandles = new SortedList();                                                       

            AddAllBeforeAOPHandles();                                                       

            AddAllAfterAOPHandles();                                                       

        }                                                       

                         

为了能够根据方法名获得相对应的委托对象,我们又定义了两个Find方法。考虑到可能会有多个用户同时调用,在这两个方法中,我利用lock避免了对象的争用:                                                   

        protected BeforeAOPHandle FindBeforeAOPHandle(string methodName)                                                       

        {                                                       

            BeforeAOPHandle beforeHandle;                                                       

            lock (this.m_BeforeHandles)                                                       

            {                                                       

                beforeHandle = (BeforeAOPHandle)m_BeforeHandles[methodName];                                                       

            }                                                       

            return beforeHandle;                                                       

        }                                                       

        protected AfterAOPHandle FindAfterAOPHandle(string methodName)                                                       

        {                                                       

            AfterAOPHandle afterHandle;                                                       

            lock (this.m_AfterHandles)                                                       

            {                                                       

                afterHandle = (AfterAOPHandle)m_AfterHandles[methodName];                                                       

            }                                                       

            return afterHandle;                                                       

        }                                                       

接下来是IMessageSink接口要求实现的方法和属性:                                                   

        public IMessageSink NextSink                                                       

        {                                                       

            get { return m_NextSink; }                                                       

        }                                                       

                         

        public IMessage SyncProcessMessage(IMessage msg)                                                       

        {                                                       

            IMethodCallMessage call = msg as IMethodCallMessage;                                                       

            string methodName = call.MethodName.ToUpper();                                                       

            BeforeAOPHandle beforeHandle = FindBeforeAOPHandle(methodName);                                                       

            if (beforeHandle != null)                                                       

            {                                                       

                beforeHandle(call);                                                       

            }                                                       

            IMessage retMsg = m_NextSink.SyncProcessMessage(msg);                                                       

            IMethodReturnMessage replyMsg = retMsg as IMethodReturnMessage;                                                       

            AfterAOPHandle afterHandle = FindAfterAOPHandle(methodName);                                                       

            if (afterHandle != null)                                                       

            {                                                       

                afterHandle(replyMsg);                                                       

            }                                                       

            return retMsg;                                                       

        }                                                       

                         

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)                                                       

        {                                                       

            return null;                                                       

        }                                                       

需要注意的是SyncProcessMessage()方法。在该方法中,通过FindBeforeAOPHandle()和FindAfterAOPHandle()方法,找到BeforeAOPHandle和AfterAOPHandle委托对象,并执行它们。即执行这两个委托对象具体指向的方法,类似与AspectJ中的before和after的execution。                                                   

                         

现在,我们就可以象AspectJ那样定义自己的aspect了。如权限管理一例,我们定义一个类AuthorizationAOPSink,它继承了AOPSink:                                                   

public class AuthorizationAOPSink : AOPSink                                                         

{                                                       

    public AuthorizationAOPSink(IMessageSink nextSink)                                                       

            : base(nextSink)                                                       

   {                                                       

   }                                                       

}                                                       

然后在这个方法中,实现before和after的逻辑。注意before和after方法应与之前定义的委托BeforeAOPHandle和AfterAOPHandle一致。不过,以本例而言,并不需要实现after逻辑:                                                   

private void Before_Authorization(IMethodCallMessage callMsg)                                                       

{                                                              

        if (callMsg == null)                                                       

        {                                                       

              return;                                                       

        }                                                       

        if (!permissions.Verify(Permission.ADMIN))                                                   

        {                                                       

                 throw UnauthorizedException();                                                       

         }                                                       

}                                                   

然后我们override基类中的抽象方法AddAllBeforeAOPHandles()和AddAllAfterAOPHandles():                                                   

protected override void AddAllBeforeAOPHandles()                                                       

        {                                                       

            AddBeforeAOPHandle("ADDORDER", new BeforeAOPHandle(Before_Authorization));                                                       

            AddBeforeAOPHandle("DELETEORDER", new BeforeAOPHandle(Before_Authorization));                                                       

        }                                                       

                         

        protected override void AddAllAfterAOPHandles()                                                       

        {                                                                   

        }                                                       

因为after逻辑不需要实现,因此重写AddAllAfterAOPHandles()时,使其为空就可以了(必须重写,因为该方法为抽象方法)。在AOPProperty类中,需要返回IMessageSink对象,所以还应修改原来的AOPProperty类中的GetObjectSink方法:                                                   

public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)                                                       

        {                                                       

            return new AOPSink(nextSink);                                                         

return new AuthorizationAOPSink(nextSink);                                                               

        }                                                       

                         

比较一下上述的实现方案,自定义的继承AOPSink类的AuthorizationAOPSink就相当于AspectJ中的aspect。而与BeforeAOPHandle和AfterAOPHandle委托对应的方法,则相当于AspectJ的before和after语法。AddAllBeforeAOPHandles()和AddAllAfterAOPHandle()则相当于AspectJ的pointcut。通过引入委托的方法,使得我们的AOP实现,具有了AspectJ的一些特性,而这些实现是不需要专门的编译器的。

                         

很明显,如果我们要求OrderManager类中新增的UpdateOrder方法,也要加入权限控制,那么我们可以在AddAllBeforeAOPHandles()方法中,增加UpdaeOrder方法与before逻辑的映射:

AddBeforeAOPHandle("UPDATEORDER", Before_Authorization);                                                       

同样的,如果要对权限控制进行修改,开发业务经理对订单管理的权限,那么也只需要修改Before_Authorization()方法:

private void Before_Authorization(IMessage callMsg)                                                       

{                                                       

       IMethodCallMessage call = callMsg as IMethodCallMessage;                                                       

        if (call == null)                                                       

        {                                                       

              return;                                                       

        }                                                       

        if (!(permissions.Verify(Permission.ADMIN)|| permissions.Verify(Permission.MANAGER)))                                                   

        {                                                       

                 throw UnauthorizedException();                                                       

         }                                                       

}                                                       

                         

四、进一步完善                                                   

由于我们的委托列表m_BeforeHandles和m_AfterHandles为SortedList类型,因此作为key的methodName必须是唯一的。如果系统要求添加其他权限控制的逻辑,例如增加认证功能,就不能再在AuthorizationAOPSink类的AddAllBeforeAOPHandles()方法中增加方法名与认证功能的before逻辑之间的映射了。                                                   

private void Before_Authentication(IMessage callMsg){……}                                                       

protected override void AddAllBeforeAOPHandles()                                                       

{                                                       

       ……                                                       

       AddBeforeAOPHandle("ADDORDER", new BeforeAOPHandle(Before_ Authentication));                                                       

       AddBeforeAOPHandle("DELETEORDER", new BeforeAOPHandle(Before_ Authentication));                                                       

}                                                       

如果在AuthorizationAOPSink类中添加上面的代码,由于新增的“ADDORDER”key与前面重复,故执行程序时,是找不到相应的委托Before_Authentication的。                                                   

                         

解决的办法就是为认证功能新定义一个aspect。由于在本方案中,实现AOP功能的不仅仅是实现了IMessageSink接口的AOPSink类,同时该类还与Property、Attribute有关。也就是说,如果我们新定义一个AuthenticationAOPSink,那么还要定义与之对应的AuthenticationAOPProperty类。为便于扩展,我采用了Template Method模式,为所有的property定义了抽象类AOPProperty,其中的抽象方法或虚方法,则留待其子类来实现。                                                   

    public abstract class AOPProperty : IContextProperty, IContributeObjectSink                                                         

    {                                                       

        protected abstract IMessageSink CreateSink(IMessageSink nextSink);                                                       

        protected virtual string GetName()                                                       

        {                                                       

            return "AOP";                                                       

        }                                                       

                         

        protected virtual void FreezeImpl(Context newContext)                                                       

        {                                                       

            return;                                                       

        }                                                       

        protected virtual bool CheckNewContext(Context newCtx)                                                       

        {                                                       

            return true;                                                       

        }                                                       

                         

        #region IContributeObjectSink Members                                                       

        public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)                                                       

        {                                                       

            return CreateSink(nextSink);                                                       

        }                                                       

        #endregion                                                       

                         

        #region IContextProperty Members                                                       

        public void Freeze(Context newContext)                                                       

        {                                                       

            FreezeImpl(newContext);                                                       

        }                                                       

        public bool IsNewContextOK(Context newCtx)                                                       

        {                                                       

            return CheckNewContext(newCtx);                                                       

        }                                                       

        public string Name                                                       

        {                                                       

            get { return GetName(); }                                                       

        }                                                       

        #endregion                                                       

}                                                       

与原来的AOPProperty类相比,IContextProperty,IContributeObjectSink接口的方法与属性,都没有直接实现,而是在其内部调用了相关的抽象方法和虚方法。包括:抽象方法CreateSink(),虚方法FreezeImpl(),CheckNewContext()以及GetName()。对于其子类而言,需要override的,主要是抽象方法CreateSink()和GetName()(因为Property的Name必须是唯一的),至于其他虚方法,可以根据需要选择是否override。例如,自定义权限控制的属性类AuthorizationAOPProperty:                                                   

    public class AuthorizationAOPProperty :AOPProperty                                                         

    {                                                           

        protected override IMessageSink CreateSink(IMessageSink nextSink)                                                       

        {                                                       

            return new AuthorizationAOPSink(nextSink);                                                       

        }                                                       

                         

        protected override string GetName()                                                       

        {                                                       

            return "AuthorizationAOP";                                                       

        }                                                       

    }                                                   

在该类中,我们override了CreateSink()方法,创建了一个AuthorizationAOPSink对象。同时override了虚方法GetName,返回了自己的一个名字“AuthorizationAOP”。                                                   

                         

关于Attribute类,观察其方法GetPropertiesForNewContext(),其实现是在IConstructionCallMessage消息的上下文property中添加自定义property。这些property组成了一个链,它是可以静态添加的。鉴于此,我们可以采取两种策略:                                                   

1、 所有的aspect都使用同一个Attribute。其实现如下:                                                   

    [AttributeUsage(AttributeTargets.Class)]                                                       

    public class AOPAttribute:ContextAttribute                                                         

    {                                                       

        public AOPAttribute()                                                       

            : base("AOP")                                                       

        {                                                       

        }                                                       

                         

        public override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)                                                       

        {                                                       

            ctorMsg.ContextProperties.Add(new AuthorizationAOPProperty());                                                       

            ctorMsg.ContextProperties.Add(new AuthenticationAOPProperty());                                                       

        }                                                       

}                                                       

在方法GetPropertiesForNewContext()中,添加多个自定义Property。在添加Property时,需要注意添加Property的顺序。                                                   

2、 不同的aspect使用不同的Attribute。此时可以为这些Attribute定义一个共同的抽象基类AOPAttribute:                                                   

[AttributeUsage(AttributeTargets.Class)]                                                       

    public abstract class AOPAttribute:ContextAttribute                                                         

    {                                                       

         public AOPAttribute()                                                       

             : base("AOP")                                                       

         {                                                       

         }                                                       

                         

         public sealed override void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)                                                       

         {                                                       

             ctorMsg.ContextProperties.Add(GetAOPProperty());                                                       

         }                                                       

                         

         protected abstract AOPProperty GetAOPProperty();                                                                

    }                                                       

注:我将GetPropertiesForNewContext()方法sealed,目的是不需要其子类在重写该方法。                                                   

                         

继承AOPAttribute类的子类只需要重写GetAOPProperty()方法即可。但在为OrderManager类定义Attribute的时候,需注意其顺序。如以下的顺序:                                                   

[AuthorizationAOP]                                                       

[AuthenticationAOP]                                                       

public class OrderManager{}                                                       

此时,AuthorizationAOPAttribute在前,AuthenticationAOPAttribute在后。如果以Decorator的角度来看,对被装饰的方法,AuthorizationAOPAttribute在内,AuthenticationAOPAttribute在外。                                                   

考虑到aspect的应用,有的方法需要多个aspect,有的则只需要单个aspect,所以,第二个方案更佳。                                                   

                         

五、AOP实例                                                   

接下来,我通过一个实例,介绍AOP的具体实现。假定我们要设计一个计算器,它能提供加法和减法功能。我们希望,在计算过程中,能够通过日志记录整个计算过程及其结果,同时需要监测其运算性能。该例中,核心业务是加法和减法,而公共的业务则是日志与监测功能。根据前面对AOP的分析,这两个功能应为我们整个系统需要剥离出来的“方面”。                                                   

                         

我们已经拥有了一个AOP实现机制,以及核心的类库,包括AOPSink、AOPProperty、AOPAttribute三个抽象基类。现在,我们分别为日志aspect和监测aspect,定义相应的Sink、Property、Attribute。                                                   

                         

首先是日志aspect:                                                   

LogAOPSink.cs:                                                       

using System;                                                       

using System.Runtime.Remoting.Messaging;                                                       

using Wayfarer.AOP;                                                       

                         

namespace Wayfarer.AOPSample                                                       

{                                                       

    /// <summary>                                                         

    /// Summary description for LogAOPSink.                                                         

    /// </summary>                                                         

    public class LogAOPSink:AOPSink                                                         

    {                                                       

         public LogAOPSink(IMessageSink nextSink):base(nextSink)                                                       

         {                                                       

                                                                    

         }                                                       

                         

         protected override void AddAllBeforeAOPHandles()                                                       

         {                                                       

              AddBeforeAOPHandle("ADD",new BeforeAOPHandle(Before_Log));                                                       

             AddBeforeAOPHandle("SUBSTRACT",new BeforeAOPHandle(Before_Log));                                                       

         }                                                       

                         

         protected override void AddAllAfterAOPHandles()                                                       

         {                                                       

             AddAfterAOPHandle("ADD",new AfterAOPHandle(After_Log));                                                       

             AddAfterAOPHandle("SUBSTRACT",new AfterAOPHandle(After_Log));                                                       

         }                                                       

                         

         private void Before_Log(IMethodCallMessage callMsg)                                                       

         {                                                                   

             if (callMsg == null)                                                       

             {                                                       

                  return;                                                       

             }                                                                   

                       Console.WriteLine("{0}({1},{2})",callMsg.MethodName,callMsg.GetArg(0),callMsg.GetArg(1));                                                       

         }                                                       

                         

         private void After_Log(IMethodReturnMessage replyMsg)                                                       

         {                                                                   

             if (replyMsg == null)                                                       

             {                                                       

                  return;                                                       

             }                                                                   

             Console.WriteLine("Result is {0}",replyMsg.ReturnValue);                                                                 

         }                                                       

                         

    }                                                       

}                                                       

                         

LogAOPProperty.cs                                                       

using System;                                                       

using Wayfarer.AOP;                                                       

using System.Runtime.Remoting.Messaging;                                                       

                         

namespace Wayfarer.AOPSample                                                       

{                                                       

    /// <summary>                                                         

    /// Summary description for LogAOPProperty.                                                         

    /// </summary>                                                         

    public class LogAOPProperty:AOPProperty                                                         

    {                                                       

         protected override IMessageSink CreateSink(IMessageSink nextSink)                                                       

         {                                                       

             return new LogAOPSink(nextSink);                                                       

         }                                                       

                         

         protected override string GetName()                                                       

         {                                                       

             return "LogAOP";                                                       

         }                                                       

    }                                                       

}                                                       

LogAOPAttribute.cs:                                                       

using System;                                                       

using System.Runtime.Remoting.Activation;                                                       

using System.Runtime.Remoting.Contexts;                                                       

using Wayfarer.AOP;                                                       

                         

namespace Wayfarer.AOPSample                                                       

{                                                       

    /// <summary>                                                         

    /// Summary description for LogAOPAttribute.                                                         

    /// </summary>                                                         

    [AttributeUsage(AttributeTargets.Class)]                                                       

    public class LogAOPAttribute:AOPAttribute                                                         

    {                                                       

         protected override AOPProperty GetAOPProperty()                                                       

         {                                                       

             return new LogAOPProperty();                                                       

         }                                                       

                                                           

    }                                                       

}                                                       

                         

然后再定义监测aspect:                                                   

MonitorAOPSink.cs:                                                   

using System;                                                       

using System.Runtime.Remoting.Messaging;                                                       

using Wayfarer.AOP;                                                       

                         

namespace Wayfarer.AOPSample                                                       

{                                                       

    /// <summary>                                                         

    /// Summary description for MonitorAOPSink.                                                         

    /// </summary>                                                         

    public class MonitorAOPSink:AOPSink                                                         

    {                                                       

         public MonitorAOPSink(IMessageSink nextSink):base(nextSink)                                                       

         {                                                       

                                                                    

         }                                                       

                         

         protected override void AddAllBeforeAOPHandles()                                                       

         {                                                       

             AddBeforeAOPHandle("ADD",new BeforeAOPHandle(Before_Monitor));                                                       

             AddBeforeAOPHandle("SUBSTRACT",new BeforeAOPHandle(Before_Monitor));                                                       

         }                                                       

         protected override void AddAllAfterAOPHandles()                                                       

         {                                                       

             AddAfterAOPHandle("ADD",new AfterAOPHandle(After_Monitor));                                                       

             AddAfterAOPHandle("SUBSTRACT",new AfterAOPHandle(After_Monitor));                                                       

         }                                                       

                         

         private void Before_Monitor(IMethodCallMessage callMsg)                                                       

         {                                                                   

             if (callMsg == null)                                                       

             {                                                       

                  return;                                                       

             }                                                       

             Console.WriteLine("Before {0} at {1}",callMsg.MethodName,DateTime.Now);                                                       

         }                                                       

         private void After_Monitor(IMethodReturnMessage replyMsg)                                                       

         {                                                                   

             if (replyMsg == null)                                                       

             {                                                       

                  return;                                                       

             }                                                       

             Console.WriteLine("After {0} at {1}",replyMsg.MethodName,DateTime.Now);                                                       

         }                                                       

    }                                                       

}                                                       

MonitorAOPProperty.cs:                                                   

using System;                                                       

using Wayfarer.AOP;                                                       

using System.Runtime.Remoting.Messaging;                                                       

                         

namespace Wayfarer.AOPSample                                                       

{                                                       

    /// <summary>                                                         

    /// Summary description for MonitorAOPProperty.                                                         

    /// </summary>                                                         

    public class MonitorAOPProperty:AOPProperty                                                         

    {                                                       

         public MonitorAOPProperty()                                                       

         {                                                       

             //                                                         

             // TODO: Add constructor logic here                                                         

             //                                                         

         }                                                       

                         

         protected override IMessageSink CreateSink(IMessageSink nextSink)                                                       

         {                                                       

             return new MonitorAOPSink(nextSink);                                                       

         }                                                       

                         

         protected override string GetName()                                                       

         {                                                       

             return "MonitorAOP";                                                       

         }                                                       

    }                                                       

}                                                       

MonitorAOPAttribute.cs:                                                   

using System;                                                       

using System.Runtime.Remoting.Activation;                                                       

using System.Runtime.Remoting.Contexts;                                                       

using Wayfarer.AOP;                                                       

                         

namespace Wayfarer.AOPSample                                                       

{                                                       

    /// <summary>                                                         

    /// Summary description for MonitorAOPAttribute.                                                         

    /// </summary>                                                         

    [AttributeUsage(AttributeTargets.Class)]                                                       

    public class MonitorAOPAttribute:AOPAttribute                                                         

    {                                                       

         protected override AOPProperty GetAOPProperty()                                                       

         {                                                       

             return new MonitorAOPProperty();                                                       

         }                                                       

    }                                                       

}                                                       

注意在这两个方面中,各自的Property的Name必须是唯一的。                                                   

现在,可以定义计算器类。                                                   

Calculator.cs:                                                   

using System;

namespace Wayfarer.AOPSample                                                       

{                                                       

    /// <summary>                                                         

    /// Summary description for Calculator.                                                         

    /// </summary>                                                         

    [MonitorAOP]                                                       

    [LogAOP]                                                       

    public class Calculator:ContextBoundObject                                                         

    {                                                       

         public int Add(int x,int y)                                                       

         {                                                       

             return x + y;                                                       

         }                                                       

                         

         public int Substract(int x,int y)                                                       

         {                                                       

             return x - y;                                                       

         }                                                       

    }                                                       

}                                                       

需要注意的是Calculator类必须继承ContextBoundObject类。                                                   

最后,我们写一个控制台程序来执行Calculator:                                                   

Program.cs:                                                       

using System;                                                       

namespace Wayfarer.AOPSample                                                       

{                                                       

    /// <summary>                                                         

    /// Summary description for Class1.                                                         

    /// </summary>                                                         

    class Program                                                         

    {                                                       

         /// <summary>                                                         

         /// The main entry point for the application.                                                         

         /// </summary>                                                         

         [STAThread]                                                       

         static void                            Main                           (string[] args)                                                     

         {                                                       

             Calculator cal = new Calculator();                                                       

             cal.Add(3,5);                                                       

             cal.Substract(3,5);                                                       

             Console.ReadLine();                                                       

         }                                                       

    }                                                       

}                                                       

运行结果如下:                                                   

 

六、结论                                                   

在.Net平台下采用动态代理技术实现AOP,其原理并不复杂,而.Net Framework也提供了足够的技术来实现它。如果再结合好的设计模式,提供一个基本的AOP框架,将大大地简化开发人员处理“aspect”的工作。当然,本文虽然提供了实现AOP的实例,但其架构的设计还远远不能达到企业级的要求,如在稳定性、可扩展性上还需经过进一步的测试与改善。例如我们可以通过配置文件的形式,来配置方法与方面之间的映射。同时,由于采用了动态代理,在性能上还期待改进。                                                   

                         

使用动态代理技术实现AOP,对实现AOP的类有一个限制,就是必须派生于ContextBoundObject类,这对于单继承语言来说,确实是一个比较致命的缺陷。所谓“仁者见仁,智者见智”,这就需要根据项目的情况,做出正确的抉择了。                                                   

                         

参考:                                                   

1、 JGTM,《A Taste of AOP from Solving Problems with OOP and Design Patterns》                                                   

2、 NiWalker,《Attribute在.Net编程的应用》                                                   

3、板桥里人,《AOP与权限控制实现》                        

在《在.Net中关于AOP的实现》我通过动态代理的技术,基本上实现了AOP的几个技术要素,包括aspect,advice,pointcut。在文末我提到采用配置文件方式,来获取advice和pointcut之间的映射,从而使得构建aspect具有扩展性。

细细思考这个问题,我发现使用delegate来构建advice,似乎并非一个明智的选择。我在建立映射关系时,是将要拦截的方法名和拦截需要实现的aspect逻辑建立一个对应关系,而该aspect逻辑确实可以通过delegate,使其指向一族方法签名与该委托完全匹配的方法。这使得advice能够抽象化,以便于具体实现的扩展。然而,委托其实现毕竟是面向过程的范畴,虽然在.Net下,delegate本身仍是一个类对象,然而在创建具体的委托实例时,仍然很难通过配置文件和反射技术来获得。

                         

考虑到委托具有的接口抽象的本质,也许采用接口的方式来取代委托更为可行。在之前的实现方案中,我为advice定义了两个委托:

public delegate void BeforeAOPHandle(IMethodCallMessage callMsg);                                                       

public delegate void AfterAOPHandle(IMethodReturnMessage replyMsg);                                                       

                         

我可以定义两个接口IBeforeAction和IAfterAction,分别与这两个委托相对应:                                                         

    public interface IBeforeAdvice                                                         

    {                                                       

        void BeforeAdvice(IMethodCallMessage callMsg);                                                       

}                                                       

    public interface IAfterAdvice                                                         

    {                                                       

        void AfterAdvice(IMethodReturnMessage returnMsg);                                                       

}                                                       

通过定义的接口,可以将Advice与Aspect分离开来,这也完全符合OO思想中的“责任分离”原则。                                                   

(注:为什么要为Advice定义两个接口?这是考虑到有些Aspect只需要提供Before或After两个逻辑之一,如权限控制,就只需要before Action。)                                                   

                         

那么当类库使用者,要定义自己的Aspect时,就可以定义具体的Advice类,来实现这两个接口,以及具体的Advice逻辑了。例如,之前提到的日志Aspect:                                                   

    public class LogAdvice:IAfterAdvice,IBeforeAdvice                                                         

    {                                                       

        #region IBeforeAdvice Members                                                       

                         

        public void BeforeAdvice(IMethodCallMessage callMsg)                                                       

        {                                                       

            if (callMsg == null)                                                       

            {                                                       

                return;                                                       

            }                                                       

            Console.WriteLine("{0}({1},{2})", callMsg.MethodName, callMsg.GetArg(0), callMsg.GetArg(1));                                                       

        }                                                       

                         

        #endregion                                                       

                         

        #region IAfterAdvice Members                                                       

                         

        public void AfterAdvice(IMethodReturnMessage returnMsg)                                                       

        {                                                       

            if (returnMsg == null)                                                       

            {                                                       

                return;                                                       

            }                                                       

            Console.WriteLine("Result is {0}", returnMsg.ReturnValue);                                                       

        }                                                       

                         

        #endregion                                                       

}                                                       

                         

而在AOPSink类的派生类中,添加方法名与Advice映射关系(此映射关系,我们即可理解为AOP的pointcut)时,就可以添加实现了Advice接口的类对象,如:                                                   

         public override void AddAllBeforeAdvices()                                                       

         {                                                       

            AddBeforeAdvice("ADD",new LogAdvice());                                                       

            AddBeforeAdvice("SUBSTRACT", new LogAdvice());                                                       

         }                                                       

         public override void AddAllAfterAdvices()                                                       

         {                                                       

             AddAfterAdvice("ADD",new LogAdvice());                                                       

            AddAfterAdvice("SUBSTRACT", new LogAdvice());                                                       

         }                                                       

由于LogAdvice类实现了接口IBeforeAdvice和IAfterAdvice,因此诸如new LogAdvice的操作均可以通过反射来创建该实例,如:                                                   

IBeforeAdvice beforeAdvice =                                                   

(IBeforeAdvice)Activator.CreateInstance("Wayfarer.AOPSample","Wayfarer.AOPSample.LogAdvice").Unwrap();                                                       

而CreateInstance()方法的参数值,是完全可以通过配置文件来配置的:                                                   

<aop>                                                      

    <aspect value ="LOG">                                                        

         <advice type="before" assembly="Wayfarer.AOPSample" class="Wayfarer.AOPSample.LogAdvice">                                                        

             <pointcut>ADD</pointcut>                                                        

             <pointcut>SUBSTRACT</pointcut>                                                        

         </advice>                                                      

         <advice type="after" assembly="Wayfarer.AOPSample" class="Wayfarer.AOPSample.LogAdvice">                                                        

             <pointcut>ADD</pointcut>                                                        

             <pointcut>SUBSTRACT</pointcut>                                                        

         </advice>                                                      

    </aspect>                                                           

</aop>                                                   

这无疑改善了AOP实现的扩展性。                                                   

                         

《在.Net中关于AOP的实现》实现AOP的方案,要求包含被拦截方法的类必须继承ContextBoundObject。这是一个比较大的限制。不仅如此,ContextBoundObject对程序的性能也有极大的影响。我们可以做一个小测试。定义两个类,其中一个类继承ContextBoundObject。它们都实现了一个累加的操作:

class NormalObject                                                         

    {                                                       

        public void Sum(int n)                                                       

        {                                                       

            int sum = 0;                                                       

            for (int i = 1; i <= n; i++)                                                       

            {                                                       

                sum += i;                                                       

            }                                                       

            Console.WriteLine("The result is {0}",sum);                                                       

            Thread.Sleep(10);                                                       

        }                                                       

    }                                                       

                         

    class MarshalObject:ContextBoundObject                                                         

    {                                                       

        public void Sum(int n)                                                       

        {                                                       

            int sum = 0;                                                       

            for (int i = 1; i <= n; i++)                                                       

            {                                                       

                sum += i;                                                       

            }                                                       

            Console.WriteLine("The result is {0}", sum);                                                       

            Thread.Sleep(10);                                                       

        }                                                       

    }                                                       

然后执行这两个类的Sum()方法,测试其性能:

    class Program                                                         

    {                                                       

        static void                            Main                           (string[] args)                                                     

        {                                                       

            long normalObjMs, marshalObjMs;                                                       

            Stopwatch watch = new Stopwatch();                                                       

            NormalObject no = new NormalObject();                                                       

            MarshalObject mo = new MarshalObject();                                                       

                         

            watch.Start();                                                       

            no.Sum(1000000);                                                       

            watch.Stop();                                                       

            normalObjMs = watch.ElapsedMilliseconds;                                                       

            watch.Reset();                                                       

                         

            watch.Start();                                                       

            mo.Sum(1000000);                                                       

            watch.Stop();                                                       

            marshalObjMs = watch.ElapsedMilliseconds;                                                       

            watch.Reset();                                                       

                         

            Console.WriteLine("The normal object consume {0} milliseconds.",normalObjMs);                                                       

            Console.WriteLine("The contextbound object consume {0} milliseconds.",marshalObjMs);                                                                   

            Console.ReadLine();                                                       

        }                                                       

    }

得到的结果如下:

 

从性能的差异看,两者之间的差距是比较大的。如果将其应用在企业级的复杂逻辑上,这种区别就非常明显了,对系统带来的影响也是非常巨大的。

                         

另外,在《在.Net中关于AOP的实现》文章后,有朋友发表了很多中肯的意见。其中有人提到了AOPAttribute继承ContextAttribute的问题。评论中提及微软在以后的版本中,不再提供ContextAttribute。如果真是如此,确有必要放弃继承ContextAttribute的形式。不过,在.Net中,除了ContextAttribute之外,还提供有一个接口IContextAttribute,该接口的定义为:

public interface IContextAttribute                                                       

{                                                       

        void GetPropertiesForNewContext(IConstructionCallMessage msg);                                                       

        bool IsContextOK(Context ctx, IConstructionCallMessage msg);       

}                                                       

此时只需要将原来的AOPAttribute实现该接口即可:                                                         

    public abstract class AOPAttribute:Attribute,IContextAttribute//ContextAttribute                                                         

    {                                                       

        #region IContextAttribute Members                                                       

        public void GetPropertiesForNewContext(IConstructionCallMessage ctorMsg)                                                       

        {                                                       

            AOPProperty property = GetAOPProperty();                                                       

            property.AspectXml = m_AspectXml;                                                       

            property.AspectXmlFlag = m_AspectXmlFlag;                                                       

            ctorMsg.ContextProperties.Add(property);                                                       

        }                                                       

                         

        public bool IsContextOK(Context ctx, IConstructionCallMessage ctorMsg)                                                       

        {                                                       

            return false;                                                       

        }                                                       

        #endregion                                                       

}                                                       

不知道,IContextAttribute似乎也会在未来的版本中被取消呢?                                                   

                         

然而,从总体来看,这种使用ContextBoundObject的方式是不太理想的,也许它只能停留在实验室阶段,或许期待微软在未来的版本中得到更好的解决!?                                                   

                         

当然,如果采用Castle的DynamicProxy技术,可以突破必须继承CotextBoundObject的局限,但随着而来的局限却是AOP拦截的方法,要求必须是virtual的。坦白说,这样的限制,不过与前者乃“五十步笑百步”的区别而已。我还是期待有更好的解决方案。                                                   

                         

说到AOP的几大要素,在这里可以补充说说,它主要包括:                                                   

1、Cross-cutting concern

                         

  在OO模型中,虽然大部份的类只有单一的、特定的功能,但它们通常会与其他类有着共同的第二需求。例如,当线程进入或离开某个方法时,我们可能既要在数据访问层的类中记录日志,又要在UI层的类中记录日志。虽然每个类的基本功能极然不同,但用来满足第二需求的代码却基本相同。

                         

2、Advice

                         

  它是指想要应用到现有模型的附加代码。例如在《在.Net中关于AOP的实现》的例子中,是指关于打印日志的逻辑代码。

                         

3、Point-cut

                         

  这个术语是指应用程序中的一个执行点,在这个执行点上需要采用前面的cross-cutting concern。如例子中,执行Add()方法时出现一个Point-cut,当方法执行完毕,离开方法时又出现另一个Point-cut。

                         

4、Aspect

                         

Point-cut和advice结合在一起就叫做aspect。如例子中的Log和Monitor。在对本例的重构中,我已经AOPSink更名为Aspect,相应的LogAOPSink、MonitorAOPSink也更名为LogAspect,MonitorAspect。

                         

以上提到的PointCut和Advice在AOP技术中,通常称为动态横切技术。与之相对应的,是较少被提及的静态横切。它与动态横切的区别在于它并不修改一个给定对象的执行行为,相反,它允许通过引入附加的方法属性和字段来修改对象固有的结构。在很多AOP实现中,将静态横切称为introduce或者mixin。                                                   

                         

在开发应用系统时,如果需要在不修改原有代码的前提下,引入第三方产品和API库,静态横切技术是有很大的用武之地的。从这一点来看,它有点类似于设计模式中提到的Adapter模式需要达到的目标。不过,看起来静态横切技术应比Adapter模式更加灵活和功能强大。                                                   

                         

例如,一个已经实现了收发邮件的类Mail。然而它并没有实现地址验证的功能。现在第三方提供了验证功能的接口IValidatable:                                                   

public interface IValidatable                                                       

{                                                       

    bool ValidateAddress();                                                       

}                                                       

如果没有AOP,采用设计模式的方式,在不改变Mail类的前提下,可以通过Adapter模式,引入MailAdater,继承Mail类,同时实现IValidatable接口。采用introduce技术,却更容易实现该功能的扩展,我们只需要定义aspect:(注:java代码,使用了AspectJ)                                                   

import com.acme.validate.Validatable;                                                       

                         

public aspect EmailValidateAspect                                                   

{                                                       

   declare parents: Email implements IValidatable;                                                       

                         

   public boolean Email.validateAddress(){                                                       

     if(this.getToAddress() != null){                                                       

          return true;                                                       

     }else{                                                       

          return false;                                                       

     }                                                       

   }                                                       

}                                                       

                         

从上可以看到,通过EmailValidateAspect方面,为Email类introduce了新的方法ValidateAddress()。非常容易的就完成了Email的扩展。                                                   

                         

我们可以比较一下,如果采用Adapter模式,原有的Email类是不能被显示转换为IValidatable接口的,也即是说如下的代码是不可行的:                                                   

Email mail = new Email();                                                       

IValidatable validate = ((IValidatable)mail).ValidateAddress();                                                       

要调用ValidateAddress()方法,必须通过EmailAdapter类。然而通过静态横切技术,上面的代码就完全可行了。                                                   

                         

静态横切的技术在企业应用上还需要进一步验证和测试,不过遗憾的是,《在.Net中关于AOP的实现》一文采用的动态代理技术,是无法完成实现静态横切的目标的。

转载于:https://www.cnblogs.com/wangwangfei/p/4387643.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/359019.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

宝塔访问域名访问不到

首先说明一点&#xff0c;我这个问题不是宝塔面板绑定域名的那种情况。 浏览器输入域名会报上面的错误&#xff0c;经过各种尝试&#xff0c;发现是安全组没有放开&#xff0c;在阿里云控制台添加一个80/80的通用安全组规则即可&#xff0c;知道上行和下行的区别&#xff0c;打…

spring.jpa配置_使用JPA和Spring 3.1进行事务配置

spring.jpa配置1.概述 本教程将讨论配置Spring Transactions &#xff0c;使用Transactional批注和常见陷阱的正确方法 。 有关核心持久性配置的更深入讨论&#xff0c;请查看Spring with JPA教程 。 有两种不同的配置事务的方法– 批注和AOP –每种都有自己的优势–我们将在…

双千兆和双频千兆哪个好_关于千兆路由器的那些事儿!赶紧收藏

无线Wi-Fi已成为现代人生活、工作的标配&#xff0c;随着百兆乃至千兆的光纤网络普及&#xff0c;传统路由器已跟不上时代的速度了。不少用户的眼光已投向速度更快的千兆路由器&#xff0c;那么大家真的了解什么是千兆路由器吗&#xff1f;千兆路由器有哪几种&#xff1f;近期火…

宝塔常用的命令大全(直接上官网)

安装宝塔 Centos安装脚本 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install.sh && sh install.sh Ubuntu/Deepin安装脚本 wget -O install.sh http://download.bt.cn/install/install-ubuntu.sh && sudo bash ins…

华三ospf联动bfd_HCIE2020__路由交换专家__BFD综合实验

1.1.1 关于本实验本实验通过配置BFD与BGP协议联动及与VRRP协议联动&#xff0c;掌握BFD的功能和配置方法。1.1.2 实验目的理解BFD的工作原理。 掌握BFD与BGP联动的应用场景及配置方法。 掌握BFD与VRRP联动的应用场景及配置方法。1.1.3 实验组网介绍图1-1 BFD原理配置实验拓扑图…

struts2,实现Ajax异步通信

用例需要依赖的jar&#xff1a; struts2-core.jarstruts2-convention-plugin.jar&#xff0c;非必须org.codehaus.jackson.jar&#xff0c;提供json支持用例代码如下&#xff1a; 数据库DDL语句无 struts.xml1 <?xml version"1.0" encoding"UTF-8"?&g…

快速提示:消息驱动Bean中的异常处理

让我们快速回顾一下有关消息驱动Bean的特殊处理。 MDB的入口点是重写的onMessage方法。 它不提供引发检查异常的任何作用域&#xff0c;因此&#xff0c;如果要处理错误情形&#xff0c;则需要从代码中传播未检查异常&#xff08;java.lang.RuntimeException的子类&#xff09…

大学生助学贷款如何还利息(本金+利息都可以)

生源地助学贷款还利息的步骤: 1、首先需要将钱转入支付宝,金额稍微高于应还款项即可。然后登陆电脑版支付宝,一定是要电脑登陆(手机上无法操作),如图所示 2、然后在支付宝主页中,点击右上角的“应用中心”,然后点击“生活服务”如图所示。

(实用)将wordpad添加到Windows PowerShell中

PowerShell能够直接打开notepad&#xff0c;但是无法调用wordpad&#xff0c;因为后者的可执行文件并不在系统默认的环境变量$env:Path中&#xff0c;只要将wordpad所在的路径添加到$env:Path&#xff0c;就可以从CMD或PowerShell中直接启动wordpad。 在PowerSehll下输入&#…

mysql分组区某列最后一条_[MySQL]MySQL数据库如何按某列分组排序后查询每个分组的最后一条数据?...

比如当前有如下的消息表(messages)及示例数据&#xff1a;Id Name Other_Columns-------------------------1 A A_data_12 A A_data_23 A A_data_34 B B_data_15 B B_data_26 C C_data_1按照以下SQL语句查询&#xff1a;select * from messages group by name得到的查询结果为&…

谷歌浏览器下载的内容老是自动打开

原因是每次下载内容在浏览器左下角都有提示&#xff0c;一般都是选择打开所在文件夹&#xff0c;一次手残&#xff0c;点了总是打开此文件导致一下载文件就自动打开。 解决方法。 1.点击右上角的... 2.找到设置-高级设置-下载 3.关掉我标红的位置信息即可。现在看不到啦&…

JBoss Fuse 6.2发布–指导如何快速尝试

在上周的红帽峰会上&#xff0c;宣布发布了JBoss Fuse 6.2 。 我要祝贺保险丝团队发布此版本。 我知道他们今年以来一直非常努力地进行质量检查&#xff0c;并确保质量检查通过了企业产品所期望的高品质壁垒。 因此&#xff0c;带着我的Camel帽子&#xff0c;很高兴看到包含最…

插入文件找不到桌面了?

解决方法&#xff0c;在那个界面空白处&#xff0c;右键显示所有文件即可。

mysql自增长主键_MySQL数据库8(九)列属性之主键、自增长

主键顾名思义&#xff0c;主要的键&#xff0c;primary key&#xff0c;在一张表中&#xff0c;有且只有一个字段&#xff0c;里面的值具有唯一性创建主键随表创建系统提供了两种增加主键的方式&#xff1a;1、直接在需要当做主键的字段之后&#xff0c;增加primary key属性来确…

微信小程序速成页面

你是否还在辛辛苦苦地码代码&#xff0c;特别是前端页面的编写&#xff0c;很多都是非常简单的&#xff0c;但界面多了还是要花费大量的时间&#xff0c;缩短了你陪男/女朋友的时间&#xff0c;家人的时间&#xff0c;休闲的时光。 微信小程序前端开发者工具将大大地为您节省开…

Ajax 生成流文件下载 以及复选框的实现

JQuery的ajax函数的返回类型只有xml、text、json、html等类型&#xff0c;没有“流”类型&#xff0c;所以我们要实现ajax下载&#xff0c;不能够使用相应的ajax函数进行文件下载。但可以用js生成一个form&#xff0c;用这个form提交参数&#xff0c;并返回“流”类型的数据。在…

mysql1130_解决远程连接mysql错误1130的方法

解决远程连接mysql错误1130代码的方法今天在用远程连接Mysql服务器的数据库&#xff0c;不管怎么弄都是连接不到&#xff0c;错误代码是1130&#xff0c;ERROR 1130: Host 192.168.2.159 is not allowed to connect to this MySQL server猜想是无法给远程连接的用户权限问题。结…

使用Classycle验证类/包依赖关系

Classycle是一个非常好的类和包依赖项的分析器和依赖项检查器。 它使您可以定义程序包组&#xff08;组件&#xff0c;层&#xff09;&#xff0c;并表达不需要的依赖性&#xff0c;例如周期或特定程序包之间的依赖性。 例如&#xff0c;您可以指定不希望包周期&#xff0c;也…

初学者如何学编程呢

先简单做个测试,这样就能找出你比较适合哪种语言。 下面简单介绍一下不同的语言吧

CellSet 遍历

CellSet 结构&#xff1a; 查询MDX&#xff1a; SELECT NON EMPTY {{ {{ {{ {{ {{ AddCalculatedMembers([店铺.店铺ID].[店铺ID].Members)}} }} }} }} }} DIMENSION PROPERTIES MEMBER_TYPE , [店铺.店铺ID].[店铺ID].[国家], [店铺.店铺ID].[店铺ID].[区域], [店铺.…