zoukankan      html  css  js  c++  java
  • Spring AOP初步总结(一)

    学习AOP有段时间了,一直没空总结一下,导致有些知识点都遗忘了,之后会把以前学过的Spring核心相关的知识点总结一轮...

    先大体介绍下Spring AOP的特点(均摘自"Spring in action第四版"):

      Spring支持了AOP,另外还有很多实现了AOP的技术,例如AspectJ,它补充了Spring AOP框架的功能,他们之间有着大量的协作,而且Spring AOP中大量借鉴了AspectJ项目,Spring AOP相对粗粒度,而AspectJ提供更强大更细粒度的控制,以及更丰富的AOP工具集,但需要额外的语法学习;

      Spring借鉴了AspectJ的切面,以提供注解驱动的AOP,编程模型几乎与编写成熟的AspectJ注解切面完全一致。这种AOP风格的好处在于能够不使用XML来完成功能。Spring AOP构建在动态代理之上,因此,Spring对AOP的支持局限于方法拦截;如果你对AOP的需求超过了建党方法调用(如构造器或属性拦截),那么你需要AspectJ来实现切面;

      Spring提供了4种类型的AOP支持:

        基于代理的经典Spring AOP;

        纯POJO切面;

        @AspectJ注解驱动的切面;

        注入式AspectJ切面(适用于Spring各版本);

     

     

    AOP相关的术语(概念):

      1.通知(Advice):

        切面的工作被称为通知,通知定义了切面是什么以及何时使用;

        分5类:

    前置通知(Before) 在目标方法被调用之前调用通知功能;
    后置通知(After) 在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
    返回通知(After-returning) 在目标方法成功执行之后调用通知;
    异常通知(After-throwing) 在目标方法抛出异常后调用通知;
    环绕通知(Around)

    通知包裹了被通知的方法,在被通知的方法调用之前和调用之后
    执行自定义的行为。

      2.连接点(Join point)

        应用通知的时机被称为连接点;连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为;

     

      3.切点(Poincut)

        切点定义了切面的在何处实施;通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名来指定;

     

      4.切面Aspect

        是通知(何时,做什么)和切点(何处)的结合;

     

      5.引入(Introduction)

        允许我们向现有的类添加新方法或新属性;

     

      6.织入(Weaving)

        把切面应用到目标对象并创建新的代理对象的过程

        3种织入时机:

    编译期 在目标类编译时被织入,AspectJ的织入编译器就是以这种方式织入的
    类加载期 在目标类加载到JVM中时被织入,AspectJ5的加载时织入(load-time weaving,LTW)支持
    运行期 在运行的某个时刻被织入,一般情况下AOP容器会为目标对象动态创建一个代理对象,Spring AOP就支持这种方式

        

    Spring AOP是基于动态代理的:

      通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。如下图所
    示,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当
    代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。

      

    编写切点(若干图例了解切点表达式):

       

      

        我们使用execution()指示器选择Performance的perform()方法。方法表达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号(..)表明切点要选择任意的perform()方法,无论该方法的入参是什么。

      

      我们使用了“&&”操作符把execution()和within()指示器连接在一起形成与(and)关系(切点必须匹配所有的指示器)。类似地,我们可以使用“||”操作符来标识或(or)关系,而使用“!”操作符来标识非(not)操作。因为“&”在XML中有特殊含义,所以在Spring的XML配置里面描述切点时,我们可以使用and来代替“&&”。同样,or和not可以分别用来代替“||”和“!”。

      

      在这里,我们希望在执行Performance的perform()方法时应用通知,但限定bean的ID为woodstock。

     

    下面正式开始创建切面:

    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    @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");
         }
    }

      Audience类使用@AspectJ注解进行了标注。该注解表明Audience不仅仅是一个POJO,还是一个切面。Audience类中的方法都使用注解来定义切面的具体行为。Audience有四个方法,定义了一个观众在观看演出时可能会做的事情。在演出之前,观众要就坐(takeSeats())并将手机调至静音状态(silenceCellPhones();

      AspectJ提供了五个注解来定义通知

    这样,一个切面就定义好了,但我们四个注解使用的表达式都一样,可以用@Pointcut注解提供表达式的引用:

      1.提供一个空方法,在上面增加注解:@Pointcut("execution(** concert.Performance.perform(..))");

      2.其它注解引用,例如: @Before("上面定义的空方法名()");

    光这样还不够,下面要装配Audience类成为一个bean:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    
    @Configuration
    @EnableAspectJAutoProxy //气筒AspectJ自动代理
    @ComponentScan
    public class ConcertConfig { //bean的配置类
        
        @Bean
        public Audience audience() { //配置Audience类成为一个Bean
            return new Audience();
        }
    }

    以上就实现了前置后置分离通知的切面;

     

    接下在介绍另一种:环绕通知的写法(逻辑不复杂的时候建议用这种方式,更加直观,一旦逻辑复杂,可读性会很差):

     

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Around;
    import org.aspectj.lang.Aspect;
    import org.aspectj.lang.Pointcut;
    
    @Aspect
    public void Audience {
        @Pointcut("execution(** concert.Performance.perform(..))")
        public void performance(){}
    
        @Around("performance()")
        public void watchPerformance(PerceedingJoinPoint jp){
            try {
                System.out.println("Silencing cell phones");
                System.out.println("Taking seats");
                jp.proceed();                   //调用被通知的方法(可以多次调用)
                System.out.println("CLAP CLAP CLAP...");
            } catch (Throwable e) {
                System.out.println("Demanding a refund");
            }
        }
    }

    以上总结的是不带参数构造切面的情况

     

    接下来介绍如果在通知上符加参数的情况:

    import java.util.HashMap;
    import java.util.Map;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    
    
    @Aspect
    public class TrackCounter {
        private Map<Integer, Interger> trackCounts = 
            new HashMap<Integer, Integer>(); 
        
        @Pointcut(
                "execution(* soundsystem.CompactDisc.playTrack(int))" +
                "&& args(trackNumber)")
        public void trackPlayed(int trackNumber) {}
    
        @Before("trackPlayed(trackNumber)")
        public void countTrack(int trackNumber){
            int currentCount = getPlayCOunt(trackNumber);
            trackCounts.put(trackNumber, currentCount + 1);
        }
    
        public int getPlayCount(int trackNumber) {
            return trackCOunts.containKey(trackNumber) ?     
                trackCounts.get(trackNumber) : 0;
        }
    }

    图例来解释表达式:

  • 相关阅读:
    vim 的配置文件
    linux bash 的自动补全
    linux ping 命令
    linux 安装ifconfig
    dos exist 命令
    linux 用户的添加,组的添加,以及查看
    if else 的.bat 文件
    For 的.bat文件
    rmdir 的.bat文件
    dos set 命令
  • 原文地址:https://www.cnblogs.com/Joey44/p/10025188.html
Copyright © 2011-2022 走看看