Puzzle 面向切面AOP开发框架 For .Net

AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

日常的产品开发中最常见的就是数据保存的功能。举例来说,现在有个用户信息数据保存的功能,我们希望在数据保存前对数据进行校验,其中现在能想到的校验就包含数据完整性校验和相同数据是否存在校验。按照传统的OOP(面向对象程序设计),我们需要定义一个IUserDataSaveService(用户数据保存服务接口)以及对应的UserDataSaveServiceImpl(用户数据保存服务实现)。同时为了实现保存前数据校验的功能,需要在服务实现(UserDataSaveServiceImpl)中增加数据校验的代码。虽然我们可以通过增加数据校验接口的方式来降低耦合度,但是造成了代码的维护性不佳(假如以后需要增加其他类型的校验就需要修订现有的校验服务,造成程序的可维护性不佳)。同时在整个系统中也不可能仅有用户数据保存这一种保存服务,一定存在了各种各样不同的实现了同一保存接口(IDataSaveService)的保存服务实现,那么如何让这些保存服务实现能够有机会复用同类的数据校验,并且又能方便快速的实现各自不同的数据校验呢?这就是我开发Puzzle系统的原因,它使用AOP的思想,在面向对象设计的基础上,通过对服务中个各个功能步骤或阶段的分解,实现各个逻辑过程的分离,降低耦合,提高维护性。

讲述Puzzle系统前,我们先了解一下Puzzle系统中的一些概念。

1.首先是领域(Domain)的概念。任何一个特殊的实际应用均可被认为是一个特定的领域。

还是以保存案例进行说明,在系统中最常见的就是用户信息的保存和日志的保存。这时必然会首先定义保存功能的接口(ISaveService),并且分别在用户信息(UserInfoSaveServiceImple)和日志(LogSaveServiceImpl)中进行实现。那么很自然的用户信息(UserInfo)和日志(Log)就会被划分为一个特殊的实际应用场景,也就是领域(Domain)。如下图。

在实际业务系统中,可以根据实际业务情况对领域进行划分,在有条件的情况下,将领域划分的越细、越小,对日后功能的扩展和维护越有帮助。

2.服务的定义与实现(ServiceDefine,ServiceImplement)

使用Puzzle开发有一个基础思路是“一切皆为服务”。任何一个细小的功能都尽可能的定义为服务,从而便于此服务的扩展和被调用。

上例中的ISaveService就是一个服务的定义,而UserInfoSaveServiceImpl就是在UserInfo领域下的ISaveService实现。

简单示例如下:

public interface ISaveService
{
    void Save(object value);
}

[ServiceClass(typeof(ISaveService),ServiceLifeCycle.SingleGet)]
public class UserInfoSaveServiceImpl:ServiceComponent,ISaveService
{
    public void Save(object value)
    {
        throw new NotImplementedException();
    }
}

3.事件拦截(EventInterceptor)

TODO

4.领域服务容器(DomainServiceContainer)

有了领域的定义和服务的定义后,就需要了解领域服务容器。从字面上就可以清楚的了解到这个容器是用来存放不同领域下的服务的定义和实现(根据服务的生命周期定义来确定是否缓存实现)。

依然使用前面的例子,当Puzzle系统启动后,Puzzle系统中此时就会存在两个相对独立的领域服务容器,分别是UserInfo和Log。在这两个服务容器中都记录了各自对ISaveService的实现定义。

//从UserInfo领域下获取ISaveService的实现(UserInfoSaveServiceImple)
ISaveService userSaver = ServiceContainer.GetService<ISaveService>("UserInfo");
//从Log领域下获取ISaveService的实现(LogSaveServiceImpl)
ISaveService logSaver = ServiceContainer.GetService<ISaveService>("Log");

5.功能点与功能包(FunctionPoint,FunctionPackage)

