Spring

Spring之AOP(一)

2018-04-23  本文已影响151人  聂叼叼

一、AOP的基础

1.1、AOP是什么???

考虑这样一个问题:需要对系统中的某些业务做日志记录,比如支付系统中的支付业务需要记录支付相关日志,对于支付系统可能相当复杂,比如可能有自己的支付系统,也可能引入第三方支付平台,面对这样的支付系统该如何解决呢?
传统解决方案:

1)日志部分提前公共类LogUtils,定义“longPayBegin”方法用于记录支付开始日志,“logPayEnd”用于记录支付结果: image.png
2)支付部分,定义IPayService接口并定义支付方法“pay”,并定义了两个实现:“PointPayService”表示积分支付,“RMBPayService”表示人民币支付;并且在每个支付实现中支付逻辑和记录日志:
image.png

3)支付实现很明显有重复代码,这个重复很明显可以使用模板设计模式消除重复:


image.png
4)到此我们设计了一个可以复用的接口;但大家觉得这样记录日志会很好吗,有没有更好的解决方案?
如果对积分支付方式添加统计功能,比如在支付时记录下用户总积分数、当前消费的积分数,那我们该如何做呢?直接修改源代码添加日志记录,这完全违背了面向对象最重要的原则之一:开闭原则(对扩展开放,对修改关闭)
更好的解决方案:在我们的支付组件中由于使用了日志组件,即日志模块横切于支付组件,在传统程序设计中很难将日志组件分离出来,即不耦合我们的支付组件;因此面向方面编程AOP就诞生了,它能分离我们的组件,使组件完全不耦合:
1)采用面向方面编程后,我们的支付组件看起来如下所示,代码中不再有日志组件的任何东西;
image.png
2)所以日志相关的提取到一个切面中,AOP实现者会在合适的时候将日志功能织入到我们的支付组件中去,从而完全解耦支付组件和日志组件。
image.png

看到这大家可能不是很理解,没关系,先往下看。

面向方面编程(AOP):也可称为面向切面编程,是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP)。

在进行OOP开发时,都是基于对组件(比如类)进行开发,然后对组件进行组合,OOP最大问题就是无法解耦组件进行开发,比如我们上边举例,而AOP就是为了克服这个问题而出现的,它来进行这种耦合的分离。
AOP为开发者提供一种进行横切关注点(比如日志关注点横切了支付关注点)分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而无耦合的完成了我们的功能。

1.2、 能干什么????

AOP主要用于横切关注点分离和织入,因此需要理解横切关注点和织入:
AOP能干什么:

1.3、AOP的基本概念

在进行AOP开发前,先熟悉几个概念:

1.4、 AOP代理

AOP代理就是AOP框架通过代理模式创建的对象,Spring使用JDK动态代理或CGLIB代理来实现,Spring缺省使用JDK动态代理来实现,从而任何接口都可别代理,如果被代理的对象实现不是接口将默认使用CGLIB代理,不过CGLIB代理当然也可应用到接口。
AOP代理的目的就是将切面织入到目标对象。
概念都将完了,接下来让我们看一下AOP的 HelloWorld!吧。

二、AOP的HelloWorld(例子,对上面概念的一个使用)

2.1、准备环境

首先准备开发需要的jar包,请到spring-framework-3.0.5.RELEASE-dependencies.zip和spring-framework-3.0.5.RELEASE-with-docs中查找如下jar包:

org.springframework.aop-3.0.5.RELEASE.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.net.sf.cglib-2.2.0.jar

将这些jar包添加到“Build Path”下。

2.2、定义目标类

1)定义目标接口:

package com.nieshenkuan.aop;
/**
 * 定义目标接口:创建被代理的接口
 * @author NSK
 *
 */
public interface IHelloWorldService {
    void sayHello();
}

2)定义目标接口实现:

package com.nieshenkuan.aop;
/**
 * 定义目标接口实现:接口的实现类
 * @author NSK
 *
 */
public class HelloWorldService implements IHelloWorldService {

    @Override
    public void sayHello() {
        System.out.println("============Hello World!");

    }

}

注:在日常开发中最后将业务逻辑定义在一个专门的service包下,而实现定义在service包下的impl包中,服务接口以IXXXService形式,而服务实现就是XXXServiceImpl,这就是规约设计,见名知义。当然可以使用公司内部更好的形式,只要大家都好理解就可以了。

2.2、 定义切面支持类

有了目标类,该定义切面了,切面就是通知和切入点的组合,而切面是通过配置方式定义的,因此这定义切面前,我们需要定义切面支持类,切面支持类提供了通知实现:

package com.nieshenkuan.aop;

/**
 * 定义切面支持类 有了目标类,该定义切面了,切面就是通知和切入点的组合,而切面是通过配置方式定义的,
 * 因此这定义切面前,我们需要定义切面支持类,切面支持类提供了通知实现
 * 
 * @author NSK
 *
 */
public class HelloWorldAspect {
    // 前置通知
    public void beforeAdvice() {
        System.out.println("===========before advice");
    }

    // 后置最终通知
    public void afterFinallyAdvice() {
        System.out.println("===========after finally advice");
    }
}

此处HelloWorldAspect类不是真正的切面实现,只是定义了通知实现的类,在此我们可以把它看作就是缺少了切入点的切面。
注:对于AOP相关类最后专门放到一个包下,如“aop”包,因为AOP是动态织入的,所以如果某个目标类被AOP拦截了并应用了通知,可能很难发现这个通知实现在哪个包里,因此推荐使用规约命名,方便以后维护人员查找相应的AOP实现。

2.3 、在XML中进行配置

有了通知实现,那就让我们来配置切面吧:
1)首先配置AOP需要aop命名空间,配置头如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.2.xsd 
                    http://www.springframework.org/schema/tx 
                    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
