Spring基础知识之基于注解的AOP

背景概念:

  1)横切关注点:散布在应用中多处的功能称为横切关注点

  2)通知(Advice):切面完成的工作。通知定了了切面是什么及何时调用。

    5中可以应用的通知:

        前置通知(Before):在目标方法被调用前调用通知功能。

        后置通知(After):在目标方法完成后调用通知,此时不会关系方法输出什么。

        返回通知(After-returning):在目标方法成功执行后调用通知。

        异常通知(After-throwing):在目标方法抛出异常后调用通知。

        环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的方法。

  3)连接点(Join Point):调用通知的时机称为连接点。

  4)切点(Pointcut):通知要织入的连接点的范围。

  5)切面(Aspect):切面:通知和切点的结合。

  6)引入(Introduction):允许我们向现有的类添加新方法或属性。

  7)织入(weaving):把切面应用到目标对象并创建新的代理对象的过程。

      切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:

        编译期:切面在目标类编译时织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。

        类加载期:切面在目标类被加载到JVM时织入。这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增加该目标类的字节码。AspectJ5的加载时织入,就支持以这种方式织入。

        运行期:切面在应用运行的某个时刻被织入。一般情况下,在切面被织入时,AOP容器会为目标对象动态创建代理对象,SpringAOP就是以这种方式进行织入的。

  注:注解的解释:注解本身是没有功能的,就和XML一样,注解和XML都是一种元数据,元数据就是解释数据的数据,这里就是所谓的配置。

    注解的功能来自用注解的这个地方

Spring对AOP的支持

  SpringAOP存在的目的:解耦。

    AOP可以让一组类共享相同的行为。避免通过继承等高耦合方式实现为类添加功能。

  Spring支持4中类型的AOP支持:

    1)基于代理的经典SpringAOP;

    2)纯POJO切面;

    3)@AspectJ注解驱动的切面;

    4)注入式AspectJ切面(适用于各个Spring版本)。

  注:前三种都是SpringAOP实现的变体,SpringAOP构建在动态代理的基础之上,因此,Spring对AOP的支持局限于方法拦截。

    如果AOP需求超过了简单的方法调用(如构造器或属性拦截),那么需要使用第四种方式。

  Spring通知是java编写的

      Spring的通知是POJO实现的,可以基于注解和XML实现,相对简单便捷。

      AspectJ以java扩展的方式实现的,优点:特有的AOP语言可以获得更强大的细粒度的控制以及更丰富的AOP工作集,但学习成本大。

  Spring在运行时通知对象

      Spring运行时才会创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。

  Spring只支持方法级别的连接点  

      如果需要使用除方法拦截之外的连接点拦截功能,那么我们可以利用Aspect来补充Spring AOP的功能。

通过切点来选择连接点

  注:只有execution指示器是实际执行匹配的,其他指示器都是限定匹配的,我们在编写切点定义时最主要使用的指示器应当是:execution指示器,在此基础上使用其他指示器来限制所匹配的切点。

编写切点:

  定义一个performance接口:

package com.spring.learn.index;
public interface Performance {
    public void perform();
}

则我们想在表演接口中的表演方法触发我们的通知时,需要的切点表达式:

  execution(*  com.spring.learn.index.Performance.perform(..))

若我们仅仅需要的是com包下的类,可以使用within()来限制匹配: 

  execution(*  com.spring.learn.index.Performance.perform(..)) && within(com.*)

使用 && 来表示与关系;同理 || 表示或关系;!表示非。

  注在XML中使用and  or  not 来替换(因为符号在XML中有特殊含义)。

在切点中选择bean

  除了上述指示器外Spring还引入了bean()指示器,bean()使用bean ID或者bean名称来作为参数限制切点只匹配特定的bean。

使用注解创建切面    

  已经定义了Performance接口,它是切面中目标对象。使用AspecJ注解来定义切面。

定义切面:

  使用Audience类:观看演出的切面:

@Aspect
public class Audience {

    //这是表演之前
    @Before("execution(* com.spring.learn.index.Performance.perform(..))")
    public void silenceCellPhones(){
        System.out.println("观众的手机静音");
    }

    //表演之前
    @Before("execution(* com.spring.learn.index.Performance.perform(..))")
    public void takeSeats() {
        System.out.println("观众落座");
    }

    //表演之后
    @AfterReturning("execution(* com.spring.learn.index.Performance.perform(..))")
    public void applause() {
        System.out.println("掌声雷动");
    }

    //表演失败
    @AfterThrowing("execution(* com.spring.learn.index.Performance.perform(..))")
    public void demandRefund() {
        System.out.println("要求退款");
    }
}

@Aspect注解,表明了Audience不仅仅是一个POJO,还是一个切面。

Audience中的方法使用注解的方式定义了通知何时调用。AspectJ中提供了五个注解来定义通知:

每一个注解都使用了切点表达式来作为他的值。但是我们的切点表达式重复使用了四次,其实我们可以只写一次,然后使用引用的方式实现同样的操作。

@PointCut注解能够在一个@Aspect定义的切面内定义可重复使用的切点:

@Aspect
public class Audience {

    //定义命名的切点
    @Pointcut("execution(* com.spring.learn.index.Performance.perform(..))")
    public void myPointCut(){}

    @Before("myPointCut()")
    public void silenceCellPhones(){
        System.out.println("观众的手机静音");
    }
    
    @Before("myPointCut()")
    public void takeSeats() {
        System.out.println("观众落座");
    }
    
