Spring.Net实现AOP以及AOP相关概念(学习笔记四)
Spring的重要应用是在AOP(Aspect- Oriented programming面向方面编程)编程方面。Spring.Net是如何进行AOP的呢?下面是我在学习Spring实现AOP的例子以及我个人的一些浅显的理解,若有不妥之处,还望大侠们指出。
本节重点分如下两部分:
1、Spring实现AOP。
2、AOP相关概念。
先介绍如何实现AOP,然后再通过使用过程中的用法,谈谈我自己对AOP概念的理解。
首先还是介绍一下开发环境以及软件版本:
VS版本:VS2008 SP1、Spring版本:1.3.0。
在我使用Spring.Net实现AOP的例子中有两个部分:1、使用编程方式实现。2、使用配置方式实现。实际在应用中,应用配置实现AOP还是相对更好点。实现AOP除了之前用到的程序集以外,还需使用程序集Spring.AOP。
1、Spring实现AOP
先说说编程实现的方式。这种方式当然不需要配置文件。主要应用了程序集Spring.Net中Spring.Aop.Framework命名空间下的ProxyFactory(代理工厂)类。
先给出需要进行拦截的对象的代码:
1 public interface ICommand
2 {
3 object Execute(object context);
4
5 }
6 public class BusinessCommand : ICommand
7 {
8 public object Execute(object context)
9 {
10 Console.WriteLine("Service implementation is :{0}", context);
11 return context;
12 }
13
14 }
下面是实现对 BusinessCommand拦截的实现代码:
1public class ConsoleLoggingAroundAdvice : IMethodInterceptor
2 {
3 public object Invoke(IMethodInvocation invocation)
4 {
5 Console.WriteLine("Method in ConsoleLoggingAroundAdvice");
6
7 object obj = invocation.Proceed();
8 Console.WriteLine("invocation type is :{0}", invocation.GetType().ToString());
9 Console.WriteLine("Method name is {0}", invocation.Method.ToString());
10 return obj;
11 }
12 }
在这个例子中,我测试一下Spring.Net对Execute方法的拦截。使用代码如下:
1 ICommand target = new BusinessCommand();
2 ProxyFactory factory = new ProxyFactory(target);
3 factory.AddAdvice(new ConsoleLoggingAroundAdvice());
4 object obj = factory.GetProxy();
5
6
7 ICommand business = (ICommand)obj;
8 Console.WriteLine(obj.GetType().ToString());
9 //ICommand command = (ICommand)factory.GetProxy();
10 business.Execute("tyb");
11 target.Execute("This is the argument");
输出如下图:
从以上输出可以看到:我只是通过business.Execute("tyb")与 target.Execute("This is the argument")进行了输出。但是两条输出的执行方式是不一样的。显然后者输出的最后一条显示的结果。前者的输出结果为前5行。如果我用断点调试,则进行到ConsoleLoggingAroundAdvice中的Invoke方法。所以前一条输出了5行。为什么两条输出的结果会有这么大的差别呢?
business、target两者都是实现了ICommand接口对象的实例,而前者是通过代理工厂(ProxyFactory)获取代理(GetProxy)的方式实现的,后者则不一样。我的理解是:
通过factory.GetProxy()将拦截应用到了ICommand上,而target是直接通过new生成的,所以就没有将通知应用到target。
在输出的第一行中,输出了object obj = factory.GetProxy()中obj的类型。它也可以转换为ICommand,说明它也是实现了ICommand的。那么如果我们将ICommand business = (ICommand)obj中将ICommand用实现它的类BusinessCommand类替换成BusinessCommand business = (BusinessCommand)obj,是不是就不能正常运行了呢?答案是肯定的。运行过程中跑出如下异常:
由以上试验我们可推理:在应用拦截的对象中,拦截对象应该要实现一个接口。是不是这样呢。?我继续添加了ServiceCommand类,代码如下:
1 public class ServiceCommand
2 {
3 public object Execute(object context)
4 {
5 Console.WriteLine("Service implementation is :{0}", context);
6 return context;
7 }
8 }
1 ServiceCommand command2 = new ServiceCommand();
2 ProxyFactory factory2 = new ProxyFactory(command2);
3 factory2.AddAdvice(new ConsoleLoggingAroundAdvice());
4 ServiceCommand command3 = (ServiceCommand)factory2.GetProxy();
5 command3.Execute("tyb");
输出如下图:
由以上输出,我可以得知,根本就没有进行拦截。
注意:以上实现过程中,需要将接口申明为public类型的,即在给ProxyFactory的构造函数中给出对象的类型声明,不管是接口还是类对象,都必须是public的。
接口为private类型时,在类型转换的时候会有如下异常:
如果上例中的ServiceCommand未private类型的会有异常。但是和接口所抛出的不一样,有兴趣大家可以试试。
说完了编程实现AOP,下面说说通过配置的方式实现。
配置实现AOP。
配置方式实现AOP通过配置的方式以及IOC容器获取对象的方式实现。在IOC容器的配置中,被拦截BusinessCommand类的定义是代理工厂对象(ProxyFactoryObject).
配置如下:
1 <objects xmlns="http://www.springframework.net" xmlns:aop="http://www.springframework.net/aop">
2
3 <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject">
4 <property name="Target" >
5 <object id="businessCommand" type="SpringAOPConfig.BusinessCommand"/>
6 </property>
7 <property name="InterceptorNames">
8 <list>
9 <value>consoleLoggingAroundAdvice</value>
10 </list>
11 </property>
12 </object>
13
14
15 <object id="consoleLoggingAroundAdvice" type="SpringAOPConfig.ConsoleLoggingAroundAdvice"/>
16
17
18 </objects>
其他接口定义以及类实现同编程实现的方式大致相同,只是在拦截类定义中,实现IMethodInterceptor接口中的Invoke(IMethodInvocation invocation)方法时,只调用了object obj = invocation.Proceed()。
此时的使用方式如下:
1 IApplicationContext context = ContextRegistry.GetContext();
2
3 //command2.Execute();
4
5 object obj = context.GetObject("myServiceObject");
6 ICommand command = (ICommand)context.GetObject("myServiceObject");
7 command.Execute("tyb");
8
9 Console.WriteLine();
10
11 ICommand command2 = (ICommand)context["myServiceObject"];
12 command2.Execute("taoyongbo");
13 //command.Execute();
14 IDictionary dic = context.GetObjectsOfType(typeof(ICommand));
15 foreach (DictionaryEntry item in dic)
16 {
17 Console.WriteLine("key is: {0},value is :{1}", item.Key, item.Value);
18 }
首先过容器获取定义的对象后,通过类型转换后直接使用。
输出结果如下图:
这样就实现了和编程方式一样的效果。问题是上述的方式只是对ICommand中的Execute方法进行了拦截,如果还要对接口中的其他方法进行拦截怎么办呢。?
我们只需在ICommand中定义另一个方法就行了。定义如下:
1 public interface ICommand
2 {
3 object Execute(object context);
4
5 object Execute();
6 }
这样就实现了对接口中所有方法的拦截。
假设我们还要将拦截应用到其他的类上,那么我们该如何实现呢。?新的接口定义与被拦截类的定义和第一个没什么区别,我这里就不在给出了,大家有兴趣可以下载代码了看看就行了。我主要给出配置文件。
1 <objects xmlns="http://www.springframework.net" xmlns:aop="http://www.springframework.net/aop">
2
3 <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject">
4 <property name="Target" >
5 <object id="businessCommand" type="SpringAOPConfig.BusinessCommand"/>
6 </property>
7 <property name="InterceptorNames">
8 <list>
9 <value>consoleLoggingAroundAdvice</value>
10 </list>
11 </property>
12 </object>
13
14 <object id="myServiceObject1" type="Spring.Aop.Framework.ProxyFactoryObject">
15 <property name="Target">
16 <object type="SpringAOPConfig.BusinessTest" />
17 </property>
18 <property name="InterceptorNames">
19 <list>
20 <value>consoleLoggingAroundAdvice</value>
21 </list>
22 </property>
23 </object>
24
25 <object id="consoleLoggingAroundAdvice" type="SpringAOPConfig.ConsoleLoggingAroundAdvice"/>
由配置可以看出,新增的被拦截类的配置通前一个除了名称以外,其他都一样。
使用方式如下:
1 IApplicationContext context = ContextRegistry.GetContext();
2
3
4
5 object obj = context.GetObject("myServiceObject");
6 ICommand command = (ICommand)context.GetObject("myServiceObject");
7 command.Execute("tyb");
8 command.Execute();
9
10 Console.WriteLine();
11
12 ICommand command2 = (ICommand)context["myServiceObject"];
13 command2.Execute("taoyongbo");
14 command2.Execute();
15
16 IDictionary dic = context.GetObjectsOfType(typeof(ICommand));
17 foreach (DictionaryEntry item in dic)
18 {
19 Console.WriteLine("key is: {0},value is :{1}", item.Key, item.Value);
20 }
21
22 //IApplicationContext ctx = ContextRegistry.GetContext();
23 //BusinessCommand command = (BusinessCommand)ctx["myServiceObject"];
24 //command.Execute();
25
26 //IApplicationContext context = ContextRegistry.GetContext();
27 ITest business = (ITest)context["myServiceObject1"];
28
29 business.Fun1();
30
31 Console.Read();
对配置文件的几点总结:
一、在进行拦截时,都定义为由Spring.Aop.Framework.ProxyFactoryObject来处理。
二、名称为Target的属性,定义为目标对象类型。
三、属性为InterceptorNames的值,定义方面(Aspects)。
当然,通过配置方式实现AOP时,也需要将接口声明为public,以及在获取到目标代理对象的时候,只能通过接口来进行类型转换,这点和编程实现AOP是一致的。
2、AOP相关概念
1、方面(Aspects):对横向分布在多个对象中的关注点所作的模块化。在企业应用中事务管理就是一个典型的横切关注点。在Spring.Net中,将方面实现为Advisor或拦截器(Interceptor)。Advisor一般是通知和切入点的组合。拦截器实际上就是指通知。Spring.Net中一般把环绕通知成为拦截器, 而其他类型的通知成为通知。因为环绕通知实现的是AopAlliance.Intercept.IMethodInterceptor接口,而其他类型通知实现的是Spring.AOP命名控件下的其他接口。如ConsoleLoggingAroundAdvice
2、连接点(joinPoint):程序过程中的一个点。如程序中的方法。如:BusinessCommand中的Execute方法。
3、通知(Advice):AOP框架在某个连接点(方法)中所采取的行为。通知有很多类型:如环线通知、前置通知、后置通知、异常通知。如:ConsoleLoggingAroundAdvice中public object Invoke(IMethodInvocation invocation)所执行的代码
4、切入点(PointCut):指通知的应用条件,用于确定某个通知应用到那个连接点(方法)上。AOP框架允许程序员自己定义切入点,也可以在定义中使用正则表达式定义。如:object id="myServiceObject"节点的定义
5、引入(Introduction):向目标对象添加字段或方法的行为。Spring.Net允许为目标对象引入新的接口。如:可以利用引入让任何对象在运行期间实现IAuditable接口,以简化对象状态变化的跟踪过程。如程序中使用:factory.AddAdvice(new ConsoleLoggingAroundAdvice())将通知应用到目标对象上。
6、目标对象(Target Object ):包办连接点的对象。也被称为被通知或被代理对象。如:BusinessCommand
7、AOP代理(AOP Proxy):由AOP框架在将通知应用于目标对象后创建的对象。在Spring.Net中是使用IL代码在运行期创建的动态代理。如:程序中使用的object obj = factory.GetProxy()中。
8、织入(Weaving):将方面进行组装,以创建一个目标对象。它可以在编译期间完成,也可以在运行期完成。Spring在运行时执行织入。织如是一个过程,如程序中ProxyFactory factory = new ProxyFactory(target); factory.AddAdvice(new ConsoleLoggingAroundAdvice()); object obj = factory.GetProxy();的执行。
以上就是我对Spring.Net中AOP的理解。要什么不妥指出,还望不吝赐教!
代码下载:Spring.Net AOP DEMO
参考文档:Spring.Net框架参考手册
http://www.cnblogs.com/GoodHelper/archive/2009/11/13/SpringNet_Aop_Concept.html