011-Spring aop 002-核心说明-切点PointCut、通知Advice、切面Advisor

一、概述

  切点Pointcut,切点代表了一个关于目标函数的过滤规则,后续的通知是基于切点来跟目标函数关联起来的。

  然后要围绕该切点定义一系列的通知Advice,如@Before、@After、@AfterReturning、@AfterThrowing、@Around等等定义的方法都是通知。其含义是在切点定义的函数执行之前、完成之后、正常返回之后、抛出异常之后以及环绕前后执行对应的切面逻辑。

  一个切点和针对该切点的一个通知共同构成了一个切面Advisor。对于一个方法,我们可以定义多个切点都隐含它,并且对于每个切点都可定义多个通知来形成多个切面,SpringAOP底层框架会保证在该方法调用时候将所有符合条件的切面都切入到其执行之前或之后或环绕。通知Advice的子类Interceptor或MethodInterceptor的类名更具体一些,包含了拦截器的概念。

  SpringAOP使用运行时连接点Joinpoint的概念将切面切入到调用方法中,一个运行时连接点就是对于一个可访问对象的访问过程的具体化,可能其子类Invocation或MethodInvocation的类名会更加具体一些。在实际调用中运行时连接点包括了被调用方法、被调用对象、适用于该方法的拦截器链等等信息。

  执行的过程类似于FilterChain,先正向执行拦截器链的前置逻辑,然后调用method,接着反向执行拦截器链的后置逻辑,最后返回结果。

1.1、切点PointCut

上例中

    @Pointcut("execution(public * com.github.bjlhx15.springaop.service.MyTestService.doSomething1*(..))")
    public void doSomethingPointcut(){};

    @Pointcut("@annotation(com.github.bjlhx15.springaop.anno.TestTimer)")
    public void timerPointcut(){};

    @Pointcut("@within(com.github.bjlhx15.springaop.anno.TestLogger)")
    public void recordLogPointcut(){};

  都是用于定义一个切点,注释Pointcut中的value值就是切入点指示符,SpringAOP提供的这种匹配表达式是用于计算哪些方法符合该切点的定义。Pointcut接口如下所示:

public interface Pointcut {
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
    Pointcut TRUE = TruePointcut.INSTANCE;
}

  其中定义了两个抽象方法:获得类过滤器和获得方法匹配器。意思很明确,就是可以通过类过滤及方法过滤,来定义对目标函数的过滤规则。各子类可以指定具体的过滤器来实现不同的过滤过则。

  Spring2.0中增加了AspectJExpressionPointcut来支持AspectJ关于切点定义的表达式语法。其中定义了支持的各种类型的切点函数,并支持通配符和逻辑表达式。

1.1.1、原生切点函数

  原生切点函数就是我们在示例中定义切点时使用的execution、@annotation、@within等函数,在AspectJExpressionPointcut中定义了支持的各种类型的原生切点函数:

private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();
    static {
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
    }

其中:PointcutPrimitive 是来自于aspectj 的 参数。

常用的原生切点函数:

1、类型原生切点函数within

针对类型(全限定名)的过滤方法,语法格式如下:within(<typeName>);typeName表示类或接口的全限定名,支持使用通配符,例如:

/**
*匹配aopnew.service包中所有以MyTestService开头的类中的所有方法
*/
@Pointcut("within(aopnew.service.MyTestService*)")

/**
*匹配所有实现anpnew.interface.IUserService接口的类的所有方法
*/
@Pointcut("within(anpnew.interface.IUserService+)")

2、方法原生切点函数execution

针对方法签名进行过滤,语法表达式如下:

/**
*scope:表示方法作用域,例如:public, private, protect
*return-type:表示返回类型
*fully-qualified-class-name:表示类的完全限定名
*method-name:表示方法名
*parameters:表示参数
*/
execution(<scope> <return-type> <fully-qualified-class-name><method-name>(<parameters>))

对于给定的作用域、返回值类型、完全限定类名、方法名以及参数匹配的方法将会应用切点函数指定的通知,支持使用通配符,例如:

/**
*匹配作用域为public,所在类全限定名为aopnew.service.MyTestService,方法名以doSomething开头的所有方法
*
*/
@Pointcut("execution(public * aopnew.service.MyTestService.doSomething*(..))")

3、类注释原生切点函数@within

用于匹配标注了指定注释的类型内的所有方法,与within是有区别的,within是用于匹配指定类型内的方法执行;语法如下:@within(<annotationName>)

annotationName表示注释类的全限定名,支持使用通配符,例如:

