Spring框架:利用 XML文档配置 Aspect 织入
1 Java 示例代码
1.1 案例介绍
以下展示一个简单的方法增强示例 demo。案例中主要的类是 Target 类和 MyAspect 类。其中,Target 类中的方法应当定义核心业务代码;MyAspect 类中定义 AOP 通知方法。本案例期望引导 Spring 框架实现基于 JDK 的代理,因此还要为 Target 类定义一个接口并实现。最后定义一个测试类作为主方法。
定义 TargetInterface.java 接口及 Target.java 类:
public interface TargetInterface {
void save();
}
public class Target implements TargetInterface {
public void save() {
System.out.println("Target: save running...");
}
}
定义通知类 MyAspect.java,内部分别定义了一个前置增强方法和后置增强方法。
public class MyAdvice {
public void before() {
System.out.println("MyAspect: 前置增强.....");
}
public void afterReturning() {
System.out.println("MyAspect: 后置增强...");
}
}
定义测试类:
public class XMLAop {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
TargetInterface target = app.getBean(TargetInterface.class);
target.save();
}
}
运行结果:
Target: save running...
1.2 利用 XML 配置 IOC 依赖
<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.xsd
">
<!--配置目标对象-->
<bean id="target" class="com.abe.aop.xml.Target"/>
</beans>
2 配置织入关系
2.1 导入 AOP 约束
<beans 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
">
</beans>
2.2 配置 AOP 织入动作
以下直接给出使用 XML 文档 Aspect 配置方式。在后面的小节将逐个要点加以解释和扩展。
XML Aspect 配置示例:
<!--配置切面对象-->
<bean id="myAspect" class="com.abe.aop.xml.MyAdvice"/>
<!--配置织入动作:告诉Spring,哪些方法(切点)需要做哪些(前置、后置...)增强-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut="execution(public void Target.save())"/>
<aop:after-returning method="afterReturning" pointcut="execution(* TargetInterface.*(..))"/>
</aop:aspect>
</aop:config>
配置 Aspect,首先需要将 MyAdvice 类注入到 IOC 容器中。
<!--配置切面对象-->
<bean id="myAspect" class="com.abe.aop.xml.MyAdvice"/>
然后利用 <aop:config>
标签配置 AOP 织入动作,在 <aop:config>
标签内嵌套 <aop:aspect>
标签表示在该标签内声明切面。<aop:aspect>
标签内需要指定一个 ref
属性,标识执行织入动作的切面类 id,也就是 MyAdvice 类在配置 IOC 依赖时指定的 id。
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--声明切面1-->
<!--声明切面2-->
<!-- ... -->
</aop:aspect>
</aop:config>
在嵌套标签内部为通知类 MyAdvice 中的方法配置具体的织入动作。配置语法如下所示:
<!-- 多行配置 -->
<aop:通知类型 method="切面类中的方法名" pointcut="切点表达式"></aop:通知类型>
<!-- 单行配置 -->
<aop:通知类型 method="切面类中的方法名" pointcut="切点表达式"/>
这些配置的含义可简单描述为:令 method
通知方法以 <aop:通知类型>
的方式来增强 pointcout
方法的功能。例如:
<aop:after-returning method="afterReturning" pointcut="execution(* TargetInterface.*(..))"/>
其中,<aop:after-returning>
声明该通知类型是一个后置通知;method
则声明执行前置通知的方法名;最后的 pointcut
属性中需要传递一个切点表达式。它用于声明在 “何处” 执行前置通知。“何处” 指 “哪个(些)代码包” 下的 “哪个(些)类” 中的 “哪个(些)” 方法。也就是声明要对哪个(些)切点(方法)执行前置通知。
后面两个小节将总结 切点表达式 和 通知类型。
2.3 切点表达式
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
其中:
- 访问修饰符可省略
- 返回值类型、包名、类名、方法名可以使用通配符
*
,表示任意 - 包名与类名之间一个点
.
代表当前包下的类,两个点..
表示当前包及其子包下的类 - 参数列表可以使用两个点
..
表示任意数量、任意类型的参数列表
通过一些具体的示例可以快速理解切点表达式:
-
execution(public void Target.method())
:切点是 com.abe.aop 包下的 Target 类中的 method() 方法,无输入参数,无返回值 -
execution(void Target.*(..))
:切点是 com.abe.aop 包下的 Target 类中的任意
方法,允许任意
形式的重载参数,无返回值 -
execution(* com.abe.aop.*.*(..))
(较常用):切点是 com.abe.aop 包下的任意
类中的任意
方法,允许任意
形式的重载参数,允许任意
类型的返回值 -
execution(* com.abe.aop..*.*(..))
: 切点是 com.abe.aop 包及其子包下的任意
类中的任意
方法,允许任意
形式的重载参数,允许任意
类型的返回值 -
execution(* *..*.*(..))
: 切点是任意
包及其子包下的任意
类中的任意
方法,允许任意
形式的重载参数,允许任意
类型的返回值。也就是该工程下的所有连接点(方法)均成为该切面类的切点
2.4 通知类型
在 Spring框架:理解 AOP 文中有介绍,方法通知类型包括 5 种,它们的类型即配置示例如下所示:
2.4.1 前置通知
- Java 代码
public void before() { System.out.println("MyAspect: 前置增强....."); }
- XML 配置
<aop:before method="before" pointcut="execution(* TargetInterface.*(..))"/>
2.4.2 后置通知
- Java 代码
public void afterReturning() { System.out.println("MyAspect: 后置增强..."); }
- XML 配置
<aop:after-returning method="afterReturning" pointcut="execution(* TargetInterface.*(..))"/>
2.4.3 环绕通知
环绕通知相对比较特殊,由于需要在 Target 类方法执行前后分别作功能增强,此处需要调用 aspectj 库中的 ProceedingJoinPoint 类,表示当前正在执行的连接点。
- Java 代码
/** * 环绕增强 * @param pjp 正在执行的连接点,也就是切点 */ public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("MyAspect: 环绕前增强..."); // 切点方法 Object proceed = pjp.proceed(); System.out.println("MyAspect: 环绕后增强..."); return proceed; }
- XML 配置
<aop:around method="around" pointcut="execution(* TargetInterface.*(..))"/>
2.4.4 异常通知
- Java 代码
public void afterThrowing() { System.out.println("MyAspect: 异常抛出增强..."); }
- XML 配置
<aop:after-throwing method="afterThrowing" pointcut="execution(* TargetInterface.*(..))"/>
2.4.5 最终通知
- Java 代码
public void after() { System.out.println("MyAspect: 最终增强..."); }
- XML 配置
<aop:after method="after" pointcut="execution(* TargetInterface.*(..))"/>
在上面的配置示例中,发现同一个切点表达式被重复生命多次。Spring 框架支持对切点表达式的抽取和预定义,在具体的配置语句中利用 pointcut-ref
属性导入切点表达式:
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(* com.abe.aop.xml.TargetInterface.*(..))"/>
<!--调用切点表达式-->
<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:before method="before2" pointcut-ref="myPointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
<aop:around method="around" pointcut-ref="myPointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
<aop:after method="after" pointcut-ref="myPointcut"/>
3 完整配置示例
MyAdvice.java
public class MyAdvice {
public void before() {
System.out.println("MyAspect: 前置增强.....");
}
public void afterReturning() {
System.out.println("MyAspect: 后置增强...");
}
/**
* 环绕增强
* @param pjp 正在执行的连接点,也就是切点
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("MyAspect: 环绕前增强...");
// 切点方法
Object proceed = pjp.proceed();
System.out.println("MyAspect: 环绕后增强...");
return proceed;
}
public void afterThrowing() {
System.out.println("MyAspect: 异常抛出增强...");
}
public void after() {
System.out.println("MyAspect: 最终增强...");
}
}
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.xsd
">
<!--配置目标对象-->
<bean id="target" class="com.abe.aop.xml.Target"/>
<!--配置切面对象-->
<bean id="myAspect" class="com.abe.aop.xml.MyAdvice"/>
<!--配置织入动作:告诉Spring,哪些方法(切点)需要做哪些(前置、后置...)增强-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(* com.abe.aop.xml.TargetInterface.*(..))"/>
<!--调用切点表达式-->
<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
<aop:around method="around" pointcut-ref="myPointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
调用测试类 XMLAOP.java 的执行结果:
MyAspect: 前置增强.....
MyAspect: 环绕前增强...
Target: save running...
MyAspect: 最终增强...
MyAspect: 环绕后增强...
MyAspect: 后置增强...