在设计时对业务进行不同的领域划分,并且在编码时对与抽象出的服务定义做出了实现后,还需要对于所有的服务进行拼装组合(就想玩拼图游戏一样)。一个服务实现就是一个功能点(FunctionPoint),一个服务领域下的功能点集合就是一个功能包(FunctionPackage)。

不同的系统中会有不同类型的功能点。Puzzle系统使用功能点特性标记(FunctionPointAttribute)来标记不同的功能点类型。

Puzzle系统默认提供了服务类型功能点(ServiceClass)和事件拦截器类型功能点(EventInterceptorClass)

功能点特性标记的详细说明和如何创建新的功能点类型会在后面提到。

使用XML文件方式来进行功能包和功能点的定义。

简单的功能包示例如下:

<?xml version="1.0" encoding="utf-8"?>
<FunctionPackage xmlns="http://Puzzle">
  <Name>Standard FunctionPackage</Name>
  <Domain>Standard</Domain>
  <BasePackages />
  <Items>
    <FunctionPoint>
      <Name>HelloWorld</Name>
      <AssemblyQualifiedName>Example.ServiceClass.Console.Services.HelloWorldService,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
  </Items>
</FunctionPackage>

示例中的功能包定义表示该功能包所属的领域名称为“Standard”,只包含一个名称为HelloWorld的功能点,并且没有引用基础功能包。

Puzzle系统在启动后解析此功能包时会首先查询是否存在名称为“Standard”的DomainServiceContainer,如果没有会首先创建。之后会将HelloWorld功能点的定义放入该领域服务容器中,等待被调用。

6.服务的生命周期(ServiceLifeCycle)

在软件的设计开开发过程中,不可避免的会使用到单例模式(wikipedia)。那么就意味着领域服务容器中的不同服务的生命周期会有所不同。Puzzle系统中使用ServiceLifeCycle枚举标识了服务的生命周期定义。

public enum ServiceLifeCycle
SingleGet 每次获取时重新创建
Singleton 每一领域的服务容器中只存在唯一实例

第二点介绍服务实现时的示例中可以看到UserInfoSaveServiceImpl被标记了ServiceClassAttribute,其中第二个参数为ServiceLifeCycle.SingleGet。此参数标识了UserInfoSaveServiceImpl这个服务实现是在每次创建时均创建新的实例(领域服务容器中不会保留该服务实现的实例,每次获取服务时均重新创建新实例)。如果将其标记为ServiceLifeCycle.Singleton则表示此服务实现的实例在领域服务容器中是一直存在的,直到所有系统被关闭回收(领域服务容器保留该服务实现的实例,每次获取到的实例均为同一个)。

7.Puzzle系统的启动和关闭

Puzzle.Application.Start()

开始运行系统(收集并解析功能包,创建领域服务容器)。

Puzzle.Application.Stop()

关闭系统,并释放系统分配的所有资源。

简单服务实现示例:

以下示例显示了如何创建了一个服务接口及对应的一个服务实现。并且如何通过功能包和功能点的配置使其在Puzzle系统中生效,及如何获取该服务实现。

首先定义服务接口及服务实现。

using System;

namespace Example
{
    public interface IHelloWorldSerivce
    {
        void ShowHelloWorld();
    }
}

using System;
using Puzzle;

namespace Example.Console.Services
{
    [ServiceClass(typeof(IHelloWorldSerivce), ServiceLifeCycle.SingleGet)]
    public class HelloWorldService : ServiceComponent, IHelloWorldSerivce
    {
        public void ShowHelloWorld()
        {
            System.Console.WriteLine("Hello World");
        }
    }
}

通过功能包的配置向Puzzle说明功能点及其所属的领域。

<?xml version="1.0" encoding="utf-8"?>
<FunctionPackage xmlns="http://Puzzle">
  <Name>Standard FunctionPackage</Name>
  <Domain>Standard</Domain>
  <BasePackages />
  <Items>
    <FunctionPoint>
      <Name>HelloWorld</Name>
      <AssemblyQualifiedName>Example.Console.Services.HelloWorldService,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
  </Items>
