2008-01-02 17:33 BeanSoft

今天一看,Blog爬到了第一,又很久没更新了,因此就把这两天正在写的电子书的一部分贴上吧。今天想找找Spring 2.0 AOP的例子,谁料想网上文章一大抄,而且呢,大部分人是直接copy Spring的文档,自己连个demo也不写一种的那个,就见一堆文章说的云里雾里热火朝天的AOP,也没见着几个能跑的例子。最后总算找到了个兄弟的小短文,有个能跑的例子,这才算弄好了1.2和2.0的对比部分。好了,下面是正文(其实尚未完工,先凑合看吧):

10.3 开发Spring 1.2 AOP应用

本节将会给大家展示一个恐怖的例子,FBI特务人员已经介入了您的生活,您所做的一切都在他们的监视之中,包括聊QQ,泡MM,这在现实生活中是真实存在的,为了民众的安全和稳定,对嫌疑犯进行必要的监控是必要的。

注意:本章虽然介绍了多种AOP实现方式,然而,在实际项目中只要使用一种就可以达到目的了(因为Spring的AOP存在多种写法,完全掌握还是挺复杂),其它方式仅供参考,千万不要像孔乙己一样,研究“茴”字的N种写法,这样就脱离了学习技术的初衷了:学习是为了解决问题,不是为了炫耀自己。另外,如果在项目中滥用AOP的后果就是系统的执行效率大大降低,甚至配置不当会导致死循环。记住一个真理:系统越复杂,效率越低,出故障的可能越大。另外一条建议:千万不要用AOP在服务器上记录日志,或者在服务器上打印不必要的调试信息,那样对系统只能有害无益,日志输出是单线程操作,切记。做项目,一般来说是功能越少越好。高手更多的时候只能做出破坏力大,不易维护的垃圾系统。

10.3.1 开发Man对象

这个项目非常简单,仿照上节内容,创建项目并添加Spring开发功能,不同的是添加library的时候要把Spring 2.0 AOP Libraries加入进来。因为Spring 2.0的类库是兼容1.2的,所以这里就用2.0了。项目名为_Spring12AOP。接下来我们要创建一个自由人的对象,他有聊QQ和泡MM这两个方法,还有一个姓名属性。好了,先建立这个类:

/**

  • 具有聊QQ和泡MM两个行为的人对象,还有一个用户名属性。

  • @author BeanSoft

*/

public class Man {

private String name;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public void qq() {

System.out.println(“我在聊QQ”);

}

public void mm() {

System.out.println(“我在泡MM”);

}

}

清单10.6 Man类源码

10.3.2 开发前置通知(Before advice)对象:FBI

首先贴一段Spring文档中关于Before advice的介绍:

前置通知(Before advice__): 在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

说通俗点就是写一个如何处理监视结果的对象,可以把监视结果打印出来以作为必要的时候的呈堂证物,或者派探员立即跟踪,但是这个过程只能在你进行某活动前进行,否则就失去监视的意义了,这个对象更像“诸葛亮”。详细的了解这个类需要学习JDK里面关于反射部分的内容,下面是这个类的代码:

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**

  • 联邦调查局的探员将您的所有行动都记录在案。

  • @author BeanSoft

*/

public class FBI implements MethodBeforeAdvice {

public void before(Method method, Object[] args, Object target)

throws Throwable {

Man man = (Man)target;

System.err.println(“FBI 发现” + man.getName() + “正在进行 “ + method.getName() + “ 活动。”);

}

}

清单10.7 FBI类源码

10.3.3 装配拦截器和Bean

最后要做的,就是创建一个平民对象,注意不是自由人哦,因为平民是随时处于FBI的监视之下的。这个对象本质上是类ProxyFactoryBean的一个示例,这个类位于包org.springframework.aop.framework下,自由人只能活在这个代理工厂类的阴影下了,也就是成了平民了。好了,我们把相应的Spring配置文件代码放给大家:

张三 fbi

清单10.8 Spring AOP 配置文件源码 applicationContext.xml

