面向切面编程 (AOP)
面向切面编程 (AOP) 通过提供另一种思考程序结构的方法来补充面向对象编程 (OOP)。
AOP 在 Spring.NET 中的使用:
- 提供声明式企业服务,特别是作为 COM+ 声明式服务的替代品。
最重要的服务是声明式事务管理,它建立在 Spring.NET 的事务抽象基础上。此功能计划在 Spring.NET 的即将发布版本中推出。
- 允许用户实现自定义方面,使用 AOP 补充 OOP 的使用。
概念
基础概念
- Aspect(切面)
对可能跨多个对象的关注点的模块化。事务管理是企业应用程序中跨领域关注点的一个很好的例子。切面在 Spring.NET 中实现为 Advisors 或拦截器。
- Joinpoint(连接点)
程序执行期间的一个点,例如方法调用或特定异常的抛出。
- Advice(通知)
AOP 框架在特定连接点采取的操作。不同类型的通知包括 “around”、”before” 和 “throws” 通知。通知类型将在下文讨论。包括 Spring.NET 在内的许多 AOP 框架将通知建模为拦截器,在连接点周围维护一条拦截器链。
- Pointcut(切入点)
指定通知应该触发的一组连接点。AOP 框架必须允许开发人员指定切入点,例如使用正则表达式。
- Introduction(引入)
向被通知的类添加方法或字段。Spring.NET 允许您将新接口引入任何被通知对象。例如,您可以使用引入使任何对象实现 IAuditable 接口,以简化对对象状态变化的跟踪。
- Target object(目标对象)
包含连接点的对象。也称为被通知对象或代理对象。
- AOP proxy(AOP 代理)
由 AOP 框架创建的对象,包括通知。在 Spring.NET 中,AOP 代理是使用在运行时生成的 IL 代码的动态代理。
- Weaving(织入)
组合切面以创建一个被通知对象。这可以在编译时完成(例如,使用 Gripper-Loom.NET 编译器),也可以在运行时完成。Spring.NET 在运行时进行织入。
不同类型
- Around advice(环绕通知)
环绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知将在方法调用前后执行自定义行为。它们负责选择是否继续执行连接点,或通过返回自己的返回值或抛出异常来中断执行。
- Before advice(前置通知)
在连接点之前执行的通知,但不能阻止执行流继续到连接点(除非它抛出异常)。
- Throws advice(异常通知)
在方法抛出异常时执行的通知。Spring.NET 提供强类型的异常通知,因此您可以编写捕获您感兴趣的异常(及其子类)的代码,而无需从 Exception 转型。
- After returning advice(返回后通知)
在连接点正常完成后执行的通知:例如,如果方法返回而不抛出异常。
快速开始
- ICommand.cs
public interface ICommand
{
object Execute(object context);
}
- ServiceCommand.cs
public class ServiceCommand : ICommand
{
public object Execute(object context)
{
Console.Out.WriteLine("Service implementation : [{0}]", context);
return null;
}
}
- ConsoleLoggingAroundAdvice.cs
public class ConsoleLoggingAroundAdvice : IMethodInterceptor
{
public object Invoke(IMethodInvocation invocation)
{
Console.Out.WriteLine("Advice executing; calling the advised method...");
object returnValue = invocation.Proceed();
Console.Out.WriteLine("Advice executed; advised method returned " + returnValue);
return returnValue;
}
}
调用
ProxyFactory factory = new ProxyFactory(new ServiceCommand());
factory.AddAdvice(new ConsoleLoggingAroundAdvice());
ICommand command = (ICommand)factory.GetProxy();
command.Execute("This is the argument");
运行结果
Advice executing; calling the advised method...
Service implementation : [This is the argument]
Advice executed; advised method returned
当然,也可以使用 XML 声明的方式。(XML 比较实用,且复用性比较强)
<object id="consoleLoggingAroundAdvice"
type="springNet.Aop.ConsoleLoggingAroundAdvice"/>
<object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="target">
<object id="myServiceObjectTarget"
type="springNet.Aop.ServiceCommand"/>
</property>
<property name="interceptorNames">
<list>
<value>consoleLoggingAroundAdvice</value>
</list>
</property>
</object>
调用方式
IApplicationContext context = new XmlApplicationContext(
"Resources/Objects.xml");
ICommand command = (ICommand)context["myServiceObject"];
command.Execute("Execute with xml");
结果
Advice executing; calling the advised method...
Service implementation : [Execute with xml]
Advice executed; advised method returned
Pointcuts 基础
假设方法改变如下,Advice 保持不变。
- ICommand.cs
public interface ICommand
{
object Execute(object context);
object LazyExecute(object context);
}
- ServiceCommand.cs
public class ServiceCommand : ICommand
{
public object Execute(object context)
{
Console.Out.WriteLine("ServiceCommand Execute...{0}", context);
return null;
}
public object LazyExecute(object context)
{
Console.Out.WriteLine("ServiceCommand LazyExecute...{0}", context);
return null;
}
}
运行如下测试
ProxyFactory factory = new ProxyFactory(new ServiceCommand());
factory.AddAdvice(new ConsoleLoggingAroundAdvice());
ICommand command = (ICommand)factory.GetProxy();
command.Execute("This is the argument");
command.LazyExecute("This is the argument");
结果
Advice executing; calling the advised method...
ServiceCommand Execute...This is the argument
Advice executed; advised method returned
Advice executing; calling the advised method...
ServiceCommand LazyExecute...This is the argument
Advice executed; advised method returned
可见,对于2个方法AOP都生效了。这很合情合理。
如果这个时候提出一个需求,要求只有 LazyExecute() 执行AOP,怎么办呢?
修改调用代码如下:
ProxyFactory factory = new ProxyFactory(new ServiceCommand());
factory.AddAdvisor(new DefaultPointcutAdvisor(
new SdkRegularExpressionMethodPointcut("Lazy"),
new ConsoleLoggingAroundAdvice()));
ICommand command = (ICommand)factory.GetProxy();
command.Execute("This is the argument");
command.LazyExecute("This is the argument");
可见,只有满足 Lazy 开头的方法,才会被匹配到。
ServiceCommand Execute...This is the argument
Advice executing; calling the advised method...
ServiceCommand LazyExecute...This is the argument
Advice executed; advised method returned
使用 XML 的方式如下
<object id="consoleLoggingAroundAdvice"
type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor">
<property name="pattern" value="Lazy"/>
<property name="advice">
<object type="springNet.Aop.ConsoleLoggingAroundAdvice"/>
</property>
</object>
<object id="myServiceObject"
type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="target">
<object id="myServiceObjectTarget"
type="springNet.Aop.ServiceCommand"/>
</property>
<property name="interceptorNames">
<list>
<value>consoleLoggingAroundAdvice</value>
</list>
</property>
</object>
Pointcuts 进一步
一、Before advice
Before advice is just that… it is advice that runs before the target method invocation is invoked.
- ConsoleLoggingBeforeAdvice.cs
public class ConsoleLoggingBeforeAdvice : IMethodBeforeAdvice
{
public void Before(MethodInfo method, object[] args, object target)
{
Console.Out.WriteLine("Intercepted call to this method : " + method.Name);
Console.Out.WriteLine(" The target is : " + target);
Console.Out.WriteLine(" The arguments are : ");
if (args != null)
{
foreach (object arg in args)
{
Console.Out.WriteLine("\t: " + arg);
}
}
}
}
执行调用:
ProxyFactory factory = new ProxyFactory(new ServiceCommand());
factory.AddAdvice(new ConsoleLoggingBeforeAdvice());
ICommand command = (ICommand)factory.GetProxy();
command.Execute("Execute with before advice");
结果为:
Intercepted call to this method : Execute
The target is : springNet.Aop.ServiceCommand
The arguments are :
: Execute with before advice
ServiceCommand Execute...Execute with before advice
当然也可以使用 XML 配置方式。
<object id="beforeAdvice"
type="springNet.Aop..ConsoleLoggingBeforeAdvice"/>
<object id="myServiceObject"
type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="target">
<object id="myServiceObjectTarget"
type="springNet.Aop.ServiceCommand"/>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
</list>
</property>
</object>
After Advice(后置通知)
后置通知与前面提到的通知类型类似,当连接点完成后执行通知。无需详细说明。
Throws Advice(异常通知)
异常通知是当被通知的方法调用抛出异常时执行的通知。