开涛spring3(6.3) - AOP 之 6.3 基于Schema的AOP

6.3  基于Schema的AOP

基于Schema的AOP从Spring2.0之后通过“aop”命名空间来定义切面、切入点及声明通知。

在Spring配置文件中,所以AOP相关定义必须放在<aop:config>标签下,该标签下可以 有<aop:pointcut>、<aop:advisor>、<aop:aspect>标签,配置顺序不可变。

  • <aop:pointcut>:用来定义切入点,该切入点可以重用;
  • <aop:advisor>:用来定义只有一个通知和一个切入点的切面;
  • <aop:aspect>:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。

6.3.1  声明切面

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

切面使用<aop:aspect>标签指定,ref属性用来引用切面支持Bean。

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

6.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(* cn.javass..*.*(..))"/>
     <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(* cn.javass..*.*(..))"/>
        <aop:before pointcut-ref="pointcut" method="before"/>
     </aop:aspect>
    </aop:config>  

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

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

6.3.3  声明通知

基于Schema方式支持前边介绍的5中通知类型:

一、前置通知:在切入点选择的方法之前执行,通过<aop:aspect>标签下的<aop:before>标签声明:

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

pointcut和pointcut-ref二者选一,指定切入点;

method指定前置通知实现方法名,如果是多态需要加上参数类型,多个用“,”隔开,如beforeAdvice(java.lang.String);

arg-names指定通知实现方法的参数名字,多个用“,”分隔,可选,类似于【3.1.2 构造器注入】中的参数名注入限制:class文件中没生成变量调试信息是获取不到方法参数名字的,因此只有在类没生成变量调试信息时才需要使用arg-names属性来指定参数名,如arg-names="param"表示通知实现方法的参数列表的第一个参数名字为“param”。

首先在IhelloWorldService定义一个测试方法:

    public void sayBefore(String param);  

其次在HelloWorldService定义实现

public void sayBefore(String param) {
    System.out.println("============say " + param);
}  

第三在HelloWorldAspect定义通知实现:

public void beforeAdvice(String param) {
    System.out.println("===========before advice param:" + param);
} 

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

<bean id="helloWorldService" class="cn.javass.spring.chapter6.service.impl.HelloWorldService"/>
<bean id="aspect" class="cn.javass.spring.chapter6.aop.HelloWorldAspect"/>
<aop:config>
    <aop:aspect ref="aspect">
        <aop:before pointcut="execution(* lqy.springh6_2..*.sayBefore(..)) and args(param)"
                           method="beforeAdvice(java.lang.String)"
                           arg-names="param"/>
    </aop:aspect>
</aop:config> 

测试

     IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
     helloworldService.sayBefore("before");  

分析一下吧:

