AspectJ 简介
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
这是百度百科中对 AspectJ 的介绍。
通知类型
在 spring 中,定义了四种通知类型,前置通知、后置通知、环绕通知、异常通知,在 AspectJ 中,多了一种,最终通知,无论程序执行是否正常,该通知都会被执行,类似于 Java 中的 try...catch...finally 中的 finally 代码块的执行。其中每一种通知类型都对应一个注解。
- 前置通知 @Before
- 后置通知 @AfterReturning
- 环绕通知 @Around
- 异常通知 @AfterThrowing
- 最终通知 @After
切入点表达式
在 AspectJ 中定义了专门用来匹配目标方法的切入点表达式,原型如下:
execution([访问权限类型] 返回值类型 [全限定性类名] 方法名称(参数名称) [抛出异常类型]) ,可以看到,execution() 括号中的就是方法签名,其中 [] 中的是可以省略的。并且在切入点表达式中,支持通配符匹配。
- * 0个或多个字符
- .. 用在方法参数中,表示任意多个参数,用在包名中,表示当前包及其子包
- + 用在类名后,表示当前类及其子类,用在接口后,表示当前接口及其实现类
spring 引入 AspectJ
我们知道,AOP 是一种编程思想,可以有多种实现,前面的文章 spring 中自动代理生成器的实现 介绍了 spring 对于 AOP 的实现。 AspectJ 也实现了 AOP,通过切入点表达式,可以更方便的匹配到要代理的方法,所以 spring 框架也引入了这种实现。具体的实现方式,共有两种,分别是基于注解的 AOP 实现和基于 xml 的 AOP 实现,各对应一个代理生成器。
- 基于注解:AnnotationAwareAspectJAutoProxyCreator
- 基于 xml:AspectJAwareAdvisorAutoProxyCreator
下面先来看看 spring 中 AspectJ 基于注解的 AOP 实现。
基于注解的 AOP 实现
先来定义业务类:
public class UserServiceImpl implements UserService {@Overridepublic void doSome() {System.out.println("do some");}@Overridepublic void doOther() {System.out.println("do other");}@Overridepublic String doThird() {System.out.println("do third");return "aaa";}
}
需要一个切面类,如下:
@Aspect
public class MyAspect {@Before("execution(* *..UserServiceImpl.doSome())")public void beforeSome() {System.out.println("前置增强");}@AfterReturning("execution(* *..UserServiceImpl.doThird())")public void afterReturning() {System.out.println("后置增强");}
}
注册:
<!--配置目标对象-->
<bean id="myUserService" class="com.icheetor.aop.service.impl.UserServiceImpl"/>
<!--配置切面-->
<bean id="myAspect" class="com.icheetor.aop.aspect.MyAspect"/>
<!--配置自动代理-->
<!--底层由AnnotationAwareAspectJAutoProxyCreator实现-->
<aop:aspectj-autoproxy/>
这样,就实现了基于注解的 AOP。
基于 xml 配置的 AOP 实现
AspectJ 除了提供注解方式的 AOP 实现,还提供了 xml 配置方式的实现。
业务类:
public class UserServiceImpl implements UserService {@Overridepublic void doSome() {System.out.println("do some");}@Overridepublic void doOther() {System.out.println("do other");}@Overridepublic String doThird(String bb) {System.out.println("do third");return bb + "aaa";}
}
切面类:
public class MyAspect {public void beforeSome() {System.out.println("前置增强");}public void afterReturning() {System.out.println("后置增强");}
}
此处为丰富应用,还定义了一个 Advice,如下:
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println("执行前置通知:" + target.getClass().getName() + "#" + method.getName());}
}
下面来看下在 xml 中的配置:
<!--配置目标对象-->
<bean id="myUserService" class="com.icheetor.aop.service.impl.UserServiceImpl"/>
<bean id="myMethodBeforeAdvice" class="com.icheetor.aop.advice.MyMethodBeforeAdvice"/>
<!--配置切面-->
<bean id="myAspect" class="com.icheetor.aop.aspect.MyAspect"/><!--配置自动代理-->
<!--底层由AspectJAwareAdvisorAutoProxyCreator实现-->
<aop:config proxy-target-class="false" expose-proxy="false"><aop:pointcut id="doSomePointcut" expression="execution(* *..UserServiceImpl.doSome())"/><aop:pointcut id="doThirdAfterReturning" expression="execution(* *..UserServiceImpl.doThird(..))"/><!--advisor 标签需配置在 aspect 之前,由标签顺序而定,详见 org.springframework.aop.config.spring-aop.xsd--><aop:advisor id="my_advisor" advice-ref="myMethodBeforeAdvice" pointcut-ref="doSomePointcut" order="2"/><aop:aspect id="my_aspect" ref="myAspect" order="1"><aop:before method="beforeSome" pointcut-ref="doSomePointcut"/><aop:after-returning method="afterReturning" pointcut-ref="doThirdAfterReturning"/></aop:aspect>
</aop:config>
通过 <aop:pointcut> 标签来定义切入点。<aop:aspect> 定义具体的织入规则,其子标签定义各种通知类型,每一种通知类型对应一种标签。
- 前置通知 <aop:before>
- 后置通知 <aop:after-returning>
- 环绕通知 <aop:around>
- 异常通知 <aop:after-throwing>
- 最终通知 <aop:after>
通知标签中 method 用来表示匹配到后,执行切面类中的哪个方法去增强,pointcut-ref 用来指定选择哪个切入点去进行匹配。
<aop:config> 标签下还支持 advisor 的配置,advice-ref 指定引用的 advice,即增强方法,pointcut-ref 指定匹配的切入点。
当 <aop:config> 下子标签 <aop:advisor> 和 <aop:aspect> 同时存在时,<aop:advisor> 需位于 <aop:aspect> 之前,这是由 org.springframework.aop.config.spring-aop.xsd 中标签顺序指定的。
还有个 order 属性,用来决定封装拦截器链时的执行顺序,越小的会先执行,如上面示例所示,两个前置通知,myAspect 中的 beforeSome 会先执行,之后才会执行 myMethodBeforeAdvice 中重写的 before 方法。