</beans>

2)配置目标类:

    <!-- 2)配置目标类: -->
    <bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>

3)配置切面:

    <!-- 3)配置切面 -->
    <bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.nieshenkuan..*.*(..))" />
        <aop:aspect ref="aspect">
            <aop:before pointcut-ref="pointcut" method="beforeAdvice" />
            <aop:after pointcut="execution(* com.nieshenkuan..*.*(..))"
                method="afterFinallyAdvice" />
        </aop:aspect>
    </aop:config>

整体配置:applicationContext2.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.2.xsd 
                    http://www.springframework.org/schema/tx 
                    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
    <!-- 2)配置目标类: -->
    <bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>

    <!-- 3)配置切面 -->
    <bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.nieshenkuan..*.*(..))" />
        <aop:aspect ref="aspect">
            <aop:before pointcut-ref="pointcut" method="beforeAdvice" />
            <aop:after pointcut="execution(* com.nieshenkuan..*.*(..))"
                method="afterFinallyAdvice" />
        </aop:aspect>
    </aop:config>
</beans>

切入点使用<aop:config>标签下的<aop:pointcut>配置,expression属性用于定义切入点模式,默认是AspectJ语法,“execution(* com.nieshenkuan...(..))”表示匹配com.nieshenkuan包及子包下的任何方法执行。
切面使用<aop:config>标签下的<aop:aspect>标签配置,其中“ref”用来引用切面支持类的方法。
前置通知使用<aop:aspect>标签下的<aop:before>标签来定义,pointcut-ref属性用于引用切入点Bean,而method用来引用切面通知实现类中的方法,该方法就是通知实现,即在目标类方法执行之前调用的方法。
最终通知使用<aop:aspect>标签下的<aop:after >标签来定义,切入点除了使用pointcut-ref属性来引用已经存在的切入点,也可以使用pointcut属性来定义,如pointcut="execution(* com.nieshenkuan...(..))",method属性同样是指定通知实现,即在目标类方法执行之后调用的方法。

2.4、运行测试

测试类非常简单,调用被代理Bean跟调用普通Bean完全一样,Spring AOP将为目标对象创建AOP代理,具体测试代码如下:

package com.nieshenkuan.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    @org.junit.Test
    public void testAop() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext2.xml");
        IHelloWorldService helloworldService = ac.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayHello();
    }

}

该测试将输出如下如下内容:

===========before advice
============Hello World!
===========after finally advice

从输出我们可以看出:前置通知在切入点选择的连接点(方法)之前允许,而后置通知将在连接点(方法)之后执行,具体生成AOP代理及执行过程如图


Spring AOP框架生成AOP代理过程

三、 基于Schema的AOP

基于Schema的AOP从Spring2.0之后通过“aop”命名空间来定义切面、切入点及声明通知。
在Spring配置文件中,所以AOP相关定义必须放在<aop:config>标签下,该标签下可以有<aop:pointcut>、<aop:advisor>、<aop:aspect>标签,配置顺序不可变。

<aop:config> AOP定义开始(有序)
<aop:pointcut/> 切入点定义(零个或多个)
<aop:advisor/> Advisor定义(零个或多个)
<aop:aspect> 切面定义开始(零个或多个,无序)
<aop:pointcut/> 切入点定义(零个或多个)
<aop:before"/> 前置通知(零个或多个)
<aop:after-returning/> 后置返回通知(零个或多个)
<aop:after-throwing/> 后置异常通知(零个或多个)
<aop:after/> 后置最终通知(零个或多个)
<aop:around/> 环绕通知(零个或多个)
<aop:declare-parents/> 引入定义(零个或多个)
</aop:aspect> 切面定义开始(零个或多个)
</aop:config> AOP定义结束

3.1、声明切面

切面就是包含切入点和通知的对象,在Spring容器中将被定义为一个Bean,Schema方式的切面需要一个切面支持Bean,该支持Bean的字段和方法提供了切面的状态和行为信息,并通过配置方式来指定切入点和通知实现。
切面使用<aop:aspect>标签指定,ref属性用来引用切面支持Bean。

<bean id="aspectSupportBean" class="……"/>
<aop:config>
<aop:aspect id="aspectId" ref="aspectSupportBean">
      ……
</aop:aspect>
</aop:config>

切面支持Bean“aspectSupportBean”跟普通Bean完全一样使用,切面使用“ref”属性引用它。

3.2 、 声明切入点

切入点在Spring中也是一个Bean,Bean定义方式可以有很三种方式:
1)在<aop:config>标签下使用<aop:pointcut>声明一个切入点Bean,该切入点可以被多个切面使用,对于需要共享使用的切入点最好使用该方式,该切入点使用id属性指定Bean名字,在通知定义时使用pointcut-ref属性通过该id引用切入点,expression属性指定切入点表达式:

<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.nieshenkuan..*.*(..))"/>
<aop:aspect ref="aspectSupportBean">
  <aop:before pointcut-ref="pointcut" method="before"/>
    </aop:aspect>
</aop:config>

2)在<aop:aspect>标签下使用<aop:pointcut>声明一个切入点Bean,该切入点可以被多个切面使用,但一般该切入点只被该切面使用,当然也可以被其他切面使用,但最好不要那样使用,该切入点使用id属性指定Bean名字,在通知定义时使用pointcut-ref属性通过该id引用切入点,expression属性指定切入点表达式:

<aop:config>
<aop:aspect ref="aspectSupportBean">
    <aop:pointcut id=" pointcut" expression="execution(* com.nieshenkuan..*.*(..))"/>
  <aop:before pointcut-ref="pointcut" method="before"/>
    </aop:aspect>
</aop:config>

