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

来源:互联网 发布:macbook免费清理软件 编辑:程序博客网 时间:2024/05/09 00:35

曾经建立过这样一种原则:基于注解的配置 > 基于 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 切面?