1)切入点匹配:在配置中使用“execution(* lqy.springh6_2..*.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="返回值对应的后置返回通知实现方法参数名"
    />  

pointcut和pointcut-ref同前置通知同义;

method同前置通知同义;

arg-names同前置通知同义;

returning定 义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法执行正常返回后,将把目标方法返回值传给通知方法;returning限定了只有目标方 法返回值匹配与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返 回值。

1.测试

package lqy.springh6_2;

public interface IHelloWorldService {  

    public boolean sayAfterReturning();

}  
package lqy.springh6_2;

public class HelloWorldService implements IHelloWorldService {  

    public boolean sayAfterReturning() {
        System.out.println("============after returning");
        return true;
    } 

}
package lqy.springh6_2;

public class HelloWorldAspect {  

 public void afterReturningAdvice(Object retVal) {
        System.out.println("===========after returning advice retVal:" + retVal);
    } 

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

           <bean id="helloWorldService"  class="lqy.springh6_2.HelloWorldService"/>  

<bean id="aspect" class="lqy.springh6_2.HelloWorldAspect"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* lqy.springh6_2..*.*(..))"/>
    <aop:aspect ref="aspect">
        <aop:after-returning pointcut="execution(* lqy.springh6_2..*.sayAfterReturning(..))"
                                method="afterReturningAdvice"
                               arg-names="retVal"
                               returning="retVal"/>
    </aop:aspect>
</aop:config> 

</beans>  

测试一下

package lqy.springh6_2;

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

public class test2 {

    /**
     * @param args
     */
    public static void main(String[] args) {
        ApplicationContext ctx =  new ClassPathXmlApplicationContext("lqy/springh6_2/springh6_2.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayAfterReturning();     

    }

}

分析一下吧:

1)切入点匹配:在配置中使用“execution(* cn.javass..*.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="将抛出的异常赋值给的通知实现方法参数名"/>

pointcut和pointcut-ref同前置通知同义;

method同前置通知同义;

arg-names同前置通知同义;

throwing定 义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;throwing限定了只有目标 方法抛出的异常匹配与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,对于throwing对应的通知方法参数为Throwable类型将 匹配任何异常。

package lqy.springh6_2;
public interface IHelloWorldService {
    public void sayAfterThrowing();
}  
package lqy.springh6_2;

public class HelloWorldService implements IHelloWorldService {
    public void sayAfterThrowing() {
        System.out.println("============before throwing");
        throw new RuntimeException(); 
    }
}
package lqy.springh6_2;
public class HelloWorldAspect {
    public void afterThrowingAdvice(Exception exception) {
          System.out.println("===========after throwing advice exception:" + exception);
        }
}
<?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-3.0.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">  

           <bean id="helloWorldService"  class="lqy.springh6_2.HelloWorldService"/>  

<bean id="aspect" class="lqy.springh6_2.HelloWorldAspect"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* lqy.springh6_2..*.*(..))"/>
    <aop:aspect ref="aspect">  

    <aop:after-throwing pointcut="execution(* lqy.springh6_2..*.sayAfterThrowing(..))"
                                    method="afterThrowingAdvice"
                                    arg-names="exception"
                                    throwing="exception"/>  

    </aop:aspect>
</aop:config> 

</beans>  
    public static void main(String[] args) {
        ApplicationContext ctx =  new ClassPathXmlApplicationContext("lqy/springh6_2/springh6_2.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayAfterThrowing();     

    }

分析一下吧:

1)切入点匹配:在配置中使用“execution(* cn.javass..*.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同前置通知同义;

package lqy.springh6_2;
public interface IHelloWorldService {
    public boolean sayAfterFinally();
}  
package lqy.springh6_2;

public class HelloWorldService implements IHelloWorldService {  

public boolean sayAfterFinally() {
        System.out.println("============before finally");
        throw new RuntimeException();
}
}
package lqy.springh6_2;
public class HelloWorldAspect {
    public void afterFinallyAdvice() {
        System.out.println("===========after finally advice");
}
}
<?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-3.0.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">  

           <bean id="helloWorldService"  class="lqy.springh6_2.HelloWorldService"/>  

<bean id="aspect" class="lqy.springh6_2.HelloWorldAspect"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* lqy.springh6_2..*.*(..))"/>
    <aop:aspect ref="aspect">  

    <aop:after pointcut="execution(* lqy.springh6_2..*.sayAfterFinally(..))"
             method="afterFinallyAdvice"/>  

    </aop:aspect>
</aop:config> 

</beans>  
    public static void main(String[] args) {
        ApplicationContext ctx =  new ClassPathXmlApplicationContext("lqy/springh6_2/springh6_2.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);
        helloworldService.sayAfterFinally();     

    }

分析一下吧:

1)切入点匹配:在配置中使用“execution(* cn.javass..*.sayAfterFinally(..))”匹配目标方法sayAfterFinally,该方法将抛出RuntimeException异常;

2)目标方法定义:使用method=" afterFinallyAdvice "指定后置最终通知实现方法。

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

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

pointcut和pointcut-ref同前置通知同义;

method同前置通知同义;

arg-names同前置通知同义;

环 绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型,在通知实现方法内部使用 ProceedingJoinPoint的proceed()方法使目标方法执行,proceed 方法可以传入可选的Object[]数组,该数组的值将被作为目标方法执行时的参数。

package lqy.springh6_2;
public interface IHelloWorldService {  

    public void sayAround(String param);  

}  
package lqy.springh6_2;

public class HelloWorldService implements IHelloWorldService {  

    public void sayAround(String param) {
           System.out.println("============around param:" + param);
        }
}
package lqy.springh6_2;

import org.aspectj.lang.ProceedingJoinPoint;

public class HelloWorldAspect {
    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;
    }
}
<?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-3.0.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">  

           <bean id="helloWorldService"  class="lqy.springh6_2.HelloWorldService"/>  

<bean id="aspect" class="lqy.springh6_2.HelloWorldAspect"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* lqy.springh6_2..*.*(..))"/>
    <aop:aspect ref="aspect">  

    <aop:around pointcut="execution(* lqy.springh6_2..*.sayAround(..))"
           method="aroundAdvice"/> 

    </aop:aspect>
</aop:config> 

</beans>  
package lqy.springh6_2;

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

public class test2 {

    /**
     * @param args
     */
    public static void main(String[] args) {
        ApplicationContext ctx =  new ClassPathXmlApplicationContext("lqy/springh6_2/springh6_2.xml");
        IHelloWorldService helloworldService = ctx.getBean("helloWorldService", IHelloWorldService.class);   

        helloworldService.sayAround("haha");    

    }

}

分析一下吧:

1)切入点匹配:在配置中使用“execution(* lqy.springh6_2..*.sayAround(..))”匹配目标方法sayAround;

2)目标方法定义:使 用method="aroundAdvice"指定环绕通知实现方法,在该实现中,第一个方法参数为pjp,类型为 ProceedingJoinPoint,其中“Object retVal = pjp.proceed(new Object[] {"replace"});”,用于执行目标方法,且目标方法参数被“new Object[] {"replace"}”替换,最后返回“retVal ”返回值。

3)测试:我们使用“helloworldService.sayAround("haha");”传入参数为“haha”,但最终输出为“replace”,说明参数被替换了。

若改成

Object retVal = pjp.proceed();

不再替换了

6.3.4  引入

Spring引入允许为目标对象引入新的接口,通过在< aop:aspect>标签内使用< aop:declare-parents>标签进行引入,省略。

