《Spring实战》学习笔记 - 第4章 面向切面的Spring

什么是面向切面编程

切面能过帮助我们模块化横切关注点(影响应用多处的功能)
每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能(例如: 安全和事务管理)

AOP术语

  • 通知(Advice):

    • 定义了切面是什么以及何时使用
    • 通知类型:
      • 前置通知: 在目标方法调用之前,调用通知功能
      • 后置通知: 在目标方法完成之后,调用通知功能
      • 返回通知: 在目标方法成功执行之后,调用通知功能
      • 异常通知: 在目标方法抛出异常后,调用通知功能
      • 环绕通知: 在目标方法调用之前和调用之后,调用通知功能
  • 连接点(Join point):

    • 是在应用执行过程中能够插入切面的一个点,切面可以利用这些点插入到应用的正常流程中,并添加新的行为
  • 切点(Pointcut):

    • 匹配通知所有要织入的一个或多个链接点,定义了何处执行行为
  • 切面(Aspect):

    • 横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)
    • 切面是通知和切面点的结合通知切点共同定义了切面的全部内容—它是什么,在何时和何处完成其功能
  • 引入(Introduction):

    • 向现有的类添加新方法或属性
  • 织入(Weaving):

    • 把切面应用到目标对象创建新的代理对象的过程。切面在指定的连接点被织入到目标对象
    • 在目标对象的生命周期有多个点可以进行织入:
      • 编译期: 切面在目标类编译时被织入,需要特殊的编译器
      • 类加载期: 切面在目标类加载到JVM时织入,需要特殊的类加载器
      • 运行期: 切面在应用运行的某个时刻被织入,AOP容器会动态创建代理对象

通过切点来选择链接点

Spring支持AspectJ切点指示器的一部分

编写切点

  • 定义接口

    // 代表现场表演
    public interface Performance {
        public void perform();
    }
  • 使用execution()指示器

    execution(* concert.Performance.perform(..))

  • 使用within()指示器限制切点范围

    execution(* concert.Performance.perform(..) && within(concert.*))

    • 操作符:
      • &&: and
      • ||: or
      • !: not

在切点中选择bean

  • Spring中有bean()指示器,通过bean的ID来标识bean

    execution(* concert.Performance.perform() and bean('example'))

使用注解创建切面

定义切面

  • 示例: 定义观众类

    @Aspect
    public class Audience {
        // 表演前手机静音
        @Before("execution(** concert.Performance.perform(..))")
        public void silenceCellPhones() {
            System.out.println("Silencing cell phones");
        }
        // 表演前入座
        @Before("execution(** concert.Performance.perform(..))")
        public void takeSeats() {
            System.out.println("Taking seats");
        }   
        // 表演后鼓掌
        @AfterReturning("execution(** concert.Performance.perform(..))")
        public void applause() {
            System.out.println("CLAP CLAP CLAP");
        }
        // 表演出问题退款
        @AfterThrowing("execution(** concert.Performance.perform())")
        public void demandRefund() {
            System.out.println("Demanding a refund");       
        }
    }
    • @Aspect表示该类为一个切面
    • 注解及对应通知方式:
      • @Before: 前置通知
      • @After: 后置通知
      • @AfterReturning: 返回通知
      • @AfterThrowing: 异常通知
      • @Around: 环绕通知
  • 使用@Pointcut注解来定义可重用的切点

    @Aspect
    public class Audience {
        // 定义命名的切点
        @Pointcut("execution(** concert.Performance.perform(..))")
        public void performance() {}
    
        @Before("performance()")
        public void silenceCellPhones() {
            System.out.println("Silencing cell phones");
        }   
        @Before("performance()")
        public void takeSeats() {
            System.out.println("Taking seats");
        }   
        @AfterReturning("performance()")
        public void applause() {
            System.out.println("CLAP CLAP CLAP");
        }   
        @AfterThrowing("performance()")
        public void demandRefund() {
            System.out.println("Demanding a refund");       
        }
    }
    • performance()方法的实际内容并不重要,只是作为一个标识
    • 除了注解和没有实际操做的performance()Audience类依然是一个POJO。能够正常调用其方法,可以独立的进行单元测试,也能够装配为bean
    • 将切面装配为bean时,需要在JavaConfig类中启用自动代理功能

      @Configuration
      @EnableAspectJAutoProxy
      @ComponentScan
      public class ConcertConfig {
          @Bean
          public Audience audience() {
              return new Audience();
          }
      }
    • 或者通过XML配置文件启动

      <!-- 需要在beans标签中声明aop命名空间 -->
      <context:component-scan base-package="concert"/>
      <aop:aspectj-autoproxy/> <!-- 启动AspecJ自动代理 -->
      <bean class="concert.Audience"/> <!-- 声明Audience bean -->
    • AspectJ自动代理会为使用@Aspect注解的bean创建一个代理