    @AfterReturning("myPointCut()")
    public void applause() {
        System.out.println("掌声雷动");
    }
    
    @AfterThrowing("myPointCut()")
    public void demandRefund() {
        System.out.println("要求再来一场新的表演");
    }
}

到此为止Audience仍只是一个普通的POJO类,即使使用了@Aspect注解也不会被视为切面,这些注解不会解析,也不会创建将其转化为切面的代理。

需要额外的配置类,并在配置类上使用EnableAspectJ-AutoProxy 注解启动自动代理功能:

//启用AspectJ自动代理
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AspectConfig {

    //声明Audience bean
    @Bean
    public Audience audience() {
        return new Audience();
    }
}

如果使用的是XML来装配bean的话,需要使用Spring AOP命名空间的<aop:aspectj-autoproxy> 元素:

不论哪种方式,AspectJ自动代理都会使用@Aspect注解的bean创建一个代理,这个代理会围绕所有该切面的切点所匹配的bean。将会为bean创建一个代理,使通知的方法在切点前后被调用。

创建环绕通知

@Aspect
public class Audience {

    //定义命名的切点
    @Pointcut("execution(* com.spring.learn.index.Performance.perform(..))")
    public void myPointCut(){}


    @Around("myPointCut()")
    public void watchPerformance(ProceedingJoinPoint jp){
        try {
            System.out.println("观众的手机静音");
            System.out.println("观众落座");
            jp.proceed();
            System.out.println("掌声雷动");
        }catch (Throwable e) {
            System.out.println("退票退票");
        }
    }
}

可以看到环绕方法可以实现之前的几个注解的所有功能。

注:ProceedingJoinPoint参数,这个参数必须有,因为要在通知中通过它来调用被通知的方法。通知方法可以做任何事,当要将控制权交给被通知的方法时,需要调用ProceedingJoinPoint的proceed()方法。

注:  一定要调用proceed()方法,否则通知将会阻塞被通知方法的调用。

    实际上你也可以对被通知方法进行多次调用。这一般是为了实现重试逻辑。

处理通知中的参数

  场景:磁带中不同的磁道有多种歌曲,调用playTrack()方法可以实现播放。要记录每个磁道被播放的次数,使用切面来完成:

  通过上述的方式可以将切面上的参数同步到通知上。

通过注解引入新功能

  通过引入的AOP概念,切面可以为Spring bean添加新的方法。

我们为Performance接口引入下面的接口:

public interface Encoreable {
    void perforEncore();
}

则定义的切面如下:

@Aspect
public class EncoreableIntroducer {
    @DeclareParents(value = "com.spring.learn.index.Performance+",
                        defaultImpl = DefaultEncoreable.class)
    public static Encoreable encoreable;
}

注:和其他切面一样,我们需要将在Spring应用中将Encoreableintroducer声明为一个bean:

//启用AspectJ自动代理
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AspectConfig {

    //声明Audience bean
    @Bean
    public Audience audience() {
        return new Audience();
    }
    
    //声明EncoreableIntroducer  bean
    @Bean
    public EncoreableIntroducer encoreableIntroducer() {
        return new EncoreableIntroducer();
    }
}

或:

由于目前已经不接触XML配置了。

所以XML配置与AspectJ的更强大面向切面实现,等过一阵再来补充。

AspectJ的全面学习博客地址

本文内容是书中内容兼具自己的个人看法所成。可能在个人看法上会有诸多问题(毕竟知识量有限,导致认知也有限),如果读者觉得有问题请大胆提出,我们可以相互交流、相互学习,欢迎你们的到来,心成意足,等待您的评价。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员宝库

Spring 注解概览

从Java5.0开始,Java开始支持注解。Spring做为Java生态中的领军框架,从2.5版本后也开始支持注解。相比起之前使用xml来配置Spring框架,...

3568
来自专栏Java Edge

Spring Bean的生命周期Spring简介bean对象生命周期管理

3028
来自专栏精讲JAVA

Spring经典面试题和答案

Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目...

764
来自专栏Java学习网

69 个经典 Spring 面试题和答案

69 个经典 Spring 面试题和答案 Spring 概述 1. 什么是spring? Spring 是个java企业级应用的开源开发框架。Spring主要用...

2317
来自专栏JackieZheng

照虎画猫写自己的Spring——自定义注解

Fairy已经实现的功能 读取XML格式配置文件,解析得到Bean 读取JSON格式配置文件,解析得到Bean 基于XML配置的依赖注入 所以,理所当然,今天该...

2219
来自专栏java一日一条

69 个经典 Spring 面试题和答案

Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标...

462
来自专栏Java架构

2018年7月份,Spring经典面试题和答案

Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标...

1163
来自专栏码匠的流水账

聊聊resilience4j的Retry

resilience4j-retry-0.13.0-sources.jar!/io/github/resilience4j/retry/Retry.java

331
来自专栏技术墨客

Spring核心——IOC处理器扩展 原

Spring一直标注自己是一个非侵入式框架。非侵入式设计的概念并不新鲜,目标就是降低使用者和框架代码的耦合,毕竟框架的开发者和使用者几乎肯定不是同一个团队。Sp...

392
来自专栏我叫刘半仙

原向Spring大佬低头--大量源码流出解析

       用Spring框架做了几年的开发,只停留在会用的阶段上,然而Spring的设计思想和原理确实一个巨大的宝库。大部分人仅仅知道怎么去配,或着加上什么...

4056

扫码关注云+社区