/**
*匹配标注了TestLogger的类中的所有方法
*/
@Pointcut("@within(aopnew.annotation.TestLogger)")

4、方法注释原生切点函数@annotation

用于匹配所有标注了指定注解的方法,语法如下:@annotation(<annotationName>)

annotationName表示注释类的全限定名,支持使用通配符,例如:

/** *匹配所有标注了TestTimer的方法 */ @Pointcut("@annotation(aopnew.annotation.TestTimer)")

1.1.2、通配符

上述的原生切点函数中都支持通配符,在示例中我们看到了很多如  *  ,  ..  , +等,它们的含义如下:

.. :匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包,例如:

/**
*匹配aopnew包及子包中的类名为MyTestService2中的以doSomething开头并且作用域为public的所有方法
*/
@Pointcut("execution(public * aopnew..MyTestService2.doSomething*(..))")

+ :匹配给定类的任意子类,例如:

/**
*匹配所有实现anpnew.interface.IUserService接口的类的所有方法
*/
@Pointcut("within(anpnew.interface.IUserService+)")

* :匹配任意数量的字符,例如:

/**
*匹配aopnew.service包中任意类中的所有方法
*/
@Pointcut("within(aopnew.service.*)")

1.1.3、逻辑表达式

切点指示符可以使用运算符语法进行表达式的混编,如and、or、not(或&&、||、!),例如:

/**
*匹配类上标注了TestLogger并且方法上标注了TestTimer的所有方法
*/
@Pointcut("@within(aopnew.annotation.TestLogger) && @annotation(aopnew.annotation.TestTimer)")

1.2、通知Advice

  通知Advice描述了当符合某切点的方法调用时,在调用过程的哪个时机执行哪样的切面逻辑。Spring2.0引入了AspectJ的通知类型,主要分5种,分别是前置通知@Before、后置通知@AfterReturn、异常通知@AfterThrowing、最终通知@After以及环绕通知@Around。

  单单解释通知Advice可能不是很直观,其子类拦截器Interceptor可能更直观更容易理解一些。AspectJ各个不同的通知注释最终会解析并构建成为不同类型的拦截器,它们的作用就是拦截方法并在方法调用的不同时机执行拦截器定义的切入逻辑。

1、前置通知@Before

  前置通知通过@Before注解进行标注,可直接传入切点表达式的值也可以传入@Pointcut定义的切点函数名。该通知在目标函数执行前执行,其中传递的参数JoinPoint是运行时对方法调用过程的一个具体化,是一个运行时动态的概念,内部包含了被调用方法、方法所在的对象及拦截器链等信息。

2、后置通知@AfterReturning

  通过@AfterReturning注解进行标注,该函数在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同。

  请注意,在任何通知中这些参数都是可选的,需要使用时直接填写即可,不需要使用时,可以完全不用声明出来。

3、异常通知 @AfterThrowing

  该通知只有在异常时才会被触发,并由throwing来声明一个接收异常信息的变量,同样异常通知也拥有Joinpoint参数,需要时加上即可

4、最终通知 @After

  该通知有点类似于finally代码块,只要应用了无论什么情况下都会执行。

5、环绕通知@Around

  环绕通知既可以在目标方法前执行也可在目标方法之后执行,更重要的是环绕通知可以控制目标方法是否指向执行。第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来传递拦截器(通知)链或执行函数,proceed()的返回值就是环绕通知的返回值。

  同样的,ProceedingJoinPoint是运行时对方法调用过程的一个具体化,是一个运行时动态的概念,内部包含了被调用方法、方法所在的对象及拦截器链等信息,并且其相较于JoinPoint增加了proceed函数用于传递拦截器链或执行函数。

1.2.1、说明

  通知的继承路径为:Advice<-Interceptor<-MethodInterceptor

其中MethodInterceptor的接口定义如下:

public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}

  也就是说方法拦截器的子类都需要实现一个方法,接受MethodInvocation类型的参数invocation。MethodInvocation顾名思义是动态概念方法调用的具体化,其本质上是一个运行时连接点JoinPoint。

  MethodInterceptor子类在invoke方法中执行自己的业务逻辑并调用invocation.proceed()来传递拦截器调用链。例如:

Object invoke(MethodInvocation invocation) throws Throwable{
    ...do something before method invocation...
    Object obj = null;
    try{
    obj = invocation.proceed();
    }catch(Throwable e){
        ...do something after throwing...
    }finally{
        ...do something after method invoke...
    }
    ...do something after method return...
    return obj;
}

  上面的示例显示出了拦截器可以在方法调用的各个时机执行切入业务的大体实现,而前面的五种通知本质上都是上述代码的一个变种。