3)匿名切入点Bean,可以在声明通知时通过pointcut属性指定切入点表达式,该切入点是匿名切入点,只被该通知使用:

<aop:config>
<aop:aspect ref="aspectSupportBean">
<aop:after pointcut="execution(* com.nieshenkuan..*.*(..))"
 method="afterFinallyAdvice"/>
    </aop:aspect>
</aop:config>

3.3 、声明通知

基于Schema方式支持前边介绍的5种通知类型:
一、前置通知:在切入点选择的方法之前执行,通过<aop:aspect>标签下的<aop:before>标签声明:

<aop:before pointcut="切入点表达式"  pointcut-ref="切入点Bean引用"
method="前置通知实现方法名" 
arg-names="前置通知实现方法参数列表参数名字"/>
package com.nieshenkuan.aop;

/**
 * 定义目标接口:创建被代理的接口
 * 
 * @author NSK
 *
 */
public interface IHelloWorldService {

    void sayHello();

    // 新添加的方法=================================================
    void sayBefore(String param);
// 新添加的方法=================================================
}

其次在com.nieshenkuan.aop. HelloWorldService定义实现:

package com.nieshenkuan.aop;

/**
 * 定义目标接口实现:接口的实现类
 * 
 * @author NSK
 *
 */
public class HelloWorldService implements IHelloWorldService {

    @Override
    public void sayHello() {
        System.out.println("============Hello World!");

    }

    // 新添加的方法=================================================
    @Override
    public void sayBefore(String param) {
        System.out.println("============say " + param);

    }
// 新添加的方法=================================================
}

第三在com.nieshenkuan.aop. HelloWorldAspect定义通知实现:

package com.nieshenkuan.aop;

/**
 * 定义切面支持类 有了目标类,该定义切面了,切面就是通知和切入点的组合,而切面是通过配置方式定义的,
 * 因此这定义切面前,我们需要定义切面支持类,切面支持类提供了通知实现
 * 
 * @author NSK
 *
 */
public class HelloWorldAspect {
    // 前置通知
    public void beforeAdvice() {
        System.out.println("===========before advice");
    }

    // 后置最终通知
    public void afterFinallyAdvice() {
        System.out.println("===========after finally advice");
    }

    // 新添加的方法=====================================
    public void beforeAdvice(String param) {
        System.out.println("===========before advice param:" + param);
    }
    // 新添加的方法=================================================
}

最后在applicationContext3.xml配置文件中进行如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.2.xsd 
                    http://www.springframework.org/schema/tx 
                    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>


    <bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
    <aop:config>
        <aop:aspect ref="aspect">
            <aop:before
                pointcut="execution(* com.nieshenkuan..*.sayBefore(..))  and args(param)"
                method="beforeAdvice(java.lang.String)" arg-names="param" />
        </aop:aspect>
    </aop:config>
</beans>

测试代码Test.java

// ===============================================================
    @org.junit.Test
    public void estSchemaBeforeAdvice() {
        System.out.println("======================================");
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext3.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayBefore("before");
        System.out.println("======================================");
    }
    // =================================================================

输出为:

======================================
===========before advice param:before
============say before
======================================

分析一下吧:
1)切入点匹配:在配置中使用“execution(* cn.javass..*.sayBefore(..)) ”匹配目标方法sayBefore,且使用“args(param)”匹配目标方法只有一个参数且传入的参数类型为通知实现方法中同名的参数类型;
2)目标方法定义:使用method=" beforeAdvice(java.lang.String) "指定前置通知实现方法,且该通知有一个参数类型为java.lang.String参数;
3)目标方法参数命名:其中使用arg-names=" param "指定通知实现方法参数名为“param”,切入点中使用“args(param)”匹配的目标方法参数将自动传递给通知实现方法同名参数。

二、后置返回通知:在切入点选择的方法正常返回时执行,通过<aop:aspect>标签下的<aop:after-returning>标签声明:

<aop:after-returning pointcut="切入点表达式"  pointcut-ref="切入点Bean引用"
method="后置返回通知实现方法名" 
arg-names="后置返回通知实现方法参数列表参数名字"
returning="返回值对应的后置返回通知实现方法参数名"
/>
package com.nieshenkuan.aop;

/**
 * 定义目标接口:创建被代理的接口
 * 
 * @author NSK
 *
 */
public interface IHelloWorldService {

    void sayHello();

    // 新添加的方法=================================================
    void sayBefore(String param);
    // 新添加的方法=================================================

    // 新添加的方法,后置通知=============================
    boolean sayAfterReturning();
    // 新添加的方法,后置通知=============================
}

其次在com.nieshenkuan.aop. HelloWorldService定义实现:

package com.nieshenkuan.aop;

/**
 * 定义目标接口实现:接口的实现类
 * 
 * @author NSK
 *
 */
public class HelloWorldService implements IHelloWorldService {

    @Override
    public void sayHello() {
        System.out.println("============Hello World!");

    }

    // 新添加的方法=================================================
    @Override
    public void sayBefore(String param) {
        System.out.println("============say " + param);

    }
    // 新添加的方法=================================================

    // 新添加的方法,后置通知=============================
    @Override
    public boolean sayAfterReturning() {
        System.out.println("============after returning");
        return true;
    }
    // 新添加的方法,后置通知=============================
}

第三在com.nieshenkuan.aop. HelloWorldAspect定义通知实现(后置通知):


/**
 * 定义切面支持类 有了目标类,该定义切面了,切面就是通知和切入点的组合,而切面是通过配置方式定义的,
 * 因此这定义切面前,我们需要定义切面支持类,切面支持类提供了通知实现
 * 
 * @author NSK
 *
 */
public class HelloWorldAspect {
    // 前置通知
    public void beforeAdvice() {
        System.out.println("===========before advice");
    }

