zoukankan      html  css  js  c++  java
  • Spring实战(十)Spring AOP应用——为方法引入新功能、为对象引入新方法

      切面最基本的元素是通知和切点,切点用于准确定位应该在什么地方应用切面的通知。

      1、Spring借助AspectJ的切点表达式语言来定义Spring切面

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

      重要的一点是,Spring仅支持AspectJ切点指示器的一个子集,当尝试使用AspectJ其他指示器时,会抛出异常

     

      arg()        限制连接点匹配参数为指定类型的执行方法

      @args()     限制连接点匹配参数为指定注解标注的执行方法

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

      this()       限制连接点匹配AOP代理的bean引用为指定类型的类

      target      限制连接点匹配目标对象为指定类型的类

      @target()     限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解

      within()      限制连接点匹配指定的类型

      @within     限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义

                在由指定的注解所注解的类中)

      @annotation   限定匹配带有指定注解的连接点

     

      2、编写切点——切点表达式

    package concert
    public interface Performance{
      public void perform();  
    }

      下面编写Performance的perform()方法的触发的通知,这个切点表达式能够设置当perform()方法执行时触发通知的调用:

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

     

      其中方法表达式从“ * ”号开始,表示我们不关心方法返回值的类型;

      然后,指定全限定类名和方法名;

      方法参数列表中,用两个点号(..)表明切点要选择任意的perform()方法,而不关心该方法的入参是什么。

      假设,我们要配置的切点仅匹配concert包,可以利用within()指示器:

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

      这里我们使用了“&&”操作,在其他切点表达式中我们也可以使用“||”、“!”操作。(or、not)

     

      3、在切点中选择bean

      Spring引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID or bean名称作为参数来限制切点,让它只匹配特定的bean。

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

      这时,切面的通知会被织入到ID为woodstock的bean中。

      还可以如下使用,切面的通知会被织入到所有ID不为woodstock的bean中:

     execution( *  concert.Performance.perform(..) ) and !bean('woodstock')

     

     

      4、使用AspectJ注解创建切面

      使用注解创建切面是AspectJ 5引入的关键特性,之前需要学习一种Java语言的扩展。

      首先确定,上面定义的Performance接口,是切面中切点的目标对象,接下来我们定义一个Audience类,我们把观众作为演出的切面。

    @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!!~~");
        }
    
        @AfterThrowing("execution(* concert.Performance.perform(..))")
        public void demandRefund(){
            System.out.println("Demanding a refund.");
        }
    }

     

      这四个方法定义了一个观众在观看演出时可能会做的事情:

      演出之前,观众就坐—将手机调至静音状态;

      演出精彩—观众鼓掌;

      演出很烂—观众要求退款。

      (通知方法中的注解都给定了一个切点表达式作为它的值)

     

      5、AspectJ提供的注解(声明通知方法)

      @After          通知方法在目标方法返回or抛出异常后执行;

      @AfterReturning       通知方法在目标方法返回后调用;

      @AfterThrowing      通知方法在目标方法抛出异常后调用;

      @Around         通知方法将目标方法封装起来;

      @Before          通知方法在目标方法调用之前执行;

     

      6、使用@Pointcut注解在一个@AspectJ切面内定义可以重用的切点。

      在上一个切面Performance类中,注解中的切点表达式我们重复使用了四次,下面使用@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!!~~");
        }
    
        @AfterThrowing("performance()")
        public void demandRefund(){
            System.out.println("Demanding a refund.");
        }
    }

      这个Audience类中的performance()方法的实际内容并不重要,在这里它实际上应该是空的,因为该方法本身只是一个标识,供@Pointcut注解依附。

      

      到此为止,我们创建了切面吗?并没有,目前,Audience只会是Spring容器中的一个bean。(@AspectJ会自动创建为bean?)

      这里即使我们使用了AspeJ注解,但它不会被是为切面,这些注解不会解析,也不会创建将其转换为切面的代理。

     

      7、使用JavaConfig,XML开启自动代理

      JavaConfig:

      可以在配置类(使用JavaConfig有一个配置类(@Configuration)来开启各项功能)上使用@EnableAspeJAutoProxy启用自动代理功能;

    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan
    public class ConcertConfig{
     @Bean
     public Audience audience{
       return new Audience();  
     }            
    }

     

      XML:

      使用Spring的aop命名空间中的<aop:aspectj-autoproxy>元素,开启自动代理。(先声明Spring的AOP命名空间)

      不管是哪种方式,AspeJ自动代理都会为使用@Aspect所注解的bean创建一个代理,这个代理会围绕所有该切面所匹配的bean。

     

      8、创建环绕通知@Around

      环绕通知是最为强大的通知类型,可以使自己编写的逻辑将被通知的目标方法完全包装起来。

      下面用一个环绕通知来代替之前的多个前置和后置通知:

    @Aspect
    public class AudienceAround {
        @Pointcut("execution(* concert.Performance.perform(..))")
        public void performance() {
        }
    
        @Around("performance()")
        public void watchPerformance(ProceedingJoinPoint jp) {
            try {
                System.out.println("Silencing cell phone");
                System.out.println("Taking seats");
                jp.proceed();
                System.out.println("CLAP~~CLAP~");
            }catch(Throwable e){
                System.out.println("Demanding a refund!");
            }
        }
    }

      这个位于一个方法中的通知所达到的效果,与之前的几个前置和后置通知是一样的。

      注意到,这个watchPerformance方法接受了一个ProceedingJoinPoint类型的参数,jp对象必须要有,因为通过它的proceed()方法来调用被通知(增强)的方法。也就是说,这个通知阻塞了被通知的方法,我们必须手动调用。

      9、若被通知的方法含有参数,切面能访问和使用被传递给被通知的方法的参数吗?

       也就是说,我们的增强逻辑中,需要利用被增强的方法中的参数完成一些功能。例如:

    @Aspect
    public class TrackCounter{
        @Pointcut("execution(* soundsystem.CompactDisc.playTrack(int))"&&"args(trackNumber)")
        public void trackPlayed(int trackNumber) {}
    
        @Before("trackPlayed(trackNumber)")
        public void countTrack(int trackNumber){...}  
    
        ...    
    }

       这个切面中,使用@Pointcut注解定义命名的切点中声明了要提供给通知方法的参数。切点表达式中"args(trackNumber)"限定符表明了传递给playTrack()方法的int型参数也会传递到通知中去,参数的名称trackNumber也与切点方法签名中的参数相匹配。

      下面的前置通知方法的注解@Before中与countTrack方法中的入参均有trackNumber,这样就完成了从命名切点到通知方法的参数转移。

      形象点来说,我们靠AspectJ注解作为载体,把被通知方法中的参数转义到通知中。

      目前为止,我们所使用的切面中,所包装的都是被通知对象的已有方法,这仅仅是切面能实现的功能之一。

      10、如何通过编写切面,为被通知的对象(不是方法)引入全新的功能?

      Java不是动态语言,一旦类编译完成了,我们就很难再为该类添加新功能。

      我们之前做的都是为对象拥有的方法添加新功能,那我们为什么不能为对象增加新的方法呢?

      使用Spring AOP,我们可以为bean引入新的方法,代理拦截调用并委托给实现该方法的其他对象。

        11、通过@DeclareParens注解实现为对象引入新方法

      现在我们来为之前的Performance接口的所有实现引入新接口Encoreable(观众要求返场表演):

    package concert;
    
    public interface Encoreable {
        void performEncore();
    }

      借助于AOP的引入功能,我们可以不必在设计上妥协或者侵入性地改变现有的实现。

      为了实现该功能,创建一个新的切面:

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

      这个切面并没有提供前置、后置或环绕通知,而是通过@DeclareParens注解,将Encoreable接口引入到Performance bean中:

    • value属性指定了那种类型的bean要引入该接口。这里是指定为所有实现Performance的类型,“+”表示是Performance的所有子类型,而不是本身。
    • defaultImpl属性指定了为引入功能提供实现的类。在这里,指定DefaultEncoreable提供实现。
    • @DeclareParens注解所标注的静态属性表明了要引入的接口,这里我们引入Encoreable接口。

      和其他切面一样,我们需要在Spring应用中将EncoreableIntroducer声明为一个bean。然后Spring的自动代理机制会获取到它的声明,当Spring发现一个bean使用@AspectJ注解时,Spring就会创建一个代理,单后将调用委托给被代理的bean或被引入的实现,这取决于调用的方法属于被代理的bean还是属于被引入的接口。

      Spring的注解和自动代理提供了一种便利的方式创建切面,但是面向注解的切面声明有一个明显的劣势:你必须能够为通知类添加注解。为了做到这一点,必须要有源码!

      若没有源码or不想将AspectJ注解放入你的代码中,可以使用Spring XML配置文件声明切面。

     

     

  • 相关阅读:
    《高等应用数学问题的MATLAB求解》——第4章习题代码
    《高等应用数学问题的MATLAB求解》——第3章习题代码
    《高等应用数学问题的MATLAB求解》——第2章习题代码
    2020年高考数学全国一卷第16题
    LR&PCA&KPCA
    package.json.lock
    Charles 抓 iphone 手机包
    竞赛196
    竞赛197
    js编程语言!!!!
  • 原文地址:https://www.cnblogs.com/bigbigbigo/p/8375019.html
Copyright © 2011-2022 走看看