在这个文件中,定义了两个bean:man_和_fbi,都是普通的类定义。复杂一些的地方在_civilian_这个bean的定义中,它要拦截或者监视的目标(target)是_man_,负责进行处理监视结果的对象(interceptorNames)是_fbi_,具体进行监视工作的对象,就是这个ProxyFactoryBean,它相当于窃听器之类的东西,但是很显然窃听结果是需要人来处理的,那就是FBI。

简单说: man成为了civilian,它被ProxyFactoryBean监控,监控结果交给FBI处理。很显然,如果没有国家,也就没有civilian,更谈不上FBI了。所以,在这里您不能再去找man,因为man在实际生活中是不存在的,所以您只能找civilian,这样您才能感觉到FBI的存在。下面是相关的bean关系图:

clip_image002

图 10.9 Bean关系图

10.3.4 测试和运行

OK,如上节讨论,只有当平民的时候才会被监视,现在我们就可以写一个测试类来感受一下平民生活:

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {

public static void main(String[] args) {

ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);

Man man = (Man) ctx.getBean(“civilian”)

;

man.qq();

man.mm();

}

}

清单10.9 Spring AOP 测试类源码

运行一下,您就可以看到可怕的真相:

log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).

log4j:WARN Please initialize the log4j system properly.

FBI 发现张三正在进行 qq 活动。

FBI 发现张三正在进行 mm 活动。

我在聊QQ

我在泡MM

是不是很恐怖呢?FBI正在监控您的一举一动,并把这些东西都记录在案。现在你应该可以了解AOP的过程了:调用man这个bean的任何一个方法之前,都会事先通知(调用)fbi这个bean并告知相关的调用信息,这些信息包括方法(method),参数(args)以及目标对象(target,这里就是man这个对象)。

如果你把上面的代码改成_ctx.getBean(“man”)_,那么自由人是不会被监控的,所以这时候您就不会看到FBI输出的恐怖信息了。此时的输出如下:

log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).

log4j:WARN Please initialize the log4j system properly.

我在聊QQ

我在泡MM

10.3.5 AOP简介和相关概念

现在我们已经写了一个很简单的Spring AOP 例子,现在就给大家简单介绍一下相关的概念,这些信息来自于Spring的中文文档。

