一、AOP基本概念
- 切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect 注解(@AspectJ风格)来实现。
- 连接点(Joinpoint): 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。 在Spring AOP中,一个连接点 总是 代表一个方法的执行。 通过声明一个org.aspectj.lang.JoinPoint类型的参数可以使通知(Advice)的主体部分获得连接点信息。
- 通知(Advice): 在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
- 切入点(Pointcut): 匹配连接点(Joinpoint)的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。 切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
- 引入(Introduction): (也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。 Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。 例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
- 目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(advised) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
- AOP代理(AOP Proxy): AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。 注意:Spring 2.0最新引入的基于模式(schema-based)风格和@AspectJ注解风格的切面声明,对于使用这些风格的用户来说,代理的创建是透明的。
- 织入(Weaving): 把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。 这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。 Spring和其他纯Java AOP框架一样,在运行时完成织入。
二、通知的类型
- 前置通知(Before advice): 在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
- 返回后通知(After returning advice): 在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
- 抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。
- 后通知(After (finally) advice): 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice): 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
三、例子
(1)beans.xml
<?xml version="1.0" encoding="UTF-8"?> <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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <aop:aspectj-autoproxy /> <bean id="person" class="quickstart.aspectj.Person" scope="prototype"> <property name="name" value="Lucy" /> <property name="age" value="12" /> </bean> <bean id="organ" class="quickstart.aspectj.Organization" /> </beans>
(2)java bean
package quickstart.aspectj; public class Person { private String name; private String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String login(String loginInfo) { System.out.println("Person login() " + "LoginInfo:" + loginInfo + this); return this.name + " logining..."; } public void loginThrow() throws Exception { throw new Exception(); } }
package quickstart.aspectj; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class Organization { // 创建切入点 (以下切点注释方法均可以使用) // @Pointcut("within(quickstart.aspectj.Person)") // @Pointcut("execution(* quickstart.aspectj.Person.*(..))") @Pointcut("execution(* quickstart.aspectj.Person.login(..))") public void logPerson() { } // 后通知 (After (finally) advice) 使用切入点 logPerson (在“返回后通知”之前) @After("quickstart.aspectj.Organization.logPerson()") public void logAfter(JoinPoint joinPoint) { System.out.println("logPerson 后通知 方法名:" + joinPoint.getSignature().getName()); } // 前置通知 (Before advice) 使用切入点 logPerson @Before("quickstart.aspectj.Organization.logPerson()") public void logBefore(JoinPoint joinPoint) { System.out.println("logPerson 前置通知 方法名:" + joinPoint.getSignature().getName()); } // 前置通知 (Before advice) 使用切入点 logPerson @Before("quickstart.aspectj.Organization.logPerson() &&" + "args(info)") public void logBeforeArgs(JoinPoint joinPoint, String info) { System.out.println("logPerson 前置通知(有参数) 方法名:" + joinPoint.getSignature().getName() + " 参数: " + info); } // 返回后通知1 (After returning advice) 使用切入点 logPerson (在“后通知”之后) @AfterReturning("quickstart.aspectj.Organization.logPerson()") public void logAfterReturning1(JoinPoint joinPoint) { System.out.println("logPerson 返回后通知1 方法名:" + joinPoint.getSignature().getName() + " return: "); } // 返回后通知2 (After returning advice) 使用切入点 logPerson (在“后通知”之后) @AfterReturning(pointcut = "quickstart.aspectj.Organization.logPerson()", returning = "retVal") public void logAfterReturning2(JoinPoint joinPoint, Object retVal) { System.out.println("logPerson 返回后通知2 方法名:" + joinPoint.getSignature().getName() + " return: " + retVal.toString()); } // 环绕通知 (Around Advice) 使用切入点 logPerson (在“前通知”之后) @Around("quickstart.aspectj.Organization.logPerson()") public Object logAround(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch System.out.println("logPerson 环绕通知 start stopwatch 方法名:" + pjp.getSignature().getName()); // 执行切入点 Object retVal = pjp.proceed(); // stop stopwatch System.out.println("logPerson 环绕通知 stop stopwatch 方法名:" + pjp.getSignature().getName() + " return: " + retVal.toString()); return retVal; } // 创建切入点 @Pointcut("execution(* quickstart.aspectj.Person.loginThrow())") public void logThrowsPerson() { } // 抛出后通知(After throwing advice) @AfterThrowing("quickstart.aspectj.Organization.logThrowsPerson()") public void afterThrowAdvice() { System.out.println("抛出后通知(After throwing advice)"); } }
四、测试
package logcd.test; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import quickstart.aspectj.Person; public class AspectTest { private static ApplicationContext appContext; @BeforeClass public static void Init(){ appContext = new ClassPathXmlApplicationContext("beans.xml"); } @Test public void login(){ Person person = (Person)appContext.getBean("person"); person.login("Login Info Test"); } }