Spring 是由多个部分组成,包括AOP、DAO、Conetxt、Web、MVC,并且他们都已IoC 容器为基础。Spring 这么多功能都是由于其IoC 容器的特性,实现了对多种框架的集成,但 AOP 是个例外,它不是对某个框架的集成,而是提供了面向方面编程的功能,你可以自由选择是否使用AOP。AOP 提供了强大的中间件解决方案,这使得IoC 容器更加完善。
我们可以把AOP 看做是 Sping 的一种增强,它使得 Spring 可以不需要 EJB 就能够提供声明式事务管理,或者也可以使用Spring AOP 框架的全部功能来实现自己定义的切面。
AOP将应用系统分为两部分,核心业务逻辑(Core business concerns)及横向的通用逻辑,也就是所谓的方面Crosscutting enterprise concerns,例如,所有大中型应用都要涉及到的持久化管理(Persistent)、事务管理(Transaction Management)、安全管理(Security)、日志管理(Logging)和调试管理(Debugging)等。
Spring AOP的核心设计思想:代理模式
AOP常用专业术语:
① 方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的Advisor或拦截器实现。
② 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
③ 通知(Advice):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。
④ 切入点(Pointcut):指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点,例如,使用正则表达式。
⑤ 引入(Introduction):添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。
⑥ 目标对象(Target Object):包含连接点的对象,也被称作被通知或被代理对象。
⑦ AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
⑧ 编织(Weaving):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
各种通知类型包括:
① Around通知:包围一个连接点的通知,如方法调用。这是最强大的通知。Aroud通知在方法调用前后完成自定义的行为,它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出异常来短路执行。
② Before通知:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
③ Throws通知:在方法抛出异常时执行的通知。Spring提供强制类型的Throws通知,因此你可以书写代码捕获感兴趣的异常(和它的子类),不需要从Throwable或Exception强制类型转换。
④ After returning通知:在连接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。
Around通知是最通用的通知类型。大部分基于拦截的AOP框架(如Nanning和Jboss 4)只提供Around通知。
Spring AOP 开发的两种方式:
① 基于@Aspect 注释符。适合在 Java 5 及以上的平台。
Spring 2.0 及以上版本可以无缝地整合Spring AOP、IoC 和 AspectJ,使得所有的AOP 应用完全融入基于 Spring 的应用体系。
② 基于Schema 模式。对 不支持Java 5 的应用开发,可以使用这种模式。
下面我们首先讲第一种方式:基于@Aspect 注释符 的应用
Spring 版本 spring-framework-3.0.5.RELEASE
需要的包:
org.springframework.core-3.0.5.RELEASE.jar
org.springframework.beans-3.0.5.RELEASE.jar
org.springframework.context-3.0.5.RELEASE.jar
org.springframework.expression-3.0.5.RELEASE.jar
org.springframework.asm-3.0.5.RELEASE.jar
org.springframework.aop-3.0.5.RELEASE.jar
org.springframework.aspects-3.0.5.RELEASE.jar
AspectJ 版本 aspectj-1.6.10
下载地址:http://www.eclipse.org/aspectj/downloads.php
需要的包:
aspectjweaver.jar
aspectjrt.jar
另外还需要的包:
aopalliance-1.0.jar 下载地址:http://sourceforge.net/projects/aopalliance/files/aopalliance/1.0/
cglib-nodep-2.1_2.jar 下载地址:http://www.findjar.com/jar/cglib/jars/cglib-nodep-2.1_3.jar.html
commons-logging-1.0.4.jar
log4j-1.2.12.jar
㈠ 定义方面类(Aspect)
在 applicationContext.xml 中启用@Aspect 注释
为了在Spring 配置中使用@Aspect 方面的注释,你必须首先启用 Spring 对于 @Aspect 的配置支持,自动代理基于该注释符来决定是否被横切。
自动代理 是指Spring 会判断一个Bean 是否使用了一个或多个切面通知,并据此生成相应的代理以拦截其方法调用,并确认通知是否如期进行。
如果 applicationContext.xml 的格式是DTD 的,可以在 applicationContext.xml 中添加如下的配置来启用@Aspect 注释 进行动态代理
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
声明一个切面类:
package com.demo.spring.aop;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class MyAspect {
}
然后在 applicationContext.xml 中添加如下:
<bean id="myAspect" class="com.demo.spring.aop.MyAspect"/>
㈡ 定义切入点函数(Pointcut)
Spring 中切入点知识
① AspectJ 指定切入点
● execution:匹配方法执行的连接点,这是Spring 主要的切入点指定者。
● within:限定匹配特定类型的连接点(在使用Spring AOP 的时候,在匹配的类型中定义方法的执行)。
● this:限定匹配特定类型的连接点(在使用Spring AOP 的时候,在匹配的类型中定义方法的执行),其中 Bean Refrence(Spring Aop 代理) 是指定代理类型的实例。
● target:限定匹配特定类型的连接点(在使用Spring AOP 的时候,方法被执行),其中目标对象(被代理 Application Object) 是指定类型的实例。
● args:限定匹配特定类型的连接点(在使用Spring AOP 的时候,方法被执行),其中参数是指定类型的实例。
● @target:限定匹配特定类型的连接点(在使用Spring AOP 的时候,方法被执行),其中执行的对象的类已经有指定类型的注解。
● @args:限定匹配特定类型的连接点(在使用Spring AOP 的时候,方法被执行),其中实际传入参数在运行时,类型有指定类型的注解。
● @within:限定匹配特定类型的连接点,其中连接点所在类型已指定注解(在使用Spring AOP 的时候,所执行方法的所在类型已经注解)。
● @annotation:限定匹配特定类型的连接点(在使用Spring AOP 的时候,方法被执行),其中连接点的主题有某种给定的注解。
② 切入点表达式的匹配模式
execution( 【修饰符】 返回类型 【全类名】 方法名(参数类型)【异常类型】)
其中 修饰符、全类名、异常类型为可选
一些常见的切入点的例子
execution(public * * (. .)) 任意公共方法被执行时,执行切入点函数。
execution( * set* (. .)) 任何以一个“set”开始的方法被执行时,执行切入点函数。
execution( * com.demo.service.AccountService.* (. .)) 当接口AccountService 中的任意方法被执行时,执行切入点函数。
execution( * com.demo.service.*.* (. .)) 当service 包中的任意方法被执行时,执行切入点函数。
within(com.demo.service.*) 在service 包里的任意连接点。
within(com.demo.service. .*) 在service 包或子包的任意连接点。
this(com.demo.service.AccountService) 实现了AccountService 接口的代理对象的任意连接点。
target(com.demo.service.AccountService) 实现了AccountService 接口的目标对象的任意连接点。
args(java.io.Serializable) 任何一个只接受一个参数,且在运行时传入参数实现了 Serializable 接口的连接点。
★ 定义切入点函数
@Pointcut("execution(* execute(..)) && target(com.demo.spring.test.HelloWorld)" )
public void pointcut(){
}
或
@Pointcut("execution(* execute(..)) && target(com.demo.spring.test.HelloWorld) && args(arg, . .)" )
public void pointcut(String arg){
}
★ 定义通知函数
前置通知 (Before Advice)
@Before("pointcut() && args(arg, . .)")
public void beforeExecute(JoinPoint thisJoinPoint,Object arg ){
System.out.println("连接点类型:" + thisJoinPoint.getKind());
System.out.println("代理类名:" + thisJoinPoint.getSourceLocation().getClass().getName());
System.out.println("目标类名:" + thisJoinPoint.getTarget().getClass().getName());
System.out.println("目标函数名:" + thisJoinPoint.getSignature().getName());
System.out.println("参数个数:" + thisJoinPoint.getArgs().length);
System.out.println("参数值:" + arg);
System.out.println("Before Advice!");
}
返回后通知(After returning Advice)
@AfterReturning(pointcut = "pointcut()", returning = "retVal")
public void afterExecuet(JoinPoint thisJoinPoint, Object retVal){
System.out.println("return " + retVal);
}
抛出异常后通知(After Throwing Advice)
@AfterThrowing(pointcut = "pointcut()", throwing = "ex")
public void afterThrowing(JoinPoint thisJointPoint, Exception ex){
System.out.println("Throwing "+ex);
}
后通知 (After Advice)
@After("pointcut()")
public void afterExecute(JoinPoint thisJoinPoint){
System.out.print("After Advice!");
}
环绕通知(Around Advice)
@Around("pointcut()")
public Object userOperate(ProceedingJoinPoint thisJoinPoint) throws Throwable{
System.out.println("Around Advice!");
return thisJoinPoint.proceed();
}
上面的每个通知函数都有一个
JoinPoint 类型 和 ProceedingJoinPoint 类型的参数,通过该参数可以获得目标类的信息。
㈢ 建立目标函数
public class HelloWorld {
protected String message;
public String getMessage(){
return message;
}
public void setMessage(String message){
this.message = message;
}
public void execute(){
System.out.println("Hello "+getMessage()+"!");
}
}
然后在 applicationContext.xml 中添加如下:
<bean id="helloWorld" class="com.demo.spring.test.HelloWorld">
<property name="message">
<value>World</value>
</property>
</bean>
注:环绕通知与前置通知和后置通知的区别:
环绕通知:
1)环绕通知 目标方法的调用由环绕通知决定,使得通知有机会 即 在目标方法执行之前 又 在执行之后 运行。它可以决定目标方法什么时候执行,如何执行,甚至是否 执行。
2)环绕通知 可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。目标方法的返回值 就是 环绕通知的返回值。proceed() 方法可以在通知体内调用一次、多次 或根本不用调用。
如果环绕通知体内的返回类型与目标方法的返回类型一致为thisJoinPoint.proceed();则在目标方法前执行,如果返回类型与目标方法的返回类型不一致,则在目标方法后执行,如果返回为null,则不表方法不执行。
前置通知和后置通知:
1)前置和后置通知是不能决定 目标方法什么时候执行,如何执行,甚至是否 执行,他们只是在目标方法调用前后执行通知而已,即目标方法肯定是要执行的。
2)而后置方法是无法办到 返回一个与目标对象完全不同的返回值,因为他是在目标方法返回值后调用。
#########################################################################################################
当Sturts2 与 Spring 整合开发时,用Spring AOP 来实现 Struts2 的事务管理时,应该注意的问题:
spring对AOP的支持
1、如果目标对象实现了接口,默认会采用JDK的动态代理机制实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现接口,必须使用CGLIB生成代理
4、如果代理的目标对象有多个,有实现接口的,有没有实现接口的,spring会自动在CGLIB和JDK动态代理之间切换
.如何强制使用CGLIB生成代理?
① 添加包文件 cglib-nodep-2.1_2.jar
② 在applicationContext.xml 中添加红色部分:
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" >
<property name="proxyTargetClass" value="true/">
</bean>
如果是Schema 方式添加如下:
<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK代理和CGLIB代理的区别
* JDK的代理对象 必须实现一个或多个接口。
* CGLIB代理的是 没有实现接口的对象,是针对类实现代理的,主要对指定的类生成一个子类,并覆盖其中的方法。
因为是继承,所以不能使用final来修饰类或方法。