面向切面编程AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。 除了类(classes)以外,AOP提供了 切面。切面对关注点进行模块化,例如横切多个类型和对象的事务管理。 (这些关注点术语通常称作 横切(crosscutting 关注点。)

我们来定义一些重要的AOP概念。这些术语不是Spring特有的。 不幸的是,Spring术语并不是特别的直观;如果Spring使用自己的术语,将会变得更加令人困惑。

l 切面(Aspect: 一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect注解(@AspectJ风格)来实现。

l 连接点(Joinpoint: 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。 在Spring AOP中,一个连接点 总是 代表一个方法的执行。 通过声明一个org.aspectj.lang.JoinPoint类型的参数可以使通知(Advice)的主体部分获得连接点信息。

l 通知(Advice: 在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以_拦截器_做通知模型, 并维护一个以连接点为中心的拦截器链。

l 切入点(Pointcut: 匹配连接点(Joinpoint)的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。 切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

l 引入(Introduction: (也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。 Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。 例如,你可以使用一个引入来使bean实现IsModified接口,以便简化缓存机制。

l 目标对象(Target Object: 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(advised 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied 对象。

l AOP代理(AOP Proxy): AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。 注意:Spring 2.0最新引入的基于模式(schema-based)风格和@AspectJ注解风格的切面声明,对于使用这些风格的用户来说,代理的创建是透明的。

l 织入(Weaving: 把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。 这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。 Spring和其他纯Java AOP框架一样,在运行时完成织入。

通知的类型:

l 前置通知(Before advice: 在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

l 返回后通知(After returning advice: 在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

l 抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。

l 后通知(After (finally) advice: 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

l 环绕通知(Around Advice: 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Jboss,以及EJB 3里面的拦截器(后续章节我们会加以介绍),都只提供环绕通知。

跟AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽量简单的通知类型来实现需要的功能。 例如,如果你只是需要用一个方法的返回值来更新缓存,虽然使用环绕通知也能完成同样的事情, 但是你最好使用After returning通知而不是环绕通知。 用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。 比如,你不需要调用JoinPoint(用于Around Advice)的proceed() 方法,就不会有调用的问题。

在Spring 2.0中,所有的通知参数都是静态类型,因此你可以使用合适的类型(例如一个方法执行后的返回值类型)作为通知的参数而不是使用一个对象数组。

切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得定位通知(advice)可独立于OO层次。 例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。

10.4 开发 Spring 2.0 AOP 应用

Spring 2.0实现了两种方式的AOP配置,一种是基于XML配置文件式的,可以用在JDK1.4上,另一种是基于@AspectJ风格的标注(Annotation)进行AOP开发,可以用在JDK1.5的系统上。本节就对上节的应用进行改写,使用Spring 2.0 AOP的方式来开发。关于Spring AOP的资料和相关概念的详细信息,可以阅读Spring的中文文档。

10.4.1 使用aop 标签实现

AOP

这种方式相对繁琐,所不同的是不需要再定义ProxyFactoryBean的实例,而且自动给相关的bean定义加入AOP功能,不在需要显式的去访问代理过的另外给出名字的bean定义了。

好了,现在让我们新建一个项目,名为_Spring20AOP,并按照10.3节内容设置好必要的类库和必要的文件。那么再这个例子中,Man类的代码不需要做任何修改。需要改的是FBI这个类,而且它也不需要再实现某些接口了,类的源码如下所示:

import org.aspectj.lang.JoinPoint;

/**

  • 联邦调查局的探员将您的所有行动都记录在案。

  • @author BeanSoft

*/

public class FBI {

public void before(JoinPoint point){

Man man = (Man)point.getTarget();

System.err.println(“FBI 发现” + man.getName() + “正在进行 “ +

point.getSignature().getName() + “ 活动。”);

}

}

清单10.10 FBI类源码

注意这个类里面的方法 before(JoinPoint),方法名可以是任意的,可以带一个JoinPoint类型的参数,也可以不带参数直接写成before(),但是这个连接点(JoinPoint)对象带来了所有和这次方法调用有关的信息,包括方法参数,目标对象等等,所以一般要做日志记录的话会带上它。

接下来是测试类的代码,和以前的几乎没有任何不同,只不过现在直接访问的是_man_这个bean。源码如下所示:

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {

public static void main(String[] args) {

ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);

Man man = (Man) ctx.getBean(“man”);

man.qq();

man.mm();

}

}

清单10.11 测试类AOPTest源码

这个类的执行结果和上面的例子是类似的,如下所示:

log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).

log4j:WARN Please initialize the log4j system properly.

FBI 发现张三正在进行 qq 活动。

我在聊QQ

FBI 发现张三正在进行 mm 活动。

我在泡MM

下面再介绍配置文件的写法,先看看完整的配置文件代码:

<beans xmlns=”http://www.springframework.org/schema/beans”

xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”

xmlns:aop=“http://www.springframework.org/schema/aop

xsi:schemaLocation=”

http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd”>

张三

__<aop:config>__

__<aop:pointcut id=“manPointcut”__

__expression=“execution(* Man.*(..))” />__

__<aop:aspect id=“beforeExample” ref=“fbi”>__

__<aop:before pointcut-ref=“manPointcut” method=“before” />__

__</aop:aspect>__

__</aop:config>__

</beans>

清单10.12 AOP XML格式配置文件源码applicationContext.xml

将这个配置文件的代码和清单10.8进行对比,可以看到两个不同:

  1. 配置文件的开头加入了aop命名空间,如代码中粗斜体所示。

  2. 使用aop:config标签来定义AOP,不是使用ProxyFactoryBean来定义一个新的bean。

简要介绍一下这个配置。两个bean的定义都没有什么大的变化,一个是人的对象,另一个则是联邦调查局的探员。而aop:config中定义了所有的AOP设置信息。aop:pointcut定义了一个切入点,id给出了这个切入点的唯一名字,而expression定义了切入点的表达式,那么这个定义到底表示了什么信息呢?它的意思是表示一种场景,即执行(execution)Man对象的所有方法的这种情况,这就是表达式execution(* Man.*(..))的意义所在,Man.*(..)表示Man类的所有方法。接下来呢,需要定义一个切面,用aop:aspect来定义,它的ref属性指定了这个切面所对应的bean定义的id,这里指向_fbi_这个bean类;子标签aop:before则指示了当发生了名为_manPointcut_的切入点(情况)前(用pointcut-ref属性指定,pointcut-ref__=”manPointcut”),就调用名为_before_的方法,这个方法位于aspect里面的引用的那个bean中,这里是_fbi_(即_ref=”fbi”_)。其实Spring执行到这里后,会自动的把这些代码翻译成底层的Bean定义(后台依然会采用ProxyFactoryBean这样的机制),然后把对应的获取bean的操作直接委托给代理类,这就是为什么上文提到的测试类只需要访问原来的man这个bean,对应的拦截类就会被执行的原因。从这里看到Spring 2.0中要定义一个AOP的bean类,仍然是比较复杂的,XML文件和概念都增加了很多,需要读者慢慢来学习和理解。

本节的详细参考资料可以阅读Spring参考文档的6.3. Schema-based AOP support一节。

10.4.2 使用标注(@AspectJ)实现AOP

下面的文档来自于Spring:”@AspectJ”使用了Java 5的注解,可以将切面声明为普通的Java类。 AspectJ 5发布的 AspectJ projecthttp://www.eclipse.org/aspectj)中引入了这种@AspectJ风格。 Spring 2.0 使用了和AspectJ 5一样的注解,使用了AspectJ 提供的一个库来做切点(pointcut)解析和匹配。

为了在Spring配置中使用@AspectJ aspects,你必须首先启用Spring对基于@AspectJ aspects的配置支持,自动代理(autoproxying)基于通知是否来自这些切面。 自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确认通知是否如期进行。

通过在你的Spring的配置文件中引入下列元素来启用Spring对@AspectJ的支持:

也可以通过在你的application context中添加如下定义来启用@AspectJ支持:

你需要在你的应用程序的classpath中引入两个AspectJ库:aspectjweaver.jaraspectjrt.jar。我们这里用的MyEclipse,在添加Spring开发功能时已经自动的加入了这些类库文件,无需手工配置了。

定义切面Aspect:在启用@AspectJ支持的情况下,在application context中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并用于配置在Spring AOP。

定义切入点Pointcut:现在通过在 @AspectJ 注解风格的 AOP 中,一个切入点签名通过一个普通的方法定义来提供,并且切入点表达式使用 @Pointcut 注解来表示(作为切入点签名的方法必须返回 void 类型)。

好了,引用了这么些文档,我们需要介绍这个基于标注的新的AOP项目了,这个项目的名字是Spring2_0AOPAspectJ,如前一节所示加入了Spring核心和AOP类库后,就可以开发了。那么相比较10.4.1 使用aop 标签实现AOP一节,这一个项目的代码仅仅有两个地方要改。首先我们要修改FBI类的源码,加入标注来实现切面和切入点定义,如下所示:

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

/**

  • 联邦调查局的探员将您的所有行动都记录在案。

  • @author BeanSoft

*/

@Aspect

public class FBI {

@Before(“execution(* Man.*(..))”)

public void before(JoinPoint point){

Man man = (Man)point.getTarget();

System.err.println(“FBI 发现” + man.getName() + “正在进行 “ +

point.getSignature().getName() + “ 活动。”);

}

}

清单10.12 加入了Aspect标注的FBI类

这个类中的@Before后面的_“execution(* Man.*(..))”_是切入点所对应的切入点表达式,其意义和上一节的是一致的,仍然表示的是执行 Man 类的所有方法时将触发此方法的执行。

转载请注明:WebLogic Android 博客 » Spring 1.2和2.0的简单AOP例子[整理]