Java - 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: 后置增强...
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值