面向切面的 Spring —— 如何在 XML 中声明切面?

曾经建立过这样一种原则:基于注解的配置 > 基于 Java 的配置 > 基于 XML 的配置。

Q:什么情况下使用 XML 配置声明切面?
A:如果你需要声明切面,又不能为通知类添加注解的时候,那么就必须转向 XML 配置了。

Spring 的 aop 命名空间中,提供了多个元素用来在 XML 中声明切面
这里写图片描述

Q:如何定义切面?

A:我们将 Audience 类的所有 AspectJ 注解全部移除掉。

package concert;

/**
 * 该类已经具备了成为 AOP 通知的所有条件。需要配置一下,就能成功预期的通知了。
 */
public class Audience {

    //表演之前:将手机调至静音状态
    public void silenceCellPhones() {
        System.out.println("Silencing cell phones");
    }

    //表演之前:就座
    public void takeSeats() {
        System.out.println("Taking seats");
    }

    //表演之后:精彩的话,观众应该会鼓掌喝彩
    public void applause() {
        System.out.println("CLAP CLAP CLAP!!!");
    }

    //表演失败之后:没有达到观众预期的话,观众会要求退款
    public void demandRefund() {
        System.out.println("Demanding a refund");
    }

}
Q:如何声明前置和后置通知?

A:我们使用 Spring aop 命名空间,将没有注解的 Audience 类转换为切面。

<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 启用 AspectJ 自动代理 -->
    <aop:aspectj-autoproxy/>

    <bean id="audience" class="concert.Audience"/>

    <!-- 大多数的 AOP 配置元素必须在<aop:config> 元素的上下文内使用 -->
    <!-- 在此元素内,我们可以声明一个或多个通知器、切面或者切点 -->
    <aop:config>
        <aop:aspect ref="audience">
            <!-- 表演之前 -->
            <aop:before method="silenceCellPhones" pointcut="execution(**concert.Performance.perform(..))"/>
            <!-- 表演之前 -->
            <aop:before method="takeSeats" pointcut="execution(**concert.Performance.perform(..))"/>
            <!-- 表演之后 -->
            <aop:after-returning method="applause" pointcut="execution(**concert.Performance.perform(..))"/>
            <!-- 表演失败之后 -->
            <aop:after-throwing method="demandRefund" pointcut="execution(**concert.Performance.perform(..))"/>
        </aop:aspect>
    </aop:config>

</beans>

可以简化一下,使用 aop:pointcut 元素。

<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 启用 AspectJ 自动代理 -->
    <aop:aspectj-autoproxy/>

    <bean id="audience" class="concert.Audience"/>

    <!-- 使用 <aop:pointcut> 定义命名切点 -->
    <aop:config>
        <aop:aspect ref="audience">
            <!-- 如果想让定义的切点能够在多个切面使用,我们可以把 <aop:pointcut> 元素放在 <aop:config> 元素的范围内 -->
            <aop:pointcut id="performance" expression="execution(**concert.Performance.perform(..))"/>
            <!-- 表演之前 -->
            <aop:before method="silenceCellPhones" pointcut-ref="performance"/>
            <!-- 表演之前 -->
            <aop:before method="takeSeats" pointcut-ref="performance"/>
            <!-- 表演之后 -->
            <aop:after-returning method="applause" pointcut-ref="performance"/>
            <!-- 表演失败之后 -->
            <aop:after-throwing method="demandRefund" pointcut-ref="performance"/>
        </aop:aspect>
    </aop:config>

</beans>
Q:怎么声明环绕通知?

A:如代码所示

package concert;

import org.aspectj.lang.ProceedingJoinPoint;

public class Audience {

    public void watchPerformance(ProceedingJoinPoint joinPoint){
        try {
            System.out.println("Silencing cell phones");
            System.out.println("Taking seats");
            joinPoint.proceed();
            System.out.println("CLAP CLAP CLAP!!!");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
        }
    }

}
<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 启用 AspectJ 自动代理 -->
    <aop:aspectj-autoproxy/>

    <bean id="audience" class="concert.Audience"/>

    <aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut id="performance" expression="execution(**concert.Performance.perform(..))"/>
            <!-- 声明环绕通知 --> 
            <aop:around method="watchPerformance" pointcut-ref="performance"/>
        </aop:aspect>
    </aop:config>

</beans>
Q:怎么为通知创建参数?

A:我们移除掉 TrackCounter 上所有的 @AspectJ 注解。

package soundsystem;

import java.util.HashMap;
import java.util.Map;

public class TrackCounter {

    private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();

    public void countTrack(int trackNumber) {
        int currentCount = getPlauCount(trackNumber);
        trackCounts.put(trackNumber, currentCount + 1);
    }

    public int getPlauCount(int trackNumber) {
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }

}

借助一点 Spring XML 配置,我们能够让 TrackCounter 重新变为切面。

<?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="trackCounter" class="soundsystem.TrackCounter"/>

    <bean id="cd" class="soundsystem.BlankDisc">
        <property name="title" value="titleValue01"/>
        <property name="artist" value="artistValue01"/>
        <property name="tracks">
            <list>
                <value>tracksValue01</value>
                <value>tracksValue02</value>
                <value>tracksValue03</value>
                <value>tracksValue04</value>
            </list>
        </property>
    </bean>

    <aop:config>
        <aop:aspect ref="trackCounter">
            <aop:pointcut id="trackPlayed"
                          expression="execution(**soundsystem.CompactDisc.play(int)) and args(trackNumber)"/>
            <aop:before method="countTrack" pointcut-ref="trackPlayed"/>
        </aop:aspect>
    </aop:config>

</beans>
Q:怎么通过切面引入新的功能?

A:使用 Spring aop 命名空间的 aop:declare-parents 元素

    <bean id="encoreableDelegate" class="concert.DefaultEncoreable"/>

    <aop:config>
        <aop:aspect>
            <!--
                声明了此切面所通知的 bean 要在它的对象层次结构中拥有新的父类型
                本例中,类型匹配 Performance 接口(由 types-matching 属性指定)的那些 bean 在父类结构中会增加 Encoreable 接口(由 implement-interface 属性指定)
                两种方式标识所引入接口的实现。本例中,我们①、使用 default-impl 属性用全限定类名来显示指定 Encoreable 的实现。
                ②、或者还可以使用 delegate-ref 属性来标识
             -->
            <!--<aop:declare-parents types-matching="concert.Performance" implement-interface="concert.Encoreable"
                                 default-impl="concert.DefaultEncoreable"/>-->

            <!-- delegate-ref 属性引用了一个 Spring bean 作为引入的委托 -->
            <aop:declare-parents types-matching="concert.Performance" implement-interface="concert.Encoreable"
                                 delegate-ref="encoreableDelegate"/>
        </aop:aspect>
    </aop:config>

使用 default-impl 来直接标识委托间接使用 delegate-ref 的区别在于后者是 Spring bean,它本身可以被注入通知使用其他的 Spring 配置

上一篇:面向切面的 Spring —— 如何使用注解创建切面?
下一篇:面向切面的 Spring —— 如何注入 AspectJ 切面?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值