</FunctionPackage>

启动Puzzle系统,通过ServiceContainer获取服务实现。

using System;namespace Example
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Puzzle.Application.Start();
            IHelloWorldService service = Puzzle.ServiceContainer.GetService<IHelloWorldService>("Standard");
            service.ShowHelloWorld();
        }
    }
}

// 此示例将输出以下内容:
// Hello World

附加事件拦截的示例:

在前例的基础上,当我们需要在真正的ShowHelloWorld方法进行附加操作时对其进行扩展该如何操作?

通过为实现服务增加可拦截的接口,并定义不同业务需要的拦截器即可实现相应业务需要。

以下示例显示如何在ShowHelloWorld方法输出既定内容之前和之后附加输出其他内容。

首先增加事件的接口定义。

using System;

namespace Example
{
    public interface IHelloWorldServiceEventArgs
    {
        event EventHandler BeforeShow;

        event EventHandler AfterShow;
    }
}

对原有的HelloWorldService进行扩展,使其实现刚刚增加的事件定义。并且在真正输出内容的前后分别抛出对应事件。

using System;
using Puzzle;

namespace Example
{
    [ServiceClass(typeof(IHelloWorldService), ServiceLifeCycle.SingleGet)]
    public class HelloWorldService : ServiceComponent, IHelloWorldService, IHelloWorldServiceEventArgs
    {
        public event EventHandler BeforeShow;

        public event EventHandler AfterShow;

        public void ShowHelloWorld()
        {
            if (BeforeShow != null)
            {
                this.BeforeShow(this, new EventArgs());
            }

            System.Console.WriteLine("Hello World!");

            if (AfterShow != null)
            {
                this.AfterShow(this, new EventArgs());
            }
        }
    }
}

此时我们就完成了对原有HelloWorldService的修改。使其有能力有机会在需要的情况下被扩展。

其次分别定义对应BeforeShow和AfterShow的事件拦截器(BeforeInterceptor,AfterInterceptor)。

using System;
using Puzzle;

namespace Example.EventInterceptor
{
    [EventInterceptorClass]
    public class BeforeInterceptor : ServiceComponent
    {
        [EventInterceptor(typeof(IHelloWorldService), "BeforeShow")]
        public void Show(object sender, EventArgs e)
        {
            System.Console.WriteLine("我先说:)");
        }
    }
}

using System;
using Puzzle;

namespace Example.EventInterceptor
{
    [EventInterceptorClass]
    public class AfterInterceptor : ServiceComponent
    {
        [EventInterceptor(typeof(IHelloWorldService), "AfterShow")]
        public void Show(object sender, EventArgs e)
        {
            System.Console.WriteLine("我说晚了:(");
        }
    }
}

接着在原有的功能包配置中增加以上两个事件拦截器的定义

<?xml version="1.0" encoding="utf-8"?>
<FunctionPackage xmlns="http://Puzzle">
  <Name>Standard FunctionPackage</Name>
  <Domain>Standard</Domain>
  <BasePackages />
  <Items>
    <FunctionPoint>
      <Name>HelloWorld</Name>
      <AssemblyQualifiedName>Example.EventInterceptor.HelloWorldService,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
    <FunctionPoint>
      <Name>BeforeInterceptor</Name>
      <AssemblyQualifiedName>Example.EventInterceptor.BeforeInterceptor,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
    <FunctionPoint>
      <Name>AfterInterceptor</Name>
      <AssemblyQualifiedName>Example.EventInterceptor.AfterInterceptor,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
  </Items>
</FunctionPackage>

最后不用更改原有的服务调用方法,即可发现输出内容有所变化

using System;

namespace Example
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Puzzle.Application.Start();
            IHelloWorldService service = Puzzle.ServiceContainer.GetService<IHelloWorldService>("Standard");
            service.ShowHelloWorld();
        }
    }
}