1.3、切面Advisor

  当符合某切点条件的函数在被执行时,就产生了一个运行时连接点Joinpoint的概念。运行时连接点代表了一个在静态连接点(程序中的某个位置)上发生的事件。例如:一次调用就是一个对于方法(静态连接点)的运行时连接点。

  在基于拦截器框架的上下文中,一个运行时连接点就是对于一个可访问对象的访问过程的具体化。Joinpoint接口定义如下:

public interface Joinpoint {
    Object proceed() throws Throwable;
    Object getThis();
    AccessibleObject getStaticPart();
}

  如上所述Joinpoint代表了运行时连接点,也就是代表了方法调用过程的具体化。因此是一个动态的概念,getThis()就是返回这个运行时连接点的动态部分(如方法所在的对象实例),而getStaticPart()就用于返回对应的静态连接点的信息(如方法定义本身)。

  另外,proceed()用于执行本运行时连接点的拦截器链上的下一个拦截器。由此可知,运行时连接点中除了维护被调用方法,方法所在的对象实例外还应该维护定义于该方法的所有拦截器(通知)。Joinpoint接口的继承链为:

Joinpoint<-Invocation<-MethodInvocation<-ProxyMethodInvocation

  从子类的名称上会更容易理解,运行时连接点更侧重的是描述一个调用的过程。其实现类为ReflectiveMethodInvocation,该类中维护的属性如下:

    protected final Object proxy;

    protected final Object target;

    protected final Method method;

    protected Object[] arguments;

    private final Class<?> targetClass;

    /**
     * Lazily initialized map of user-specific attributes for this invocation.
     */
    private Map<String, Object> userAttributes;

    /**
     * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
     * that need dynamic checks.
     */
    protected final List<?> interceptorsAndDynamicMethodMatchers;

    /**
     * Index from 0 of the current interceptor we‘re invoking.
     * -1 until we invoke: then the current interceptor.
     */
    private int currentInterceptorIndex = -1;