创建环绕通知

  • 环绕通知是最强大的通知类型,能够让编写的逻辑将被通知的目标完全包装起来
  • 示例:

    @Aspect
    public class Audience {
        @Pointcut("execution(** concert.Performance.perform(..))")
        public void performance() {}
    
        @Around("performance()")
        public void watchPerformance(ProceedingJoinPoint jp) {
            try {
                System.out.println("Silence cell phones");
                System.out.println("Taking seats");
                jp.proceed();
                System.out.println("CLAP CLAP CLAP");
            } catch (Throwable e) {
                System.out.println("Demanding a refund");
            }
        }
    }
    • 需要接受ProceedingJoinPoint对象作为参数,用于调用被通知的方法
    • 当将控制权交给被通知的方法时,需要调用ProceedingJoinPointproceed()方法

处理通知中的参数

  • 示例: 实现CD播放器的切面,来记录每首歌的播放次数

    @Aspect
    public class SongCounter {
        private Map<Integer, Integer> songCounts = new HashMap<Integer, Integer>(); // 第一个Integer表示歌曲编号,第二个表示播放次数
    
        @Pointcut("execution(* cdsystem.CompactDisc.playSong(int)) && args(songNumber)")
        public void songPlayed(int songNumber) {}
    
        @Before("songPlayed(songNumber)")
        public void countSong(int songNumber) {
            int currentCount = getPlayedCount(songNumber);
            songCounts.put(songNumber, currentCount + 1);
        }
    
        public void getPlayCount(int songNumber) {
            return songCounts.containsKey(songNumber) ? songCounts.get(songNumber) : 0;
        }
    }

    • args(songNumber)限定符用于传递参数,表明传递给playSong中的int参数也会到通知中
    • 切点args(parameter)需要和通知注解中的songPlayed(parameter)一致

通过注解引入新功能

  • 通过AOP为bean引入新的方法,代理拦截调用并委托给实现该方法的其他对象

  • 实际上,一个bean的实现被拆分到了多个类中

  • 示例:

    • 创建包含新功能的接口

      public interface Encoreable {
          public void performEncore();
      }
    • 创建实现了接口的对象
    • 创建切面:

      @Aspect
      public class EncoreableIntroducer {
          @DeclareParents(value="concert.Performance+", defaultImpl=DefaultEncoreable.class)
          public static Encoreable encoreable;
      }
      • 此切面没有提供通知
      • 通过@DeclareParents注解将Encoreable接口引入到Performancebean中
      • @DeclareParents:
        • value属性指定要引入该接口的bean类型
        • defaultImpl: 指定实现了引入功能的类
        • @DeclareParents标注的静态属性指明了要引入的接口
    • 将切面声明为bean:
      @Bean
      public EncoreableIntroducer encoreableIntroducer() {
          return new EncoreableIntroducer();
      }

在XML中声明切面

通常而言,基于注解的配置优于基于Java的配置优于基于XML的配置

AOP配置元素能够以非侵入性的方式声明

  • 无注解的Audience类

    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");       
        }
    }