// 原示例将输出以下内容:
// Hello World

// 此示例将输出以下内容:
// 我先说:)
// Hello World!
// 我说晚了:(

继承基础功能包中提供的功能点

功能包默认会继承所有其指定的基础功能包中的功能点。

例如,基础功能包BasePackage

<?xml version="1.0" encoding="utf-8"?>
<FunctionPackage xmlns="http://Puzzle">
  <Name>BasePackage</Name>
  <Domain>Base</Domain>
  <BasePackages />
  <Items>
    <FunctionPoint>
      <Name>HelloWorld</Name>
      <AssemblyQualifiedName>Example.ServiceClass.Console.Services.HelloWorldService,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
  </Items>
</FunctionPackage>

继承基础功能包BasePackage的InheritPackage功能包

<?xml version="1.0" encoding="utf-8"?>
<FunctionPackage xmlns="http://Puzzle">
  <Name>InheritPackage</Name>
  <Domain>Inherit</Domain>
  <BasePackages>
    <Name>BasePackage</Name>
  </BasePackages>
  <Items />
</FunctionPackage>

InheritPackage功能包并未标记任何功能点,只是标记了继承了BasePackage。此时在Puzzle系统中,Inherit领域下将自动包含BasePackage中定义的HelloWorld功能点。

using System;

namespace Example
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Puzzle.Application.Start();
            IHelloWorldService service = Puzzle.ServiceContainer.GetService<IHelloWorldService>("Inherit");
            service.ShowHelloWorld();
        }
    }
}

// 示例将输出以下内容:
// Hello World

重写或隐藏基础功能包中提供的功能点

以下示例显示当前功能能包中如何定义功能点来重写或隐藏基础功能包所提供的功能点。

例如:功能包BasePackage中有HelloWorld功能点定义:

<?xml version="1.0" encoding="utf-8"?>
<FunctionPackage xmlns="http://Puzzle">
  <Name>BasePackage</Name>
  <Domain>Base</Domain>
  <BasePackages />
  <Items>
    <FunctionPoint>
      <Name>HelloWorld</Name>
      <AssemblyQualifiedName>Example.ServiceClass.Console.Services.HelloWorldService,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
  </Items>
</FunctionPackage>

功能包CoverPackage中引用了BasePackage功能包作为基础功能包

<?xml version="1.0" encoding="utf-8"?>
<FunctionPackage xmlns="http://Puzzle">
  <Name>CoverPackage</Name>
  <Domain>Cover</Domain>
  <BasePackages>
    <Name>
      BasePackage
    </Name>
  </BasePackages>
  <Items>
    <FunctionPoint>
      <Name>HelloWorld</Name>
      <Enable>false</Enable>
      <AssemblyQualifiedName>Example.ServiceClass.Console.Services.HelloWorldService,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
  </Items>
</FunctionPackage>

功能包CoverPackage中的功能点HelloWorld重写了功能包BasePackage中的功能点HelloWorld,由于功能包CoverPackage中的HelloWorld功能点的Enable属性为false。 表示着领域CoverPackage下将不再提供基础功能包BasePackage提供的功能点HelloWorld。

通过 AssemblyQualifiedName 属性是否相同来确认是否为相同的功能点。

此时可以增加另外一个FunctionPoint节点,写入不同的IHelloWorldService接口的实现的AssemblyQualifiedName,从而实现复写BasePackage中提供的HelloWorldService接口实现的目的。

功能点之间的依赖关系

功能点之间的依赖关系由FunctionPoint对象的Dependency属性进行标记。

Dependency属性记录该功能点依赖的功能点的Name。用来标记其需要在依赖功能点执行完成后执行。

以前文提到过的事件依赖器的例子继续衍生,增加BeforeShow的事件拦截器(BeforeInterceptorInFirst)

using System;
using Puzzle;

