zoukankan      html  css  js  c++  java
  • [Spring实战系列](17)编写切点与声明切面



    切点用于准确定位应该在什么地方应用切面的通知。

    切点和通知是切面的最基本元素。


    在Spring AOP中,须要使用AspectJ的切点表达式语言来定义切点。

    关于Spring AOP的AspectJ切点,最重要的一点是Spring仅支持AspectJ切点指示器的一个子集。

    类型 说明
    arg() 限制连接点匹配參数为指定类型的运行方法

    @args() 限制连接点匹配參数由指定注解标注的运行方法

    execution() 用于匹配的是连接点的运行方法

    this() 限制连接点匹配AOP代理的Bean引用为指定类型的类。
    target() 限制连接点匹配目标对象为指定类型的类。

    @target() 限制连接点匹配特定的运行对象,这些对象相应的类具备指定类型的注解。
    within() 限制连接点匹配指定的类型。
    @within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时。方法定义再由指定的注解所标注的类里)。
    @annotation 限制匹配带有指定注解连接点。


    在Spring中尝试使用AspectJ其它指示器时,将会抛出IllegalArgumentException异常。


    注意:

    仅仅有execution指示器是唯一的运行匹配。而其它的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最基本的指示器。在此基础上,我们使用其它指示器来限制所匹配的切点。

    1. 编写切点

    例如以下图所看到的的切点表达式表示当Singer的perform()方法运行时会触发通知。我们使用execution()指示器选择Singer的perform()方法。

    方法表达式以*号開始,标示了我们不关心返回值的类型。然后,我们指定了全限定类名和方法名

    对于方法參数列表。我们使用(..)标示切点选择随意的perform()方法,不管该方法的參数是什么

    QQ截图20160212201528.png

    除此之外,我们还能够对上面的匹配进行限制,能够使用within()指示器来限制匹配
    QQ截图20160212202634.png

    我们使用&&操作符把execution()和within()指示器连接在一起形成and关系(切点必须匹配全部的指示器)。

    2. 在XML中声明切面

    Spring的AOP配置元素简化了基于POJO切面的声明:
    类型 说明
    <aop:advisor> 定义AOP通知器。

    <aop:after> 定义AOP后置通知(无论被通知的方法是否运行成功)
    <aop:after-returning> 定义AOP after-returning通知。
    <aop:after-throwing> 定义 AOP after-throwing 通知。
    <aop:around> 定义 AOP 围绕通知。
    <aop:aspect> 定义切面。
    <aop:aspectj-autoproxy> 启用@AspectJ注解驱动的切面。
    <aop:before> 定义 AOP前置通知。
    <aop:config> 顶层的AOP配置元素。

    大多数的<aop:*>元素必须包括在<aop:config>元素内。

    <aop:declare-parents> 为被通知的对象引入额外的接口,并透明的实现。
    <aop:pointcut> 定义切点。

    为了阐述Spring AOP,我们创建一个观众类(Audience)类:
    package com.sjf.bean;
    /**
    * 观众类
    * @author sjf0115
    *
    */
    public class Audience {
    public void takeSeats(){
    System.out.println("the audience is taking their seats...");
    }
    public void applaud(){
    System.out.println("very good, clap clap clap...");
    }
    public void demandRefund(){
    System.out.println("very bad, We want our money back...");
    }
    }

    Audience类并没有不论什么特别之处,她就是一个有几个方法的简单Java类。我们能够像其它类一样。利用XML把它注冊为Spring应用上下文中的一个Bean:
    <bean id = "audience" class = "com.sjf.bean.Audience">
    </bean>
    我们须要Spring AOP就能把它成为一个切面。
    2.1 前置声明和后置声明

    <?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/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
    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">
    <bean id = "singer" class = "com.sjf.bean.Singer">
    </bean>
    <bean id = "audience" class = "com.sjf.bean.Audience">
    </bean>
    <aop:config proxy-target-class="true">
    <!-- 声明定义一个切面 -->
    <aop:aspect ref = "audience">
    <!-- 表演之前 -->
    <aop:before method="takeSeats" pointcut="execution(* com.sjf.bean.Singer.perform(..))"/>
    <!-- 表演之后 -->
    <aop:after-returning method="applaud" pointcut="execution(* com.sjf.bean.Singer.perform(..))"/>
    <!-- 表演失败之后 -->
    <aop:after-throwing method="demandRefund" pointcut="execution(* com.sjf.bean.Singer.perform(..))"/>
    </aop:aspect>
    </aop:config>
    </beans>
    大多数的AOP配置元素必须在<aop:config>元素的上下文内使用。

    这条规则有几种例外场景,可是把Bean声明为一个切面时,我们总是从<aop:config>元素開始配置。


    在<aop:config>元素内。我们能够声明一个或者多个通知器,切面或者切点。上述样例中,我们使用<aop:aspect>元素声明了一个简单的切面。

    ref元素引用了一个Bean(Audience),该Bean实现了切面的功能。ref元素应用的Bean提供了在切面上通知所调用的方法。


    该切面应用了3个不同的通知。

    <aop:before>元素定义了匹配切点的方法运行之前调用前置通知方法,audience Bean 的takeSeats()方法。<aop:after-returning>元素定义了一个返回后(after-returning)通知,在切点所匹配的方法调用之后在运行applaud()方法。<aop:after-throwing>元素定义了抛出异常后通知,假设全部匹配的方法运行时抛出不论什么异常,都将调用demandRefund()方法。


    以下展示了通知逻辑怎样嵌入到业务逻辑中:
    QQ截图20160211221047.png

    在全部的通知元素中,pointcut属性定义了通知所应用的切点。pointcut属性的值是使用AspectJ切点表达式语法所定义的切点。

    你也许注意到全部通知元素中的pointcut属性的值都是一样的。这是由于全部的通知都是应用到同样的切点上。

    这似乎违反了DRY(不要反复你自己)原则。

    我们做一下改动,能够使用<aop:pointcut>元素定义一个命名切点

    <aop:config proxy-target-class="true">
    <!-- 声明定义一个切面 -->
    <aop:aspect ref = "audience">
    <aop:pointcut id="singerPerfom" expression="execution(* com.sjf.bean.Singer.perform(..))" />
    <!-- 表演之前 -->
    <aop:before method="takeSeats" pointcut-ref="singerPerfom"/>
    <!-- 表演之后 -->
    <aop:after-returning method="applaud" pointcut-ref="singerPerfom"/>
    <!-- 表演失败之后 -->
    <aop:after-throwing method="demandRefund" pointcut-ref="singerPerfom"/>
    </aop:aspect>
    </aop:config>
    <aop:pointcut>元素定义了一个id为singerPerfom的切点,同一时候改动全部的通知元素,用pointcut-ref属性来引用这个命名切点

    2.2 声明围绕通知
    假设不适用成员变量存储信息,那么在前置通知和后置通知之间共享信息是很麻烦的。我们希望实现这样一个功能:希望观众一直关注演出,并报告演出的演出时长。

    使用前置通知和后置通知实现该功能的唯一方式是:在前置通知中记录開始时间。并在某个后置通知中报告演出的时长。但这种话,我们必须在一个成员变量中保存開始时间。

    因此我们能够使用围绕通知。仅仅需在一个方法中实现就可以。

    public void PerformTime(ProceedingJoinPoint joinPoint){
    // 演出之前
    System.out.println("the audience is taking their seats...");
    try {
    long start = System.currentTimeMillis();
    // 运行演出操作
    joinPoint.proceed();
    long end = System.currentTimeMillis();
    // 演出成功
    System.out.println("very good, clap clap clap...");
    System.out.println("该演出共须要 "+(end - start) + " milliseconds");
    } catch (Throwable e) {
    // 演出失败
    System.out.println("very bad, We want our money back...");
    e.printStackTrace();
    }
    }

    对于这个新的通知方法。我们会注意到它使用了ProceedingJoinPoint作为方法的入參。

    这个对象很重要。由于它能让我们在通知里调用被通知 的方法。假设希望把控制转给被通知的方法时,我们能够调用ProceedingJoinPoint的proceed()方法。假设忘记调用proceed()方法,通知将会阻止被通知方法的调用。我们还能够在通知里多次调用被通知方法。这样做的一个目的是实现重试逻辑,在被通知的方法运行失败时重复重试。


    PerformTime()方法包括了之前3个通知方法的全部逻辑,而且该方法还会负责自身的异常处理。

    声明围绕通知与声明其它类型的通知并没有太大的差别,仅仅须要<aop:around>元素

    <aop:config proxy-target-class="true">
    <!-- 声明定义一个切面 -->
    <aop:aspect ref = "audience">
    <aop:pointcut id="singerPerfom" expression="execution(* com.sjf.bean.Singer.perform(..))" />
    <!-- 声明围绕通知 -->
    <aop:around method="performTime" pointcut-ref="singerPerfom"/>
    </aop:aspect>
    </aop:config>

    执行结果:

    the audience is taking their seats...
    正在上演个人演唱会...
    very good, clap clap clap...
    该演出共须要 37 milliseconds  

    像其它通知的XML一样,<aop:around>指定了一个切点和一个通知方法的名字。
    2.3 为通知传递參数
    到眼下为止,我们的切面非常easy,没有不论什么的參数。

    可是有时候通知并非不过对方法进行简单包装,还须要校验传递给方法的參数值,这时候为通知传递參数就非常实用了。


    以下是歌手的实体类:
    package com.sjf.bean;
    /**
    * 歌手实体类
    * @author sjf0115
    *
    */
    public class Singer {
     
    public void perform(String songName) {
    System.out.println("正在上演个人演唱会,演唱曲目为 " + songName);
    }
    }
    在这我们提供了一个Organizers(主办方)实体类。在歌手演唱之前截获歌手演唱的曲目,然后通知给大家:
    package com.sjf.bean;
    /**
    * 主办方实体类
    * @author sjf0115
    *
    */
    public class Organizers {
    public void BeforeSong(String songName){
    System.out.println("演唱会立即就開始了,演唱歌曲为 " + songName);
    }
    public void AfterSong(){
    System.out.println("演唱曲目结束。谢谢大家...");
    }
    }

    我们像曾经一样使用<aop:aspect>。<aop:before>和<aop:after>元素。可是这次我们通过配置实现将被通知方法的參数传递给通知
    <?

    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/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
    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">
    <bean id = "singer" class = "com.sjf.bean.Singer">
    </bean>
    <bean id = "Organizers" class = "com.sjf.bean.Organizers">
    </bean>
    <aop:config>
    <!-- 声明定义一个切面 -->
    <aop:aspect ref = "Organizers">
    <aop:pointcut id="singerPerform" expression="execution(* com.sjf.bean.Singer.perform(String)) and args(song)" />
    <aop:pointcut id="singerPerform2" expression="execution(* com.sjf.bean.Singer.perform(..))" />
    <aop:before method="BeforeSong" pointcut-ref="singerPerform" arg-names="song"/>
    <aop:after-returning method="AfterSong" pointcut-ref="singerPerform2"/>
    </aop:aspect>
    </aop:config>
    </beans>

    关键之处在于切点定义和<aop:before>的arg-names属性

    切点标示了Singer的perform()方法。指定了String參数。

    然后在args參数中标示了song作为參数。相同,<aop:before>元素引用了切点中song參数,标示该參数必须传递给Organizers的BeforeSong()方法


    QQ截图20160212195008.png

    执行结果:

    演唱会立即就開始了,演唱歌曲为 你是我的眼泪
    正在上演个人演唱会。演唱曲目为 你是我的眼泪
    演唱曲目结束,谢谢大家...  


    来源于:《Spring实战》


  • 相关阅读:
    NSCalendar
    保护连接字符串
    System.Runtime.InteropServices.COMException: 拒绝访问.
    Windows 7 初体验
    Sql语句收藏
    因为WMI配置,无法执行Sql Server 系统配置检查器的解决办法
    url带中文参数显示乱码的问题
    想建立一个好的团队,有意者加我
    庆祝一下,早上的帖子上了24小时排行第三
    C/S结构和b/s结构的比较
  • 原文地址:https://www.cnblogs.com/brucemengbm/p/7089398.html
Copyright © 2011-2022 走看看