其中interceptorsAndDynamicMethodMatchers就是我们上面所说的拦截器链,ReflectiveMethodInvocation的proceed方法如下所示:

    @Override
    public Object proceed() throws Throwable {
        //    We start with an index of -1 and increment early.
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }

        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            // Evaluate dynamic method matcher here: static part will already have
            // been evaluated and found to match.
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
                return dm.interceptor.invoke(this);
            }
            else {
                // Dynamic matching failed.
                // Skip this interceptor and invoke the next in the chain.
                return proceed();
            }
        }
        else {
            // It‘s an interceptor, so we just invoke it: The pointcut will have
            // been evaluated statically before this object was constructed.
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

结合前面对于MethodIntercepter子类中invoke函数的实现,运行时连接点中拦截器链的调用方式如下:

  1、如果拦截器链尚未执行完,就执行拦截器链上的下一个拦截器并将this(本动态连接点)传递过去

  2、每一个拦截器的拦截函数中都执行自己的前置逻辑并调用invocation.proceed()重复步骤1

  3、档拦截器链执行完毕,则执行方法调用,并返回结果

  4、在返回的过程中,按照前面调用顺序的反向顺序执行方法调用的后置逻辑,也就是在invocation.proceed()之后编写的逻辑

  5、拦截器链反向执行完成后,最终返回结果。

由此可得,一个定义了切面的方法调用过程如下所示:

interceptor1.before()
interceptor2.before()
......
interceptorn.before()
method.invoke()
interceptorn.aft()
......
interceptor2.aft()
interceptor1.aft()

  @Before定义的通知(拦截器)只有before()逻辑;@After、@AfterReturning、@AfterThrowing定义的通知(拦截器)只有after()逻辑;@Around定义的通知(拦截器)可以自己来定义before()和after()逻辑。

原文地址:https://www.cnblogs.com/bjlhx/p/12081493.html

时间: 2024-08-30 11:15:29

011-Spring aop 002-核心说明-切点PointCut、通知Advice、切面Advisor的相关文章

spring AOP 概述(二) Pointcut

Pointcut(切点)决定Advice通知应该作用于哪个连接点,也就是说通过Pointcut来定义需要增强的方法 的集合,这些集合的选取可以按照一定的规则来完成.在这种情况下,Pointcut通常意味着标识方法,例如,这些 需要增强的地方可以由某个正则表达式进行标识,或根据某个方法名进行匹配等. 为了方便用户使用,Spring AOP提供类具体的切点供用户使用,切点在Spring AOP中的类继承体系如下 Pointcut定义如下 1 public interface Pointcut { 2

关于 Spring AOP (AspectJ) 你该知晓的一切

[版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/54629058 出自[zejian的博客] 关联文章: 关于Spring IOC (DI-依赖注入)你需要知道的一切 关于 Spring AOP (AspectJ) 你该知晓的一切 本篇是年后第一篇博文,由于博主用了不少时间在构思这篇博文,加上最近比较忙,所以这篇文件写得比较久,也分了不同的时间段在写,已尽最大能力去连贯博文中的内容

Spring -- AOP入门基础

动态代理 我们在日常开发过程中是否会遇到下图中的这种状况 红框中的是我们要输出的日志,你是否发现,日志中大部分信息都是相同的,并且如果我们要修改一个地方,所有的地方都需要改,而且代码看起来还比较冗余 下面我们就可以通过动态代理的方式解决这个问题 看下代码 public interface Calculation { public int add(int x, int y); public int sub(int x, int y); public int mul(int x, int y); p

Spring AOP的本质

不用再百科什么AOP了,我先推荐几篇文章或者系列文章:(感谢这些博文的原作者) 0.  Spring AOP 详解   http://pandonix.iteye.com/blog/336873/ 1.  AOP技术基础系列     http://wayfarer.cnblogs.com/articles/241024.html 2.  我对AOP的理解 http://jinnianshilongnian.iteye.com/blog/1474325 3.  Spring AOP本质系列  ht

Java Spring AOP用法

Spring AOP Java web 环境搭建 Java web 项目搭建 Java Spring IOC用法 spring提供了两个核心功能,一个是IoC(控制反转),另外一个便是Aop(面向切面编程),IoC有助于应用对象之间的解耦,AOP则可以实现横切关注点(如日志.安全.缓存和事务管理)与他们所影响的对象之间的解耦. 1.简介 AOP主要包含了通知.切点和连接点等术语,介绍如下 通知(Advice) 通知定义了切面是什么以及何时调用,何时调用包含以下几种 Before 在方法被调用之前

关于 Spring AOP (AspectJ) 该知晓的一切

关联文章: 关于Spring IOC (DI-依赖注入)你需要知道的一切 关于 Spring AOP (AspectJ) 你该知晓的一切 本篇是年后第一篇博文,由于博主用了不少时间在构思这篇博文,加上最近比较忙,所以这篇文件写得比较久,也分了不同的时间段在写,已尽最大能力去连贯博文中的内容,尽力呈现出简单易懂的文字含义,如文中有错误请留言,谢谢. OOP的新生机 OOP新生机前夕 神一样的AspectJ-AOP的领跑者 AspectJ的织入方式及其原理概要 基于Aspect Spring AOP

Spring AOP初步总结(一)

学习AOP有段时间了,一直没空总结一下,导致有些知识点都遗忘了,之后会把以前学过的Spring核心相关的知识点总结一轮... 先大体介绍下Spring AOP的特点(均摘自"Spring in action第四版"): Spring支持了AOP,另外还有很多实现了AOP的技术,例如AspectJ,它补充了Spring AOP框架的功能,他们之间有着大量的协作,而且Spring AOP中大量借鉴了AspectJ项目,Spring AOP相对粗粒度,而AspectJ提供更强大更细粒度的控制

Spring系列(四):Spring AOP详解

一.AOP是什么 AOP(面向切面编程),可以说是一种编程思想,其中的Spring AOP和AspectJ都是现实了这种编程思想.相对OOP(面向过程编程)来说,提供了另外一种编程方式,对于OOP过程中产生的横切性问题,这些横切性与业务无关,可以通过预编译方式和运行期动态代理来实现.比如可以应用在:日志记录.性能监控.事务管理等. 二.AOP的基本概念 Aspect(切面):通常来说是一个类,里面定义了切点和通知,Spring AOP中可以用@AspectJ来标注这个类是切面: Join poi

Spring aop 原始的工作原理的理解

理解完aop的名词解释,继续学习spring aop的工作原理. 首先明确aop到底是什么东西?又如何不违单一原则并实现交叉处理呢? 如果对它的认识只停留在面向切面编程,那就脏了.从oop(Object Oriented Programming)说起,oop引入封装,多态,继承等概念建立对象层次的结构,处理公共行为属性的集合.对于一个系统而言,需要把分散对象整合到一起的时候,oop就虚了,因为这样的需求已经在对象层次之上了.如订单模块,还款模块都需要User对象配合(当然不止于User对象完成的