namespace Example.EventInterceptor
{
    [EventInterceptorClass]
    public class BeforeInterceptorInFirst : ServiceComponent
    {
        [EventInterceptor(typeof(IHelloWorldService), "BeforeShow")]
        public void Show(object sender, EventArgs e)
        {
            System.Console.WriteLine("沙发!!");
        }
    }
}

修改功能包文件,增加BeforeInterceptorInFirst功能点,并将BeforeInterceptor功能点的Dependency标记为BeforeInterceptorInFirst功能点的Name

<?xml version="1.0" encoding="utf-8"?>
<FunctionPackage xmlns="http://Puzzle">
  <Name>Standard FunctionPackage</Name>
  <Domain>Standard</Domain>
  <BasePackages />
  <Items>
    <FunctionPoint>
      <Name>HelloWorld</Name>
      <AssemblyQualifiedName>Example.EventInterceptor.HelloWorldService,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
    <FunctionPoint>
      <Name>BeforeInterceptor</Name>
      <Dependency>BeforeInterceptorInFirst</Dependency>
      <AssemblyQualifiedName>Example.EventInterceptor.BeforeInterceptor,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
    <FunctionPoint>
      <Name>AfterInterceptor</Name>
      <AssemblyQualifiedName>Example.EventInterceptor.AfterInterceptor,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
    <FunctionPoint>
      <Name>BeforeInterceptorInFirst</Name>
      <AssemblyQualifiedName>Example.EventInterceptor.BeforeInterceptorInFirst,Puzzle.Example</AssemblyQualifiedName>
    </FunctionPoint>
  </Items>
</FunctionPackage>

可以看到XML文件中功能点定义的顺序并不会影响到功能点在系统中依赖关系的确定。

最后不用更改原有的服务调用方法,即可发现输出内容有所变化

using System;

namespace Example
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Puzzle.Application.Start();
            IHelloWorldService service = Puzzle.ServiceContainer.GetService<IHelloWorldService>("Standard");
            service.ShowHelloWorld();
        }
    }
}

// 原示例将输出以下内容:
// 我先说:)
// Hello World!
// 我说晚了:(

// 此示例将输出以下内容:
// 沙发!!
// 我先说:)
// Hello World!
// 我说晚了:(

功能点特性标记(FunctionPointAttribute)

前文提到过,所有的功能点均由其对应的功能点特性标记来进行说明。

Puzzle系统在启动时,首先查找所有可用的功能包。在完成查找和整理所有待处理的功能包的依赖关系后,会自动开始顺序解析功能包。

解析功能包前由会首先按照前文提到的功能点之间的依赖关系及功能点的优先级对当前功能包中所有的功能点的依赖关系进行整理,整理完成后顺序解析功能点。

解析功能点时就会首先解析功能点代码中标记的功能点特性标记,根据不同的功能点特性标记中提供的功能点解析器对功能点进行解析。

Puzzle系统在解析功能点时默认将ServiceClass类型的功能点优先级排为最高,其次为EventInterceptor类型的功能点。其他类型功能点优先级为正常。

如果需要在不同的业务系统中按照增加功能点类型,只需要在FunctionPointAttribute基础上增加派生类,并增加对应的IFunctionPointParse实现即可。

由于时间有限,本篇文章和Puzzle系统都暂时还只是一个雏形和只实现了最基础功能的AOP框架,后期还会不间断的进行维护和扩展。欢迎广大看客提出宝贵意见!

Alpha 1.1 版本

Puzzle.7z

PuzzleHelper.7z

时间: 2024-08-02 09:52:07

Puzzle 面向切面AOP开发框架 For .Net的相关文章

Spring4面向切面AOP

AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的横向多模块统一控制的一种技术.AOP是OOP的补充,是spring框架中的一个重要内容.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率.AOP可以分为静态织入与动态织入,静态织入即在编译前将需织入内容写入目标模块中,这样成本非常高.动态织入则不需要改变目标模块.Spring框架实现了AOP,使用注解

