《Spring3实战》摘要(4-2)--Spring配置切面

4.3 在XML中声明切面

Spring 的 AOP 配置命名空间中,提供了对声明式切面的支持。

配置AOP命名空间:

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

</beans>

这里写图片描述

正常使用AOP功能,需要导入以下jar包:

  • spring-aop.jar
  • aopalliance.jar
  • aspectjrt.jar
  • aspectjweaver.jar

4.3.1 声明前置和后置通知

<!-- 使用Spring的AOP配置元素声明一个audience切面 -->
<aop:config><!-- AOP配置 -->
    <!-- 声明一个切面 -->
    <aop:aspect ref="audience">
        <!-- 配置切面的通知类型及织入切点,即何时和何处执行何种方法 -->
        <!-- 通知在切点执行前 -->
        <aop:before pointcut=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))" 
            method="takeSeats"/>
        <!-- 通知在切点执行前 -->   
        <aop:before pointcut=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))"
            method="turnOffCellPhones"/>
        <!-- 通知在切点成功执行后 -->
        <aop:after-returning pointcut=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))"
            method="applaud"/>
        <!-- 通知在切点执行报错时 -->
        <aop:after-throwing pointcut=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))"
            method="demandRefund"/>
    </aop:aspect>
</aop:config>

/**
 * 作为切面的Bean ID 为audience的类
 */
import org.springframework.stereotype.Component;

@Component
public class Audience {
    //表演之前
    public void takeSeats(){
        System.out.println("The audience is taking their seats.");
    }
    //表演之前
    public void turnOffCellPhones(){
        System.out.println("The audience is turning off their cellphones");
    }
    //表演之后
    public void applaud(){
        System.out.println("CLAP CLAP CLAP CLAP");
    }
    //表演失败之后
    public void demandRefund(){
        System.out.println("Boo! We want our money back!");
    }
}

上例中,所有通知的切点都是相同的,为了避免重复定义切点,可以使用<aop:pointcut>元素定义一个命名切点。如果<aop:pointcut>放在<aop:aspect>元素中,则所有该<aop:aspect>元素中的通知都可以引用该切点。如果<aop:pointcut>放在<aop:config>元素中,则该切点能够在该<aop:config>元素中定义的所有切面中被引用。

<aop:config>
    <aop:aspect ref="audience">
        <aop:pointcut expression=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))" 
            id="performance"/>
        <aop:before pointcut-ref="performance" method="takeSeats"/>

        <aop:before pointcut-ref="performance" method="turnOffCellPhones"/>

        <aop:after-returning pointcut-ref="performance" method="applaud"/>

        <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
    </aop:aspect>
</aop:config>

4.3.2 声明环绕通知

使用环绕通知可以完成之前前置通知和后置通知所实现的相同功能,但是只需要在一个方法中实现。因为整个
通知逻辑是在一个方法内实现的,所以不需要使用成员变量保存状态。

示例如下:

//环绕通知的方法
public void watchPerformance(ProceedingJoinPoint joinpoint){
    try {
        System.out.println("arround---The audience is taking their seats.");
        System.out.println("arround---The audience is turning off their cellphones");
        long start = System.currentTimeMillis();
        /*proceed()方法可以在通知方法里调用被通知的方法,在环绕通知中,proceed()方法是必须调用的,否则通知将会阻止被通知的方法的调用。*/
        joinpoint.proceed();
        long end =System.currentTimeMillis();
        System.out.println("arround---CLAP CLAP CLAP CLAP");
        System.out.println("The performance took "+(end-start)+" milliseconds.");
    } catch (Throwable e) {
        System.out.println("arround---Boo! We want our money back!");
    }
}

-----------------------------------------------------------

<!-- XML配置文件中声明切面使用环绕通知 -->
<aop:config>
    <aop:aspect ref="audience">
        <aop:pointcut expression=
            "execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))" 
            id="performance"/>
        <aop:around pointcut-ref="performance" method="watchPerformance"/>
    </aop:aspect>
</aop:config>

通知执行顺序: before -> arround -> after-returning -> after
这里写图片描述

4.3.3 为通知传递参数

举例:Magician(切面)读取Volunteer(被监听类)的方法参数

/**
 * spring配置,需要额外配置 name 为 magician 的 Bean
 */
<aop:config>
    <aop:aspect ref="magician">
        <aop:pointcut id="thinking" 
            expression="execution(* com.springinaction.springidol.Thinker.thinkOfSomething(String)) and args(thoughts)" />
        <aop:before method="interceptThoughts" pointcut-ref="thinking" arg-names="thoughts" />
    </aop:aspect>
</aop:config>

/**
 * Volunteer(被监听类)
 */
@Component
public class Volunteer implements Thinker{
    private String thoughts;
    @Override
    public void thinkOfSomething(String thoughts) {
        this.thoughts = thoughts;
    }
    public String getThoughts(){
        return thoughts;
    }   
}
/**
 * Magician类(切面类)
 */
 @Controller
public class Magician implements MindReader{
    private String thoughts;
    @Override
    public void interceptThoughts(String thoughts) {
        System.out.println("Intercepting volunteer's thoughts:"+thoughts);
        this.thoughts = thoughts;
    }
    @Override
    public String getThoughts() {
        return thoughts;
    }
}

4.3.4 通过切面引入新功能

利用被称为“引用”的 AOP 概念,切面可以为 Spring Bean 添加新方法。
需要使用<aop:declare-parents>元素。

举例如下:

/**
 * spring 配置
 * <aop:declare-parents>元素声明了此切面所通知的Bean在它的对象层次结构中拥有新的父类型。
 * 本示例中,类型匹配 Performer 接口(由types-matching属性制定)的那些Bean会实现 Contestant 接口(由implement-interface属性指定)。最后要解决的问题是Contestant接口中的方法实现来自何处。
 * 本例中,使用default-impl属性通过它的全限定类名来显式制定Contestant的实现。还可以使用default-ref属性来标识。
 * default-ref属性引用了一个 Spring Bean 作为引入的委托。该引入的Bean 本身可以被注入,被通知,或者使用其他的Spring配置。
 */
<aop:aspect>
    <aop:declare-parents 
        types-matching="com.springinaction.springidol.Performer+" 
        implement-interface="com.springinaction.springidol.Contestant"
        default-impl="com.springinaction.springidol.GraciousContestant"/>
</aop:aspect>

4.4 注解切面

使用注解来创建切面是 AspectJ 5 所引入的关键特性。AspectJ 面向注解的模型可以非常简便地通过少量注解把任意类型转变为切面。这种新特性通常称为 @AspectJ 。该注解位于aspectjrt.jar 中。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
 * 将4.3.1的切面配置示例用注解来实现
 */
@Aspect
public class Audience {
    //定义切点,切点的名称来源于注解所应用的方法名称。因此该切点的名称为"performance()"。
    //performance()方法的实现内容并不重要,该方法只是一个标识,供@Pointcut依赖。
    @Pointcut("execution(* com.spring.springdemo.bean.springidol2.Performer.perform(..))")
    public void performance(){

    }

    //表演之前
    @Before("performance()")
    public void takeSeats(){
        System.out.println("before---The audience is taking their seats.");
    }
    //表演之前
    @Before("performance()")
    public void turnOffCellPhones(){
        System.out.println("before---The audience is turning off their cellphones");
    }
    //表演之后
    @AfterReturning("performance()")
    public void applaud(){
        System.out.println("after-----CLAP CLAP CLAP CLAP");
    }
    //表演失败之后
    @AfterThrowing("performance()")
    public void demandRefund(){
        System.out.println("after-throwing----Boo! We want our money back!");
    }
    //环绕通知
    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint joinpoint){
        try {
            System.out.println("arround---The audience is taking their seats.");
            System.out.println("arround---The audience is turning off their cellphones");
            long start = System.currentTimeMillis();
            joinpoint.proceed();
            long end =System.currentTimeMillis();
            System.out.println("arround---CLAP CLAP CLAP CLAP");
            System.out.println("The performance took "+(end-start)+" milliseconds.");
        } catch (Throwable e) {
            System.out.println("arround---Boo! We want our money back!");
        }
    }
}

有了上面 @AspectJ 标识的切面类 Audience 之后,还需要让 Spring 将Audience 应用为一个切面。我们可以在 Spring 上下文中把 AnnotationAwareAspectJAutoProxyCreator 注册为一个Bean,该 Bean 知道如何把 @AspectJ 注解所标注的 Bean 转变为代理通知。

Spring在aop命名空间中提供了一个自定义的配置元素,<aop:aspectj-autoproxy />将在 Spring 上下文中创建一个 AnnotationAwareAspectJAutoProxyCreator 类,他会自动代理一些 Bean ,这些Bean的方法需要与使用 @Aspect 注解的 Bean 中所定义的切点相匹配,而这些切点又是使用 @Pointcut 注解定义出来的。

4.4.2 传递参数给所标注的通知

/**
 * 将4.3.3的切面配置示例用注解来实现
 */
@Aspect
public class Magician implements MindReader{
    private String thoughts;

    @Pointcut("execution(* com.spring.springdemo.bean.springidol2.Thinker."
            +"thinkOfSomething(String)) and args(thoughts)")
    public void thinking(String thoughts){

    }
    //这里不需要与<aop:before>元素对应的arg-names熟悉,@AspectJ能够依靠Java语法来判断为通知所传递参数的细节。
    @Before("thinking(thoughts)")
    public void interceptThoughts(String thoughts) {
        System.out.println("Intercepting volunteer's thoughts:"+thoughts);
        this.thoughts = thoughts;
    }

    @Override
    public String getThoughts() {
        return thoughts;
    }
}

4.4.3 标注引入

等价于<aop:declare-parents>的注解是 @AspectJ 的 @DeclareParents 。在基于 @AspectJ 注解所标注的类内使用时,@DeclareParents 工作方式几乎等同于<aop:declare-parents>

/**
 * 将4.3.4的切面配置示例用注解来实现
 */
@Aspect
public class ContestantIntroducer {
    @DeclareParents(value="com.spring.springdemo.bean.springidol3.Performer+"
    ,defaultImpl= GraciousContestant.class)
    public static Contestant contestant;
}

@DeclareParents注解由3部分组成

  • value 属性等同于 <aop:declare-parents> 的 types-matching 属性。它标识应该被引入指定接口的Bean的类型。
  • defaultImpl 属性等同于 <aop:declare-parents> 的 default-impl属性。它标识该类提供了所引入接口的实现。
  • 由 @DeclareParents 注解所标注的 static属性指定了将被引入的接口。

4.5 注入 AspectJ 切面

虽然 Spring AOP 能够满足许多应用的切面需求,但与 AspectJ 相比,Spring AOP 是一个功能比较弱的 AOP 解决方案。AspectJ 提供了 Spring AOP 所不能支持的许多类型的切点。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章