一、AOP概念和术语
1、切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。在Spring AOP中,切面可以使用基于模式或基于@Aspect注解的方式来实现。
2、连接点(Join Point):在程序执行过程中某个特定的点,比如方法调用的时候或异常处理的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
3、通知(Advice):在切面的某个特定的连接点上执行的动作。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
4、切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行。
5、引入(Introduction):用来给一个类型声明额外的方法或属性。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。
6、目标对象(Target Object):被一个或多个切面所通知的对象。这个对象永远是一个被代理的对象。
7、AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约。
8、织入(Weaving):把切面连接到其他应用程序类型或者对象上,并创建一个被通知的对象。
二、AOP基本思想
1、认识AOP
使用AOP,就不用在业务逻辑中实现与业务功能关系不大的代码,从而降低了耦合性,达到易于维护和重用的目的。
一个应用程序分为核心关注点和横切关注点。核心关注点和具体应用的功能相关,而横切关注点存在与整个系统的范围内。在AOP里,每个关注点的实现并不知道是否有其他关注点关注它,组合的流向是从横切关注点到主关注点。
2、AOP与Java代理机制
AOP是一种思想,它和具体的实现技术无关,任何一种符合AOP思想的技术实现,都可以看作是AOP的实现。实际上Spring的AOP是建立在Java的代理机制之上的。
3、用途
日志记录、性能统计、安全控制、权限管理、事务处理、异常处理、资源池管理等。
三、AOP的3个关键概念
1、切入点(pointcut)
(1)Join Point(连接点):Join Point指的是程序运行中的某个阶段点,如某个方法调用、异常抛出等。
(2)Pointcut是Join Point的集合,它是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发。在Pointcut接口中,主要包含了两个接口:ClassFilter和MethodMatcher,有利于代码的重用。
Pointcut源代码:
public interface Pointcut{
ClassFilter getClassFilter(); //用来将切入点限定在给定的目标类中
MethodMatcher getMethodMatcher(); //用来判断切入点是否匹配目标类给定的方法
Pointcut TRUE=TruePointcut.INSTANCE;
}
2、通知(Advice)
Advice是某个连接点所采用的处理逻辑,也就是向连接点注入的代码。
3、Advisor
Advisor是Pointcut和Advice的配置器,它包括Pointcut和Advice,是将Advice注入程序中Pointcut位置的代码。在Spring中,主要通过XML的方式来配置Pointcut和Advice。
四、Spring的3种切入点(Pointcut)实现
1、静态切入点
静态切入点只限于给定的方法和目标类,而不考虑方法的参数。Spring在调用静态切入点时只在第一次的时候计算静态切入点的位置,然后把它缓存起来,以后就不需要再进行计算。使用org.springframework.aop.support.RegexpMethodPointcut可以实现静态切入点,RegexpMethodPointcut是一个通用的正则表达式切入点,它是通过Kakarta ORO来实现的。
使用RegexpMethodPointcut的一个示例:
<bean id="settersAndAbsquatulatPointcut" class="org.springframework.aop.support.RegexpMethodPointcut" >
<property name="patterns" >
<!--设定切入点-->
<list>
<value>.*save.*</value> //表示所有以save开头的方法都是切入点
<value>.*do.*</value> //表示所有以do开头的方法都是切入点
</list>
</property>
</bean>
2、动态切入点
动态切入点不仅限定于给定的方法和类,还可以指定方法的参数。因为参数的变化性,所以动态切入点不能缓存,需要每次调用的时候都进行计算。当切入点需要在执行时根据参数值来调用通知时,就需要使用动态切入点。Spring提供了一个内建的动态切入点:控制流切入点,此切入点匹配基于当前线程的调用堆栈,开发人员只有在当前线程执行时找到特定的类和特定的方法才能返回true。
3、自定义切入点
因为Spring中的切入点是Java类,而不是语言特性,因此可以定义自定义切入点。
五、Spring的通知
1、Interception Around通知(环绕通知)
Interception Around通知会在Join Point的前后执行,是Spring中最基本的通知类型,它包围一个连接点的通知,如方法调用,它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。实现Interception Around通知的类需要实现接口MethodInterception。
示例:
public class LogInterception implements MethodInterception{
public Object invoke(MethodInvocation invocation) throws Throwable{
System.out.println("开始审核数据...");
Object rval=invocation.proceed();
System.out.println("审核数据结束...");
return rval;
}
}
2、Before通知(前置通知)
Before通知只在Join Point前执行,不能阻止连接点之前执行的流程,除非它抛出一个异常。实现Before通知的类需要实现接口MethodBeforeAdvice。
示例:
public class LogBeforeAdvice implements MethodBeforeAdvice{
public void before(Method m,Object[] args,Object target) throws Throwable{
System.out.println("开始审核数据...");
}
}
3、After Returning通知(后置通知)
After Returning通知只在Join Point后执行,在某连接点正常完成后执行的通知。实现After Returning通知的类需要实现接口AfterReturningAdvice。
示例:
public class LogAfterAdvice implements AfterReturningAdvice{
public void afterReturning(Method m,Object[] args,Object target) throws Throwable{
System.out.println("审核数据结束...");
}
}
4、Throw通知
Throw通知只在Join Point抛出异常时执行。实现Throw通知的类需要实现接口ThrowsAdvice。
示例:
public class LogThrowAdvice implements ThrowsAdvice{
public void afterThrowing(RemoteException ex) throws Throwable{
System.out.println("审核数据抛出异常,请检查..."+ex);
}
}
5、Introduction通知
Introduction通知只在Join Point调用完毕后执行。实现Introduction通知的类需要实现接口IntroductionAdvisor和接口IntroductionInterception。六、Spring中AOP的两种代理方式
1、Java动态代理
Spring默认使用的是Java动态代理,代理的是接口。
2、CGLIB代理
(1)Spring中提供了对CGLIB代理的支持,主要改变就是设定ProxyFactoryBean的proxyTargetClass属性,将该属性值设定为true即可。(2)使用CGLIB代理的好处就是不用在像使用Java动态代理那样去实现特定的接口,一个普通的Java类就可以了。
(3)如果一个业务对象并没有实现一个接口,默认使用CGLIB代理。
(4)可强制使用CGLIB代理,这种情况下,可能需要通知一个没有在接口中声明的方法,或者需要传入一个代理对象给方法作为具体类型。
七、Spring中的自动代理
要使用Spring中的动态代理,org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator包是必须的。
八、Spring AOP的功能和目标
1、Spring AOP使用纯Java实现。它不需要专门的编译过程。
2、Spring目前仅支持使用方法调用作为连接点(在Spring bean上通知方法的执行)。虽然可以在不影响到Spring AOP核心API的情况下加入对成员变量拦截器支持,但Spring并没有实现成员变量拦截器。3、Spring的AOP功能通常都和Spring IoC容器一起使用。切面使用普通的bean定义语法来配置。
九、@AspectJ支持
1、启用@AspectJ支持
为了在Spring配置中使用@AspectJ切面,首先必须启用Spring对@AspectJ切面配置的支持,并确保自动代理的bean是否能被这些切面通知。自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确保通知在需要时执行。
在Spring的配置中启用Spring对@AspectJ的支持:
<aop:aspectj-autoproxy />
2、声明一个切面
(1)启用@AspectJ支持后,在application context中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并用于配置Spring AOP。
(2)切面(用@Aspect注解的类)和其他类一样有方法和字段定义。他们也可能包括切入点,通知和引入(inter-type)声明。
(3)通知切面:在Spring AOP中,拥有切面的类本身不可能是其它切面中通知的目标。一个类上面的@Aspect注解标识它为一个切面,并且从自动代理中排除它。3、声明一个切入点
(1)Spring AOP只支持Spring bean的方法执行连接点。所以可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。在@AspectJ注解风格的AOP中,一个切入点签名通过一个普通的方法定义来提供,并且切入点表达式使用@Pointcut注解来表示(作为切入点签名的方法必须返回void 类型)。
定义一个切入点"anyOldTransfer",这个切入点将匹配任何名为 "transfer" 的方法的执行:
@Pointcut("execution(* transfer(..))")
private void anyOldTransfer() { }
(2)切入点指示符(PCD)的支持
Spring AOP支持在切入点表达式中使用如下的AspectJ切入点指示符:
execution:匹配方法执行的连接点,这是你将会用到的Spring的最主要的切入点指示符。
within:限定匹配特定类型的连接点(在使用Spring AOP的时候,在匹配的类型中定义的方法的执行)。
this:限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。
target: 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中目标对象(被代理的应用对象)是指定类型的实例。
args:限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。
@target: 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中正执行对象的类持有指定类型的注解。
@args: 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中实际传入参数的运行时类型持有指定类型的注解。
@within:限定匹配特定的连接点,其中连接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。
@annotation:限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中连接点的主题持有指定的注解。
Spring AOP还提供了一个名为'bean'的PCD。这个PCD允许限定匹配连接点到一个特定名称的Spring bean,或者到一个特定名称Spring bean的集合(当使用通配符时)。
“bean” PCD仅仅 被Spring AOP支持而不是AspectJ,这是Spring对AspectJ中定义的标准PCD的一个特定扩展。“bean” PCD不仅仅可以在类型级别(被限制在基于织入AOP上)上操作而还可以在实例级别(基于Spring bean的概念)上操作。
(3)组合切入点表达式
切入点表达式可以使用&、||和!来组合,还可以通过名字来指向切入点表达式。
三种切入点表达式:
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() { } //在一个方法执行连接点代表了任意public方法的执行时匹配
@Pointcut("within(com.xyz.someapp.trading..*")
private void inTrading() { } //在一个代表了在交易模块中的任意的方法执行时匹配
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() { } //在一个代表了在交易模块中的任意的公共方法执行时匹配
Spring AOP 用户可能会经常使用 execution切入点指示符。
执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
除了返回类型模式(ret-type-pattern)、名字模式和参数模式以外, 所有的部分都是可选的。返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 使用最频繁的返回类型模式是*,它代表了匹配任意的返回类型。 一个全限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。 可以使用*通配符作为所有或者部分命名模式。 参数模式:()匹配了一个不接受任何参数的方法, 而(..)匹配了一个接受任意数量参数的方法(零或者更多)。 模式(*)匹配了一个接受一个任何类型的参数的方法。 模式(*,String)匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型。
切入点表达式示例:
任意公共方法的执行:execution(public * *(..))
任何一个名字以“set”开始的方法的执行:execution(* set*(..))
AccountService接口定义的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))
在service包中定义的任意方法的执行:execution(* com.xyz.service.*.*(..))
在service包或其子包中定义的任意方法的执行:execution(* com.xyz.service..*.*(..))
在service包中的任意连接点(在Spring AOP中只是方法执行):within(com.xyz.service.*)
在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):within(com.xyz.service..*)
实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):this(com.xyz.service.AccountService)
实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):target(com.xyz.service.AccountService)
任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行):args(java.io.Serializable)
目标对象中有一个 @Transactional 注解的任意连接点(在Spring AOP中只是方法执行):@target(org.springframework.transaction.annotation.Transactional)
任何一个目标对象声明的类型有一个 @Transactional 注解的连接点(在Spring AOP中只是方法执行):@within(org.springframework.transaction.annotation.Transactional)
任何一个执行的方法有一个 @Transactional 注解的连接点(在Spring AOP中只是方法执行):@annotation(org.springframework.transaction.annotation.Transactional)
任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行):@args(com.xyz.security.Classified)
任何一个在名为'tradeService'的Spring bean之上的连接点(在Spring AOP中只是方法执行):bean(tradeService)
任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行):bean(*Service)
4、声明通知
通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。
(1)一个切面里使用 @Before 注解声明前置通知。(2)后置通知通常在一个匹配的方法返回的时候执行。使用 @AfterReturning 注解来声明。
(3)抛出异常通知在一个方法抛出异常后执行。使用@AfterThrowing注解来声明:
(4)不论一个方法是如何结束的,最终通知都会运行。使用@After 注解来声明。最终通知必须准备处理正常返回和异常返回两种情况。通常用它来释放资源。
(5)环绕通知使用@Around注解来声明。通知的第一个参数必须是 ProceedingJoinPoint类型。在通知体内,调用 ProceedingJoinPoint的proceed()方法会导致 后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个 Object[]对象-该数组中的值将被作为方法执行时的参数。
(6)任何通知方法可以将第一个参数定义为org.aspectj.lang.JoinPoint类型 (环绕通知需要定义第一个参数为ProceedingJoinPoint类型, 它是 JoinPoint 的一个子类)。JoinPoint 接口提供了一系列有用的方法,比如 getArgs()(返回方法参数)、 getThis()(返回代理对象)、getTarget()(返回目标)、 getSignature()(返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息)。
十、AOP实例
1、目录结构
2、定义一个接口Say
package com.aop;
public interface Say {
public void sayHello() ;
}
3、定义一个People类,实现接口Say
package com.aop;
public class People implements Say{
public void sayHello() {
System.out.println("hello");
}
}
4、定义两个通知类(前置通知与后置通知)
(1)前置通知实现MethodBeforeAdvice
package com.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class BeforeMethod implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println("前置通知");
}
}
(2)后置通知实现AfterReturningAdvice
package com.aop;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class AfterMethod implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("后置通知");
}
}
5、切点实现
package com.aop;
import java.lang.reflect.Method;
import org.springframework.aop.support.NameMatchMethodPointcut;
public class MyPointCut extends NameMatchMethodPointcut {
public boolean matches(Method method, @SuppressWarnings("rawtypes") Class targetClass) {
return true;
}
}
6、测试类
package com.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/aop/beans.xml");
Say people = (Say) context.getBean("proxyFactoryBean");
people.sayHello();
}
}
7、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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
default-autowire="byName">
<!-- 业务类 -->
<bean id="people" class="com.aop.People"></bean>
<!-- 通知类 -->
<bean id="beforeMethod" class="com.aop.BeforeMethod"></bean>
<bean id="afterMethod" class="com.aop.AfterMethod"></bean>
<!-- 切点 -->
<bean id="myPointCut" class="com.aop.MyPointCut"></bean>
<!-- Advisor -->
<bean id="myAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<ref bean="myPointCut"/>
</property>
<property name="advice">
<ref bean="beforeMethod"/>
</property>
</bean>
<!-- ProxyFactoryBean -->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.aop.Say</value>
</property>
<property name="target">
<ref bean="people"/>
</property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>afterMethod</value>
</list>
</property>
</bean>
</beans>
8、运行结果