spring面向切面aop拦截器

spring中有很多概念和名词,其中有一些名字不同,但是从功能上来看总感觉是那么的相似,比如过滤器.拦截器.aop等. 过滤器filter.spring mvc拦截器Interceptor .面向切面编程aop,实际上都具有一定的拦截作用,都是拦截住某一个面,然后进行一定的处理. 在这里主要想着手的是aop,至于他们的比较,我想等三个都一一了解完了再说,因此这里便不做过多的比较. 在我目前的项目实践中,只在一个地方手动显示的使用了aop,那便是日志管理中对部分重要操作的记录. 据我目前所知,ao

Spring学习1_面向切面( AOP )实现原理

面向切面编程 (Aspect Oriented Programming,简称AOP) 是Spring的一个重要特性,其原理是采用动态代理方式实现. 下面通过一个Demo来模拟AOP实现 整个代码目录结构如下: 其中LogInterceptor类完成为所有Service方法添加日志记录的功能. 1.Dao层实现 package com.dao; public class UserDaoImpl implements UserDao { @Override public void save() {

Spring框架使用(控制反转,依赖注入,面向切面AOP)

参见:http://blog.csdn.net/fei641327936/article/details/52015121 Mybatis: 实现IOC的轻量级的一个Bean的容器 Inversion of control 控制反转:由容器控制程序之间的关系,不是程序代码操作 Depend Inject 依赖注入 Aspect oriented programming 面向切面编程 Spring能帮助我们根据配置文件创建及组装对象之间的依赖关系: Spring面向切面编程能帮助我们无耦合的实现日

Spring_面向切面(AOP)基础

努力不一定成功:但是放弃必定会失败. 面向切面编程 在软件开发中,散布于应用中多处的功能称为横切关注点.通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中).把这些横切关注点与业务逻辑分离正是面向切面编程(AOP)所要解决的问题. 如果要重用功能的话,最常见的面向对象技术是继承(inheritance)或委托(delegation).但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系:而使用委托可能需要对委托对象进行复杂的调用

Spring面向切面(AOP)

AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志.事务.权限等,Struts2的拦截器设计就是基于AOP的思想. AOP的基本概念 Aspect(切面):通常是一个类,里面可以定义切入点和通知 JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用. Advice(通知):AOP在特定的切入点上执行的增强处理,有before.after.afterReturning.afterThrowing.around Pointcut(切入点):AOP框架创

解析Spring第三天(面向切面AOP)

面向切面:AOP 在不修改源代码的基础上,对方法进行增强.AOP的底层原理就是代理技术(第一种:jdk的动态代理(编写程序必须要有接口).第二种:cglib代理技术(生成类的子类).如果编写的程序有借口,则spring框架会自动使用jdk的动态代理技术增强,). Joinpoint(连接点) 所谓连接点是指那些被拦截到的点.在spring中,这些点指的是方法,因为spring只支持方法类型的连接点 Pointcut(切入点) -- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义 Ad

Spring基础(二)_面向切面(AOP)

面向切面编程 面向切面编程[AOP,Aspect Oriented Programming]:通过预编译方式和运行期间动态代理实现程序功能的统一维护的技术.AOP 是 Spring 框架中的一个重要内容,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. 在 Spring 中,依赖注入管理和配置应用对象,有助于应用对象之间的解耦.而面向切面编程可以实现横切关注点与它们所影响的对象之间的解耦. 横切关注点:散布在应用中

Spring学习一:IOC(控制反转)和AOP(面向切面)的xml配置和注解方式

Spring框架的作用:为了简化java开发 Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来.它是为了解决企业应用开发的复杂性而创建的 一.IOC(控制反转) 1 构造器注入 (xml配置) 2方法注入 (注解方式) (xml配置) 二,AOP(面向切面) 注解方式 xml配置 只是使用,那简单,