声明前置和后置通知

  • 示例

    <aop:config>
        <!-- 声明切面 -->
        <aop:aspect ref="audience"> <!-- 引用audience bean -->
            <aop:before
                pointcut="execution(**concert.Performance.perform(..))"
                method="slienceCellPhones"/>
            <aop:before
                pointcut="execution(**concert.Performance.perform(..))"
                method="takeSeats"/>
            <aop:after-returning
                pointcut="execution(**concert.Performance.perform(..))"
                method="applause"/>
            <aop:after-throwing
                pointcut="execution(**concert.Performance.perform(..))"
                method="demandRefund"/>
        </aop:aspect>
    </aop:config>
  • <aop:pointcut>定义切点

    <aop:config>
        <!-- 声明切面 -->
        <aop:aspect ref="audience"> <!-- 引用audience bean -->
            <!-- 定义切点 -->
            <aop:pointuct
                id="performance"
                expression="execution(**concert.Performance.perform(..))"/>
            <aop:before
                point-ref="performance"
                method="slienceCellPhones"/>
            <aop:before
                point-ref="performance"
                method="takeSeats"/>
            <aop:after-returning
                point-ref="performance"
                method="applause"/>
            <aop:after-throwing
                point-ref="performance"
                method="demandRefund"/>
        </aop:aspect>
    </aop:config>
    • point-ref="pointcutID"引用切点
    • 为了使<aop:pointcut>能够在多个切面中使用,可将其放在<aop:config>元素内

声明环绕通知

  • Java代码:

    public class Audience {
        public void watchPerformance(ProceedingJoinPoint jp) {
            try {
                System.out.println("Silence cell phones");
                System.out.println("Taking seats");
                jp.proceed();
                System.out.println("CLAP CLAP CLAP");
            } catch (Throwable e) {
                System.out.println("Demanding a ");
            }
        }
    }
  • XMl配置:

    <aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut
                id="performance"
                expression="execution(** concert.Performance.perform(..))"/>
            <!-- 声明环绕通知 -->
            <aop:around
                pointcut-ref="performance"
                method="watchPerformance"/>
        </aop:aspect>
    </aop:config>

为通知传递参数

  • Java代码

    public class SongCounter {
        private Map<Integer, Integer> songCounts = new HashMap<Integer, Integer>(); // 第一个Integer表示歌曲编号,第二个表示播放次数
    
        public void countSong(int songNumber) {
            int currentCount = getPlayedCount(songNumber);
            songCounts.put(songNumber, currentCount + 1);
        }
    
        public void getPlayCount(int songNumber) {
            return songCounts.containsKey(songNumber) ? songCounts.get(songNumber) : 0;
        }
    }
  • XML配置

    <bean id="songCounter" class="cdsystem.SongCounter"/>
    <bean id="cd" class="cdsystem.CompactDisc"/>
    
    <aop:config>
        <aop:aspect ref="songCounter">
            <aop:pointcut
                id="songPlayed"
                expression="execution(* cdsystem.CompactDisc.playSong(int))
                    and args(songNumber)"/>
    
            <aop:before
                pointcut-ref="songPlaye"
                method="songTrack"/>
        </aop:aspect>
    </aop:config>

通过切面引入新功能

  • XML配置

    <aop:config>
        <aop:aspect>
            <aop:declare-parents
                types-matching="concert.Performance+"
                implement-interface="concert.Encoreable"
                default-impl="concert.DefaultEncoreable"/>
        </aop:aspect>
    </aop:config>
    • 配型匹配Performance接口的bean在父类结构中会增加Encoreable接口
    • 有两种标识引入接口的实现

      • default-impl: 如上所示
      • delegate-ref: 将实现类声明为bean,再通过此标签引用

        <aop:aspect>
            <aop:declare-parents
                types-matching="concert.Performance+"
                implement-interface="concert.Encoreable"
                delegate-ref="encoreableDelegate"/>
        </aop:aspect>
        <bean id="encoreableDelegate" class="concert.DefaultEncoreable"/>
        

注入AspectJ切面

目前水平太低也用不到,暂时略过QAQ

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值