02第二阶段面向切面编程AOP.docx
《02第二阶段面向切面编程AOP.docx》由会员分享,可在线阅读,更多相关《02第二阶段面向切面编程AOP.docx(23页珍藏版)》请在冰豆网上搜索。
![02第二阶段面向切面编程AOP.docx](https://file1.bdocx.com/fileroot1/2022-11/25/ce43d4f8-c8ad-4a16-8881-435574172ad4/ce43d4f8-c8ad-4a16-8881-435574172ad41.gif)
02第二阶段面向切面编程AOP
十三、面向切面编程(基础篇)
AOP即面向切面编程(AspectOrientedProgramming的缩写),是OOP(面向对象编程)的一种延续形式。
是通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术,它从一个不同于OOP的角度来看待程序的结构:
OOP将应用程序分解为一系列表现为继承关系的对象;AOP则把程序分解为一系列方面(aspects)或者关注点(concerns)。
AOP将诸如事务管理等本来横向分布在多个对象中的关注点进行了模块化处理(这些关注点也常称为横切(crosscutting)关注点)。
在Spring.NET中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。
应用对象只实现它们应该做的——完成业务逻辑——仅此而已。
它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
下面我举个例子来说明这一切:
场景:
业务类CompanyManager在调用Save方法的时候需要调用SecurityManager类判断权限是否足够(图1)。
图1
准备条件:
publicclassCompanyDao
{
publicvoidSave()
{
Console.WriteLine("保存数据");
}
}
publicinterfaceICompanyManager
{
stringUserName{get;set;}
voidSave();
}
publicinterfaceISecurityManager
{
boolIsPass(stringuserName);
}
SecurityManager
publicclassSecurityManager:
ISecurityManager
{
/**////
///判断权限
///
///
///
publicboolIsPass(stringuserName)
{
returnuserName=="admin";
}
}
第一种实现方式,我们通常会这样做:
直接在CompanyManager类中调用ISecurityManager接口的IsPass方法判断权限。
SimpleCompanyManager
publicclassSimpleCompanyManager:
ICompanyManager
{
可通过外部注入的属性#region可通过外部注入的属性
publicstringUserName{get;set;}
publicCompanyDaoDao{get;set;}
#endregion
publicvoidSave()
{
//判断权限
ISecurityManagersecurity=newSecurityManager();
if(security.IsPass(UserName))
{
//执行业务方法
//.
//调用DAO层方法
Dao.Save();
}
else
{
//执行其它业务方法
Console.WriteLine("您没有该权限");
}
}
}
这样CompanyManager类与ISecurityManager或SecurityManager会发生业务性耦合。
聪明的朋友会发现在GOF(设计模式)中有一种模式(代理模式)可以解除这种耦合。
第二种实现方式,代理模式(ProxyPattern):
什么是代理模式?
是给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。
代理就是一个人或一个机构代表另一个人或者一个机构采取行动。
某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。
客户端分辨不出代理主题对象与真实主题对象。
代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入(图2)。
图2
CompanyManager
publicclassCompanyManager:
ICompanyManager
{
可通过外部注入的属性#region可通过外部注入的属性
publicstringUserName{get;set;}
publicCompanyDaoDao{get;set;}
#endregion
publicvoidSave()
{
//执行业务方法
//.
//调用DAO层方法
Dao.Save();
}
}
CompanyProxyManager
publicclassCompanyProxyManager:
ICompanyManager
{
publicstringUserName{get;set;}
privateICompanyManagertarget=newCompanyManager();
publicvoidSave()
{
//判断权限
ISecurityManagersecurity=newSecurityManager();
if(security.IsPass(UserName))
{
//调用目标对象Save方法
target.Save();
}
else
{
Console.WriteLine("您没有该权限");
}
}
}
这样,CompanyManager类就不必与判断权限的类SecurityManager耦合,但是这种方式实现起来比较麻烦。
第三种实现方式,Spring.NET提供的AOP:
AopAlliance.Intercept.IMethodInterceptor接口和ProxyFactory类的组合。
AroundAdvice
publicclassAroundAdvice:
IMethodInterceptor
{
//权限系统类(可外部注入)
privateISecurityManagermanager=newService.SecurityManager();
publicobjectInvoke(IMethodInvocationinvocation)
{
//拦截Save方法
if(invocation.Method.Name=="Save")
{
ICompanyManagertarget=(ICompanyManager)invocation.Target;
returnmanager.IsPass(target.UserName)?
invocation.Proceed():
null;
}
else
{
returninvocation.Proceed();
}
}
}
Program
classProgram
{
staticvoidMain(string[]args)
{
ICompanyManagertarget=newCompanyManager(){Dao=newCompanyDao(),UserName="admin"};
ProxyFactoryfactory=newProxyFactory(target);
factory.AddAdvice(newAroundAdvice());
ICompanyManagermanager=(ICompanyManager)factory.GetProxy();
manager.Save();
Console.ReadLine();
}
}
输出:
保存数据
Spring.NET利用System.Reflection.Emit命名空间下的类在运行时动态创建IL代码来生成AOP代理。
这使得代理(的创建)非常高效,并且不受任何继承层次的限制。
十四、AOP的概念(基础篇)
上篇我们简单的了解了AOP的应用场景,知道AOP编程的重要性。
这篇我们先看一段代码,来开始今天的学习。
回顾与上篇类似的代码:
SecurityService类的IsPass判断用户名为“admin”则有权限保存数据。
OrderService为保存数据的类,实现IOrderService接口。
Code
publicclassSecurityService
{
publicboolIsPass(stringuserName)
{
returnuserName=="admin";
}
}
publicinterfaceIOrderService
{
stringUserName{get;set;}
objectSave(objectid);
}
实现部分
OrderService
publicclassOrderService:
IOrderService
{
publicstringUserName{get;set;}
publicobjectSave(objectid)
{
return"保存:
"+id.ToString();
}
}
AroundAdvise
publicclassAroundAdvise:
IMethodInterceptor
{
publicobjectInvoke(IMethodInvocationinvocation)
{
IOrderServicetarget=(IOrderService)invocation.Target;
SecurityServicemanager=newSecurityService();
if(manager.IsPass(target.UserName))
{
returninvocation.Proceed();
}
else
{
returnnull;
}
}
}
客户端部分:
Program
classProgram
{
staticvoidMain(string[]args)
{
ProxyFactoryfactory=newProxyFactory(newOrderService(){UserName="admin"});
factory.AddAdvice(newAroundAdvise());
IOrderServiceservice=(IOrderService)factory.GetProxy();
objectresult=service.Save
(1);
Console.WriteLine(result);
Console.ReadLine();
}
}
输出:
保存:
1
通过上篇的学习,我们知道,什么是AOP——面向切面的编程。
在AOP(面向切面的编程)中,我们编写程序时,首先思考要对程序中哪些方法进行拦截,拦截到这些方法后又要做哪些业务处理。
这些关注过程,我们称之为:
横切性关注点。
由“横切性关注点”引申出以下概念:
Aspect(切面):
指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象。
程序里的切面就是AroundAdvise类的实现部分。
joinpoint(连接点):
所谓连接点是指那些被拦截到的点。
在Spring.NET中,连接点指的是方法,因为Spring.NET只支持方法类型的连接点,实际上joinpoint(连接点)还可以是字段或类构造器。
程序里的连接点就是拦截到的方法,如OrderService类的Save方法。
Pointcut(切入点):
所谓切入点是指我们要对那些joinpoint(连接点)进行拦截的定义。
程序里没有使用invocation.Method来判断拦截哪些方式,而Pointcut(切入点)就是对所有方法进行拦截。
Advice(通知):
所谓通知是指拦截到joinpoint(连接点)之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,环绕通知。
AroundAdvise继承AopAlliance.Intercept.IMethodInterceptor接口,程序里使用的是环绕通知。
Target(目标对象):
代理的目标对象。
程序里的目标对象就是OrderService类,我们通过
ProxyFactoryfactory=newProxyFactory(newOrderService(){UserName="admin"});
这句代码确定了目标对象是OrderService。
AOP代理(AOPproxy):
由AOP框架在将通知应用于目标对象后创建的对象。
程序里通过GetProxy()方法创建出的代理对象。
Weave(织入):
指将切面(aspect)应用到目标对象(target)对象并导致代理(proxy)对象创建的过程称为织入。
正如程序里所应用的,OrderService类不具备判断权限的功能,我们将判断权限的功能——SecurityService类的IsPass方法应用到目标对象的过程。
Introduction(引入):
在不修改类代码的前提下,Introduction(引入):
可以在运行期为类动态地添加一些方法或字段。
程序里在没有修改OrderService类,而是在运行期把判断权限的功能通过ProxyFactory的AddAdvice方法动态的增加进去。
这些术语不属于Spring.NET,而属于整个AOP编程。
所谓AOP,我的理解就是应该是这样一个过程,首先需要定义一个切面,这个切面是一个类,里面的方法就是关注点(也是通知),或者说里面的方法就是用来在执行目标对象方法时需要执行的前置通知,后置通知,异常通知,最终通知,环绕通知等等。
有了切面和通知,要应用到目标对象,就需要定义这些通知的切入点,换句话说就是需要对哪些方法进行拦截,而这些被拦截的方法就是连接点,所谓连接点也就是在动态执行过程,被织入切面的方法(至少在Spring.NET中只能对方法进行拦截)。
因此,在动态过程中通知的执行就属于织入过程,而被织入这些通知的对象就是目标对象了。
十五、AOP的通知类型(基础篇)
上篇我们学习了AOP的基本概念,我们回顾一下上篇提到的Advice(通知):
所谓通知是指拦截到joinpoint(连接点)之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,环绕通知。
Spring.NET的通知既可由某个类的所有对象共享,也可由该类型的单个实例独占。
共享的通知称为基于类型(per-class)的通知,而独占的通知称为基于实例(per-instance)的通知。
基于类型的通知最为常用。
很多常用功能很适合用基于类型的通知实现,比如说事务。
它们不依赖于目标对象的状态,也不会向目标对象添加新状态,仅仅对方法及其参数进行操作。
基于实例的通知比较适合做引入(introductions)。
此时通知可以向目标对象添加状态。
在AOP代理中,可以同时使用基于类型和基于实例的通知。
Spring.NET和spring框架略有不同,只有四种通知类型,没有spring框架的最终通知(目前我还没有实现最终通知,如果有朋友实现的话,可以给我留言)。
一、拦截环绕通知(aroundadvice):
Spring.NET中最基本的通知类型是拦截环绕通知(interceptionaroundadvice),即方法拦截器。
拦截环绕通知继承IMethodInterceptor接口。
注意其中IMethodInvocation.Proceed()方法的调用。
该方法会依次调用拦截器链上的其它拦截器。
大部分拦截器都需要调用这个方法并返回它的返回值。
当然,也可以不调用Proceed方法,而返回一个其它值或抛出一个异常,但一般不太会这么做。
二、前置通知(beforeadvise):
是在IMethodInterceptor.Proceed()方法调用前的通知。
继承自IMethodBeforeAdvice接口。
三、异常通知(throwsadvise):
是在IMethodInterceptor.Proceed()方法调用时发生异常的通知。
继承自IthrowsAdvice接口。
IthrowsAdvice接口没有定义任何方法:
它是一个标识接口(按:
之所以用标识接口,原因有二:
1、在通知方法中,只有最后一个参数是必须的。
如果声明为接口的方法,参数列表就被固定了。
2、如果第一个原因可以用重载的接口方法解决,那么这个原因就是使用标识接口的充分原因了:
实现此接口的类必须声明一或多个通知方法,接口方法做不到这一点),用以表明实现它的类声明了一或多个强类型的异常通知方法。
四、后置通知(afterreturningadvise):
是在IMethodInterceptor.Proceed()方法调用后的通知。
继承自IAfterReturningAdvice接口。
后置通知对切入点的执行没有影响,如果通知抛出异常,就会沿拦截器链向上抛出,从而中断拦截器链的继续执行。
代码实现:
准备条件
四种通知:
AroundAdvise
/**////
///环绕通知
///
publicclassAroundAdvise:
IMethodInterceptor
{
publicobjectInvoke(IMethodInvocationinvocation)
{
Console.Out.WriteLine(string.Format("环绕通知:
调用的方法'{0}'",invocation.Method.Name));
Console.WriteLine();
objectreturnValue=null;
try
{
returnValue=invocation.Proceed();
}
catch
{
Console.Out.WriteLine("环绕通知:
发生异常");
Console.WriteLine();
}
Console.Out.WriteLine(String.Format("环绕通知:
返回值'{0}'",returnValue));
returnreturnValue;
}
}
BeforeAdvise
/**////
///前置通知
///
publicclassBeforeAdvise:
IMethodBeforeAdvice
{
publicvoidBefore(MethodInfomethod,object[]args,objecttarget)
{
Console.Out.WriteLine("前置通知:
调用的方法名:
"+method.Name);
Console.Out.WriteLine("前置通知:
目标:
"+target);
Console.Out.WriteLine("前置通知:
参数为:
");
if(args!
=null)
{
foreach(objectarginargs)
{
Console.Out.WriteLine("\t:
"+arg);
}
}
Console.WriteLine();
}
}
ThrowsAdvise
/**////
///异常通知
///
publicclassThrowsAdvise:
IThrowsAdvice
{
publicvoidAfterThrowing(Exceptionex)
{
stringerrorMsg=string.Format("异常通知:
方法抛出的异常:
{0}",ex.Message);
Console.Error.WriteLine(errorMsg);
Console.WriteLine();
}
}
AfterReturningAdvise
/**////
///后置通知
///
publicclassAfterReturningAdvise:
IAfterReturningAdvice
{
publicvoidAfterReturning(objectreturnValue,MethodInfomethod,object[]args,objecttarget)
{
Console.Out.WriteLine("后置通知:
方法调用成功,方法名:
"+method.Name);
Console.Out.WriteLine("后置通知:
目标为:
"+target);
Console.Out.WriteLine("后置通知:
参数:
");
if(args!
=null)
{
foreach(objectarginargs)
{
Console.Out.WriteLine("\t:
"+arg);
}
}
Console.Out.WriteLine("后置通知:
返回值是:
"+returnValue);
Console.WriteLine();
}
}
接口:
publicinterfaceIOrderService
{
objectSave(objectid);
}
一、没有异常的情况
OrderService
publicclassOrderService:
IOrderService
{
/**////
///拦截该方法
///
///
///
publicobjectSave(objectid)
{
//thro