    // 后置最终通知
    public void afterFinallyAdvice() {
        System.out.println("===========after finally advice");
    }

    // 新添加的方法=====================================
    public void beforeAdvice(String param) {
        System.out.println("===========before advice param:" + param);
    }
    // 新添加的方法=================================================
    
    // 新添加的方法,后置通知=================================================
    public void afterReturningAdvice(Object retVal) {
        System.out.println("===========after returning advice retVal:" + retVal);
    }
    // 新添加的方法,后置通知=================================================
}

最后在applicationContext4.xml配置文件中接着前置通知配置的例子添加如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.2.xsd 
                    http://www.springframework.org/schema/tx 
                    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>


    <bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
    <aop:config>
        <aop:aspect ref="aspect">
            <!-- 前置通知 -->
            <aop:before
                pointcut="execution(* com.nieshenkuan..*.sayBefore(..))  and args(param)"
                method="beforeAdvice(java.lang.String)" arg-names="param" />
            <!-- 后置通知============================== -->
            <aop:after-returning
                pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
                method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
            <!-- 后置通知============================== -->
        </aop:aspect>

    </aop:config>
</beans>

测试代码Test.java

/**
     * 后置通知
     * 
     */
    // ===============================================================
    @org.junit.Test
    public void testSchemaAfterReturningAdvice() {
        System.out.println("======================================");
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayAfterReturning();
        System.out.println("======================================");
    }
    // =================================================================

结果:

======================================
============after returning
===========after returning advice retVal:true
======================================

分析一下吧:
1)切入点匹配:在配置中使用“execution(* com.nieshenkuan..*.sayAfterReturning(..)) ”匹配目标方法sayAfterReturning,该方法返回true;
2)目标方法定义:使用method="afterReturningAdvice"指定后置返回通知实现方法;
3)目标方法参数命名:其中使用arg-names="retVal"指定通知实现方法参数名为“retVal”;
4)返回值命名:returning="retVal"用于将目标返回值赋值给通知实现方法参数名为“retVal”的参数上。

三、后置异常通知:在切入点选择的方法抛出异常时执行,通过<aop:aspect>标签下的<aop:after-throwing>标签声明:

<aop:after-throwing pointcut="切入点表达式"  pointcut-ref="切入点Bean引用"
method="后置异常通知实现方法名" 
arg-names="后置异常通知实现方法参数列表参数名字"
throwing="将抛出的异常赋值给的通知实现方法参数名"/>

首先在com.nieshenkuan.aop.IHelloWorldService定义一个测试方法(后置异常通知):

package com.nieshenkuan.aop;

/**
 * 定义目标接口:创建被代理的接口
 * 
 * @author NSK
 *
 */
public interface IHelloWorldService {

    void sayHello();

    // 新添加的方法,前置通知=================================================
    void sayBefore(String param);
    // 新添加的方法,前置通知=================================================

    // 新添加的方法,后置通知=============================
    boolean sayAfterReturning();
    // 新添加的方法,后置通知=============================

    // 新添加的方法,后置异常通知=============================
    void sayAfterThrowing();
    // 新添加的方法,后置异常通知=============================
}

其次在com.nieshenkuan.aop. HelloWorldService定义实现:


/**
 * 定义目标接口实现:接口的实现类
 * 
 * @author NSK
 *
 */
public class HelloWorldService implements IHelloWorldService {

    @Override
    public void sayHello() {
        System.out.println("============Hello World!");

    }

    // 新添加的方法=================================================
    @Override
    public void sayBefore(String param) {
        System.out.println("============say " + param);

    }
    // 新添加的方法=================================================

    // 新添加的方法,后置通知=============================
    @Override
    public boolean sayAfterReturning() {
        System.out.println("============after returning");
        return true;
    }
    // 新添加的方法,后置通知=============================

    // 新添加的方法,后置异常通知=============================
    @Override
    public void sayAfterThrowing() {
        System.out.println("============before throwing");
        throw new RuntimeException();
    }
    // 新添加的方法,后置异常通知=============================
}

第三在com.nieshenkuan.aop. HelloWorldAspect定义通知实现:


/**
 * 定义切面支持类 有了目标类,该定义切面了,切面就是通知和切入点的组合,而切面是通过配置方式定义的,
 * 因此这定义切面前,我们需要定义切面支持类,切面支持类提供了通知实现
 * 
 * @author NSK
 *
 */
public class HelloWorldAspect {
    // 前置通知
    public void beforeAdvice() {
        System.out.println("===========before advice");
    }

    // 后置最终通知
    public void afterFinallyAdvice() {
        System.out.println("===========after finally advice");
    }

    // 新添加的方法=====================================
    public void beforeAdvice(String param) {
        System.out.println("===========before advice param:" + param);
    }
    // 新添加的方法=================================================

    // 新添加的方法,后置通知=================================================
    public void afterReturningAdvice(Object retVal) {
        System.out.println("===========after returning advice retVal:" + retVal);
    }

    // 新添加的方法,后置通知=================================================

    // 新添加的方法,后置异常通知=============================
    public void afterThrowingAdvice(Exception exception) {
        System.out.println("===========after throwing advice exception:" + exception);
    }
    // 新添加的方法,后置异常通知=============================
}

最后在applicationContext4.xml配置文件中接着前置通知配置的例子添加如下配置:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.2.xsd 
                    http://www.springframework.org/schema/tx 
                    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>


    <bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
    <aop:config>
        <aop:aspect ref="aspect">
            <!-- 前置通知 -->
            <aop:before
                pointcut="execution(* com.nieshenkuan..*.sayBefore(..))  and args(param)"
                method="beforeAdvice(java.lang.String)" arg-names="param" />
            <!-- 后置异常通知================================= -->
            <aop:after-throwing
                pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
                method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
            <!-- 后置异常通知================================= -->
            <!-- 后置通知============================== -->
            <aop:after-returning
                pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
                method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
            <!-- 后置通知============================== -->
        </aop:aspect>

    </aop:config>