6.3.5 Advisor

不推荐使用Advisor,除了在进行事务控制的情况下,其他情况一般不推荐使用该方式,该方式属于侵入式设计,必须实现通知API。

时间: 2024-08-08 18:44:12

开涛spring3(6.3) - AOP 之 6.3 基于Schema的AOP的相关文章

spring aop 基于schema的aop

AOP的基本概念: 连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化.方法执行.方法调用.字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP中表示为"在哪里干": 切入点(Pointcut):选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为"在哪里干的集合":(选取我们所需要的连接点的集

开涛spring3(6.4) - AOP 之 6.4 基于@AspectJ的AOP

Spring除了支持Schema方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明. 6.4.1  启用对@AspectJ的支持 Spring默认不支持@AspectJ风格的切面声明,为了支持需要使用如下配置: <aop:aspectj-autoproxy/> 这样Spring就能发现@AspectJ风格的切面并且将切面应用到目标对象. 6.4.2  声明切面 @AspectJ风格的声明切面非常简单,使用@Aspect注解进行声明: @Aspect() Public class

开涛spring3(6.2) - AOP 之 6.2 AOP的HelloWorld

6.2.1  准备环境 首先准备开发需要的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 目前,项目里包是 6.2.2  定义目标类 1)定义目标接口: package lqy

开涛spring3(12.3) - 零配置 之 12.3 注解实现Bean定义

12.3  注解实现Bean定义 12.3.1  概述 前边介绍的Bean定义全是基于XML方式定义配置元数据,且在[12.2注解实现Bean依赖注入]一节中介绍了通过注解来减少配置数量,但并没有完全消除在XML配置文件中的Bean定义,因此有没有方式完全消除XML配置Bean定义呢? Spring提供通过扫描类路径中的特殊注解类来自动注册 Bean定义.同注解驱动事务一样需要开启自动扫描并注册Bean定义支持,使用方式如下(resources/chapter12/ componentDefin

开涛spring3(6.1) - AOP 之 6.1 AOP基础

6.1.1  AOP是什么 考虑这样一个问题:需要对系统中的某些业务做日志记录,比如支付系统中的支付业务需要记录支付相关日志,对于支付系统可能相当复杂,比如可能有自己的支付系统,也可能引入第三方支付平台,面对这样的支付系统该如何解决呢? 传统解决方: 1)日志部分提前公共类LogUtils,定义“longPayBegin”方法用于记录支付开始日志,“logPayEnd”用于记录支付结果: 2)支付部分,定义IPayService接口并定义支付方法“pay”,并定义了两个实现:“PointPayS

开涛spring3(6.6) - AOP 之 6.6 通知参数

前边章节已经介绍了声明通知,但如果想获取被被通知方法参数并传递给通知方法,该如何实现呢?接下来我们将介绍两种获取通知参数的方式. 使用JoinPoint获取:Spring AOP提供使用org.aspectj.lang.JoinPoint类型获取连接点数据,任何通知方法的第一个参数都可以是JoinPoint(环绕通 知是ProceedingJoinPoint,JoinPoint子类),当然第一个参数位置也可以是JoinPoint.StaticPart类型,这 个只返回连接点的静态部分. 1) J

开涛spring3(6.8) - AOP 之 6.8 切面实例化模型

所谓切面实例化模型指何时实例化切面. Spring AOP支持AspectJ的singleton.perthis.pertarget实例化模型(目前不支持percflow.percflowbelow 和pertypewithin). singleton:即切面只会有一个实例: perthis:每个切入点表达式匹配的连接点对应的AOP对象都会创建一个新切面实例: pertarget:每个切入点表达式匹配的连接点对应的目标对象都会创建一个新的切面实例: 默认是singleton实例化模型,Schem

开涛spring3(6.9) - AOP 之 6.9 代理机制

Spring AOP通过代理模式实现,目前支持两种代理:JDK动态代理.CGLIB代理来创建AOP代理,Spring建议优先使用JDK动态代理. JDK动态代理:使用java.lang.reflect.Proxy动态代理实现,即提取目标对象的接口,然后对接口创建AOP代理. CGLIB代理:CGLIB代理不仅能进行接口代理,也能进行类代理,CGLIB代理需要注意以下问题: 不能通知final方法,因为final方法不能被覆盖(CGLIB通过生成子类来创建代理). 会产生两次构造器调用,第一次是目

开涛spring3(6.7) - AOP 之 6.7 通知顺序

如果我们有多个通知想要在同一连接点执行,那执行顺序如何确定呢?Spring AOP使用AspectJ的优先级规则来确定通知执行顺序.总共有两种情况:同一切面中通知执行顺序.不同切面中的通知执行顺序. 首先让我们看下 1) 同一切面中通知执行顺序:如图6-6所示. 图6-6 同一切面中的通知执行顺序 而如果在同一切面中定义两个相同类型通知(如同是前置通知或环绕通知(proceed之前))并在同一连接点执行时,其执行顺序是未知的,如果确实需要指定执行顺序需要将通知重构到两个切面,然后定义切面的执行顺