• 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的全面学习博客地址

    AspectJ切入点语法详解地址

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

  • 相关阅读:
    [转]IUnkown生命周期管理
    [转] com 基本数据类型
    Centos 7 修改开机等待时间 Alex
    乌班图的安装常用命令 Alex
    Ubuntu切换root用户 Alex
    乌班图开启关闭防火墙 Alex
    Ubuntu远程root用户登录 Alex
    Centos 8 更改为阿里云源 Alex
    LVS调度之搭建NAT模型实现 Alex
    解决挂载mount: wrong fs type, bad option, bad superblock on Alex
  • 原文地址:https://www.cnblogs.com/lilinzhiyu/p/8099178.html
Copyright © 2020-2023  润新知