</beans>

测试代码Test.java

/**
     * 后置异常通知
     * 
     */

    @org.junit.Test(expected = RuntimeException.class)
    public void testSchemaAfterThrowingAdvice() {
        System.out.println("======================================");
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayAfterThrowing();
        System.out.println("======================================");
    }

输出:

============before throwing
===========after throwing advice exception:java.lang.RuntimeException

分析一下吧:
1)切入点匹配:在配置中使用“execution(* com.nieshenkuan..*.sayAfterThrowing(..))”匹配目标方法sayAfterThrowing,该方法将抛出RuntimeException异常;
2)目标方法定义:使用method="afterThrowingAdvice"指定后置异常通知实现方法;
3)目标方法参数命名:其中使用arg-names="exception"指定通知实现方法参数名为“exception”;
4)异常命名:returning="exception"用于将目标方法抛出的异常赋值给通知实现方法参数名为“exception”的参数上。

四、后置最终通知:在切入点选择的方法返回时执行,不管是正常返回还是抛出异常都执行,通过<aop:aspect>标签下的<aop:after >标签声明:

<aop:after pointcut="切入点表达式"  pointcut-ref="切入点Bean引用"
method="后置最终通知实现方法名" 
arg-names="后置最终通知实现方法参数列表参数名字"/>

pointcut和pointcut-ref:同前置通知同义;
method:同前置通知同义;
arg-names:同前置通知同义;
首先在com.nieshenkuan.aop.IHelloWorldService定义一个测试方法(后置最终通知):

package com.nieshenkuan.aop;

/**
 * 定义目标接口:创建被代理的接口
 * 
 * @author NSK
 *
 */
public interface IHelloWorldService {

    void sayHello();

    // 新添加的方法,前置通知=================================================
    void sayBefore(String param);
    // 新添加的方法,前置通知=================================================

    // 新添加的方法,后置通知=============================
    boolean sayAfterReturning();
    // 新添加的方法,后置通知=============================

    // 新添加的方法,后置异常通知=============================
    void sayAfterThrowing();
    // 新添加的方法,后置异常通知=============================
    
    // 新添加的方法,后置最终通知=============================
    boolean sayAfterFinally();
    // 新添加的方法,后置最终通知=============================
}

其次在com.nieshenkuan.aop. HelloWorldService定义实现:

package com.nieshenkuan.aop;

/**
 * 定义目标接口实现:接口的实现类
 * 
 * @author NSK
 *
 */
public class HelloWorldService implements IHelloWorldService {

    @Override
    public void sayHello() {
        System.out.println("============Hello World!");

    }

    // 新添加的方法=================================================
    @Override
    public void sayBefore(String param) {
        System.out.println("============say " + param);

    }
    // 新添加的方法=================================================

    // 新添加的方法,后置通知=============================
    @Override
    public boolean sayAfterReturning() {
        System.out.println("============after returning");
        return true;
    }
    // 新添加的方法,后置通知=============================

    // 新添加的方法,后置异常通知=============================
    @Override
    public void sayAfterThrowing() {
        System.out.println("============before throwing");
        throw new RuntimeException();
    }
    // 新添加的方法,后置异常通知=============================

    // 新添加的方法,后置最终通知=============================
    @Override
    public boolean sayAfterFinally() {
        System.out.println("============before finally");
        throw new RuntimeException();
    }
    // 新添加的方法,后置最终通知=============================
}

第三在com.nieshenkuan.aop. HelloWorldAspect定义通知实现:

package com.nieshenkuan.aop;

/**
 * 定义切面支持类 有了目标类,该定义切面了,切面就是通知和切入点的组合,而切面是通过配置方式定义的,
 * 因此这定义切面前,我们需要定义切面支持类,切面支持类提供了通知实现
 * 
 * @author NSK
 *
 */
public class HelloWorldAspect {
    // 前置通知
    public void beforeAdvice() {
        System.out.println("===========before advice");
    }

    // 后置最终通知
    public void afterFinallyAdvice() {
        System.out.println("===========after finally advice");
    }

    // 新添加的方法=====================================
    public void beforeAdvice(String param) {
        System.out.println("===========before advice param:" + param);
    }
    // 新添加的方法=================================================

    // 新添加的方法,后置通知=================================================
    public void afterReturningAdvice(Object retVal) {
        System.out.println("===========after returning advice retVal:" + retVal);
    }

    // 新添加的方法,后置通知=================================================

    // 新添加的方法,后置异常通知=============================
    public void afterThrowingAdvice(Exception exception) {
        System.out.println("===========after throwing advice exception:" + exception);
    }
    // 新添加的方法,后置异常通知=============================
    
}

最后在applicationContext4.xml配置文件中接着前置通知配置的例子添加如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.2.xsd 
                    http://www.springframework.org/schema/tx 
                    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>


    <bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
    <aop:config>
        <aop:aspect ref="aspect">
            <!-- 前置通知 -->
            <aop:before
                pointcut="execution(* com.nieshenkuan..*.sayBefore(..))  and args(param)"
                method="beforeAdvice(java.lang.String)" arg-names="param" />
                
            <!-- 后置异常通知================================= -->
            <aop:after-throwing
                pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
                method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
            <!-- 后置异常通知================================= -->
            
            <!-- 后置通知============================== -->
            <aop:after-returning
                pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
                method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
            <!-- 后置通知============================== -->
            
            <!-- 后置最终通知========================== -->
            <aop:after pointcut="execution(* com.nieshenkuan..*.sayAfterFinally(..))"
                method="afterFinallyAdvice" />
            <!-- 后置最终通知 ======================== -->
            
        </aop:aspect>

    </aop:config>
</beans>

测试代码Test.java

/**
     * 后置最终通知
     */
    @org.junit.Test(expected = RuntimeException.class)
    public void testSchemaAfterFinallyAdvice() {
        System.out.println("======================================");
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayAfterFinally();
        System.out.println("======================================");
    }

结果:

============before finally
===========after finally advice

分析一下吧:
1)切入点匹配:在配置中使用“execution(* com.nieshenkuan..*.sayAfterFinally(..))”匹配目标方法sayAfterFinally,该方法将抛出RuntimeException异常;
2)目标方法定义:使用method=" afterFinallyAdvice "指定后置最终通知实现方法。

五、环绕通知:环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值,可通过<aop:aspect>标签下的<aop:around >标签声明:

<aop:around pointcut="切入点表达式"  pointcut-ref="切入点Bean引用"
method="后置最终通知实现方法名" 
arg-names="后置最终通知实现方法参数列表参数名字"/>
package com.nieshenkuan.aop;

/**
 * 定义目标接口:创建被代理的接口
 * 
 * @author NSK
 *
 */
public interface IHelloWorldService {

    void sayHello();

    // 新添加的方法,前置通知=================================================
    void sayBefore(String param);
    // 新添加的方法,前置通知=================================================

    // 新添加的方法,后置通知=============================
    boolean sayAfterReturning();
    // 新添加的方法,后置通知=============================

    // 新添加的方法,后置异常通知=============================
    void sayAfterThrowing();
    // 新添加的方法,后置异常通知=============================

    // 新添加的方法,后置最终通知=============================
    boolean sayAfterFinally();
    // 新添加的方法,后置最终通知=============================

    // 新添加的方法,环绕通知=============================
    void sayAround(String param);
    // 新添加的方法,环绕通知=============================
}

其次在com.nieshenkuan.aop. HelloWorldService定义实现:

package com.nieshenkuan.aop;

/**
 * 定义目标接口实现:接口的实现类
 * 
 * @author NSK
 *
 */
public class HelloWorldService implements IHelloWorldService {

    @Override
    public void sayHello() {
        System.out.println("============Hello World!");

    }

    // 新添加的方法=================================================
    @Override
    public void sayBefore(String param) {
        System.out.println("============say " + param);

    }
    // 新添加的方法=================================================

    // 新添加的方法,后置通知=============================
    @Override
    public boolean sayAfterReturning() {
        System.out.println("============after returning");
        return true;
    }
    // 新添加的方法,后置通知=============================

    // 新添加的方法,后置异常通知=============================
    @Override
    public void sayAfterThrowing() {
        System.out.println("============before throwing");
        throw new RuntimeException();
    }
    // 新添加的方法,后置异常通知=============================

    // 新添加的方法,后置最终通知=============================
    @Override
    public boolean sayAfterFinally() {
        System.out.println("============before finally");
        throw new RuntimeException();
    }
    // 新添加的方法,后置最终通知=============================

    // 新添加的方法,环绕通知=============================
    @Override
    public void sayAround(String param) {
        System.out.println("============around param:" + param);
    }
    // 新添加的方法,环绕通知=============================
}

第三在com.nieshenkuan.aop. HelloWorldAspect定义通知实现:

package com.nieshenkuan.aop;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 定义切面支持类 有了目标类,该定义切面了,切面就是通知和切入点的组合,而切面是通过配置方式定义的,
 * 因此这定义切面前,我们需要定义切面支持类,切面支持类提供了通知实现
 * 
 * @author NSK
 *
 */
public class HelloWorldAspect {
    // 前置通知
    public void beforeAdvice() {
        System.out.println("===========before advice");
    }

    // 后置最终通知
    public void afterFinallyAdvice() {
        System.out.println("===========after finally advice");
    }

    // 新添加的方法=====================================
    public void beforeAdvice(String param) {
        System.out.println("===========before advice param:" + param);
    }
    // 新添加的方法=================================================

    // 新添加的方法,后置通知=================================================
    public void afterReturningAdvice(Object retVal) {
        System.out.println("===========after returning advice retVal:" + retVal);
    }

    // 新添加的方法,后置通知=================================================

    // 新添加的方法,后置异常通知=============================
    public void afterThrowingAdvice(Exception exception) {
        System.out.println("===========after throwing advice exception:" + exception);
    }
    // 新添加的方法,后置异常通知=============================

    // 新添加的方法,环绕通知=============================
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("===========around before advice");
        Object retVal = pjp.proceed(new Object[] { "replace" });
        System.out.println("===========around after advice");
        return retVal;
    }
    // 新添加的方法,环绕通知=============================
}

最后在applicationContext4.xml配置文件中接着前置通知配置的例子添加如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.2.xsd 
                    http://www.springframework.org/schema/tx 
                    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>


    <bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
    <aop:config>
        <aop:aspect ref="aspect">
            <!-- 前置通知 -->
            <aop:before
                pointcut="execution(* com.nieshenkuan..*.sayBefore(..))  and args(param)"
                method="beforeAdvice(java.lang.String)" arg-names="param" />
                
            <!-- 后置异常通知================================= -->
            <aop:after-throwing
                pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
                method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
            <!-- 后置异常通知================================= -->
            
            <!-- 后置通知============================== -->
            <aop:after-returning
                pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
                method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
            <!-- 后置通知============================== -->
            
            <!-- 后置最终通知========================== -->
            <aop:after pointcut="execution(* com.nieshenkuan..*.sayAfterFinally(..))"
                method="afterFinallyAdvice" />
            <!-- 后置最终通知 ======================== -->
            
            <!-- 环绕通知 ======================== -->
            <aop:around pointcut="execution(* com.nieshenkuan..*.sayAround(..))" 
           method="aroundAdvice"/>
           <!-- 环绕通知 ======================== -->
        </aop:aspect>

    </aop:config>
</beans>

测试代码Test.java

/**
     * 环绕通知
     */
    @org.junit.Test
    public void testSchemaAroundAdvice() {
        System.out.println("======================================");
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext4.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayAround("haha");
        System.out.println("======================================");
    }

输出:

======================================
===========around before advice
============around param:replace
===========around after advice
======================================

分析一下吧:
1)切入点匹配:在配置中使用“execution(* com.nieshenkuan..*.sayAround(..))”匹配目标方法sayAround;
2)目标方法定义:使用method="aroundAdvice"指定环绕通知实现方法,在该实现中,第一个方法参数为pjp,类型为ProceedingJoinPoint,其中“Object retVal = pjp.proceed(new Object[] {"replace"});”,用于执行目标方法,且目标方法参数被“new Object[] {"replace"}”替换,最后返回“retVal ”返回值。
3)测试:我们使用“helloworldService.sayAround("haha");”传入参数为“haha”,但最终输出为“replace”,说明参数被替换了。

3.4 、引入

Spring引入允许为目标对象引入新的接口,通过在< aop:aspect>标签内使用< aop:declare-parents>标签进行引入,定义方式如下:

<aop:declare-parents
types-matching="AspectJ语法类型表达式"     
implement-interface=引入的接口"                          
  default-impl="引入接口的默认实现" 
delegate-ref="引入接口的默认实现Bean引用"/>
package com.nieshenkuan.aop;
/**
 * 被引入的接口
 * @author NSK
 *
 */
public interface IIntroductionService {
    void induct();
}
package com.nieshenkuan.aop;

/**
 * 被引入接口的实现
 * 
 * @author NSK
 *
 */
public class IntroductiondServiceImpl implements IIntroductionService {

    @Override
    public void induct() {
        System.out.println("=========introduction");
    }

}

其次在applicationContext5.xml配置文件中接着前置通知配置的例子添加如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.2.xsd 
                    http://www.springframework.org/schema/tx 
                    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>


    <bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>
    
    <!-- 使用CGLIB代理创建代理对象,默认使用JDK代理 -->
    <aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
    
    <aop:config>
        <aop:aspect ref="aspect">
            <!-- 前置通知 -->
            <aop:before
                pointcut="execution(* com.nieshenkuan..*.sayBefore(..))  and args(param)"
                method="beforeAdvice(java.lang.String)" arg-names="param" />

            <aop:declare-parents types-matching="com.nieshenkuan..*.IHelloWorldService+"
                implement-interface="com.nieshenkuan.aop.IIntroductionService"
                default-impl="com.nieshenkuan.aop.IntroductiondServiceImpl" />
                
            <!-- 后置异常通知================================= -->
            <aop:after-throwing
                pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
                method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
            <!-- 后置异常通知================================= -->

            <!-- 后置通知============================== -->
            <aop:after-returning
                pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
                method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
            <!-- 后置通知============================== -->

            <!-- 后置最终通知========================== -->
            <aop:after pointcut="execution(* com.nieshenkuan..*.sayAfterFinally(..))"
                method="afterFinallyAdvice" />
            <!-- 后置最终通知 ======================== -->

            <!-- 环绕通知 ======================== -->
            <aop:around pointcut="execution(* com.nieshenkuan..*.sayAround(..))"
                method="aroundAdvice" />
            <!-- 环绕通知 ======================== -->
        </aop:aspect>
    </aop:config>
    
</beans>

这里要注意一下,把代理改一下:不然得不到结果。。。。

<!-- 使用CGLIB代理创建代理对象,默认使用JDK代理 -->
    <aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>

测试代码:Test.java

/**
     * 引入测试
     */
    @org.junit.Test
    public void testSchemaIntroduction() {
        System.out.println("======================================");
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext5.xml");
        IIntroductionService introductionService = ctx.getBean("helloWorldService", IIntroductionService.class);
        introductionService.induct();
        System.out.println("======================================");
    }

结果:

======================================
=========introduction
======================================

分析一下吧:
1)目标对象类型匹配:使用types-matching="com.nieshenkuan..*.IHelloWorldService+"匹配IHelloWorldService接口的子类型,如HelloWorldService实现;
2)引入接口定义:通过implement-interface属性表示引入的接口,如“com.nieshenkuan.aop.IIntroductionService”。
3)引入接口的实现:通过default-impl属性指定,如“com.nieshenkuan.aop.IntroductiondServiceImpl”,也可以使用“delegate-ref”来指定实现的Bean。
4)获取引入接口:如使用“ctx.getBean("helloWorldService", IIntroductionService.class);”可直接获取到引入的接口。

3.5、 Advisor

Advisor表示只有一个通知和一个切入点的切面,由于Spring AOP都是基于AOP联盟的拦截器模型的环绕通知的,所以引入Advisor来支持各种通知类型(如前置通知等5种),Advisor概念来自于Spring1.2对AOP的支持,在AspectJ中没有相应的概念对应。
Advisor可以使用<aop:config>标签下的<aop:advisor>标签定义:

<aop:advisor pointcut="切入点表达式" pointcut-ref="切入点Bean引用"
advice-ref="通知API实现引用"/>

接下来让我们看一下示例吧:
首先在com.nieshenkuan.aop.IHelloWorldService定义一个测试方法:

package com.nieshenkuan.aop;

/**
 * 定义目标接口:创建被代理的接口
 * 
 * @author NSK
 *
 */
public interface IHelloWorldService {

    void sayHello();

    // 新添加的方法,前置通知=================================================
    void sayBefore(String param);
    // 新添加的方法,前置通知=================================================

    // 新添加的方法,后置通知=============================
    boolean sayAfterReturning();
    // 新添加的方法,后置通知=============================

    // 新添加的方法,后置异常通知=============================
    void sayAfterThrowing();
    // 新添加的方法,后置异常通知=============================

    // 新添加的方法,后置最终通知=============================
    boolean sayAfterFinally();
    // 新添加的方法,后置最终通知=============================

    // 新添加的方法,环绕通知=============================
    void sayAround(String param);
    // 新添加的方法,环绕通知=============================

    // advisor测试
    void sayAdvisorBefore(String param);
    // advisor测试
}

其次在com.nieshenkuan.aop. HelloWorldService定义实现:

package com.nieshenkuan.aop;

/**
 * 定义目标接口实现:接口的实现类
 * 
 * @author NSK
 *
 */
public class HelloWorldService implements IHelloWorldService {

    @Override
    public void sayHello() {
        System.out.println("============Hello World!");

    }

    // 新添加的方法=================================================
    @Override
    public void sayBefore(String param) {
        System.out.println("============say " + param);

    }
    // 新添加的方法=================================================

    // 新添加的方法,后置通知=============================
    @Override
    public boolean sayAfterReturning() {
        System.out.println("============after returning");
        return true;
    }
    // 新添加的方法,后置通知=============================

    // 新添加的方法,后置异常通知=============================
    @Override
    public void sayAfterThrowing() {
        System.out.println("============before throwing");
        throw new RuntimeException();
    }
    // 新添加的方法,后置异常通知=============================

    // 新添加的方法,后置最终通知=============================
    @Override
    public boolean sayAfterFinally() {
        System.out.println("============before finally");
        throw new RuntimeException();
    }
    // 新添加的方法,后置最终通知=============================

    // 新添加的方法,环绕通知=============================
    @Override
    public void sayAround(String param) {
        System.out.println("============around param:" + param);
    }
    // 新添加的方法,环绕通知=============================

    // advisor测试
    @Override
    public void sayAdvisorBefore(String param) {
        System.out.println("============say " + param);
    }
    // advisor测试
}

第三定义前置通知API实现:

package com.nieshenkuan.aop;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class BeforeAdviceImpl implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("===========before advice");
    }

}

在applicationContext6.xml配置文件中先添加通知实现Bean定义:

<!-- 定义bean -->
<bean id="beforeAdvice" class="com.nieshenkuan.aop.BeforeAdviceImpl" />

然后在<aop:config>标签下,添加Advisor定义,添加时注意顺序:

<aop:advisor pointcut="execution(* com.nieshenkuan..*.sayAdvisorBefore(..))"
            advice-ref="beforeAdvice" />

整个applicationContext6.xml的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
                    http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                    http://www.springframework.org/schema/context 
                    http://www.springframework.org/schema/context/spring-context-3.2.xsd 
                    http://www.springframework.org/schema/tx 
                    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    <bean id="helloWorldService" class="com.nieshenkuan.aop.HelloWorldService"></bean>

    <!-- 定义bean -->
    <bean id="beforeAdvice" class="com.nieshenkuan.aop.BeforeAdviceImpl" />

    <bean id="aspect" class="com.nieshenkuan.aop.HelloWorldAspect"></bean>

    <!-- 使用CGLIB代理创建代理对象,默认使用JDK代理 -->
    <aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>

    <aop:config>

        <aop:advisor pointcut="execution(* com.nieshenkuan..*.sayAdvisorBefore(..))"
            advice-ref="beforeAdvice" />

        <aop:aspect ref="aspect">
            <!-- 前置通知 -->
            <aop:before
                pointcut="execution(* com.nieshenkuan..*.sayBefore(..))  and args(param)"
                method="beforeAdvice(java.lang.String)" arg-names="param" />

            <!-- 引入接口================================== -->
            <aop:declare-parents types-matching="com.nieshenkuan..*.IHelloWorldService+"
                implement-interface="com.nieshenkuan.aop.IIntroductionService"
                default-impl="com.nieshenkuan.aop.IntroductiondServiceImpl" />
            <!-- 引入接口================================== -->

            <!-- 后置异常通知================================= -->
            <aop:after-throwing
                pointcut="execution(* com.nieshenkuan..*.sayAfterThrowing(..))"
                method="afterThrowingAdvice" arg-names="exception" throwing="exception" />
            <!-- 后置异常通知================================= -->

            <!-- 后置通知============================== -->
            <aop:after-returning
                pointcut="execution(* com.nieshenkuan..*.sayAfterReturning(..))"
                method="afterReturningAdvice" arg-names="retVal" returning="retVal" />
            <!-- 后置通知============================== -->

            <!-- 后置最终通知========================== -->
            <aop:after pointcut="execution(* com.nieshenkuan..*.sayAfterFinally(..))"
                method="afterFinallyAdvice" />
            <!-- 后置最终通知 ======================== -->

            <!-- 环绕通知 ======================== -->
            <aop:around pointcut="execution(* com.nieshenkuan..*.sayAround(..))"
                method="aroundAdvice" />
            <!-- 环绕通知 ======================== -->
        </aop:aspect>
    </aop:config>

</beans>

测试代码:Test.java

    @org.junit.Test
    public void testSchemaAdvisor() {
        System.out.println("======================================");
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext6.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayAdvisorBefore("haha");
        System.out.println("======================================");
    }

结果:

======================================
===========before advice
============say haha
======================================

在此我们只介绍了前置通知API,其他类型的在后边章节介绍。
不推荐使用Advisor,除了在进行事务控制的情况下,其他情况一般不推荐使用该方式,该方式属于侵入式设计,必须实现通知API。
未完.........

上一篇下一篇

猜你喜欢

热点阅读