avatar

目录
Spring学习 AOP

AOP

AOP即面向切面编程的简称。切面能帮助我们模块化横切关注点

定义切面术语

  1. 通知(advice)
    描述了切面要完成的工作以及何时使用
    Spring切面可以应用5中类型的通知:
    • 前置通知(Before):在目标方法被调用之前调用通知功能
    • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
    • 返回通知(After-returning):在目标方法成功执行之后调用通知;
    • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
    • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
  2. 切点(pointcut)
    定义了何处给予通知,切点的定义会匹配通知所要织入的一个或多个连接点
  3. 切面(Aspect)
    由通知和切点组合而成,它是什么(例如它是一个用于安全防护或者日志记录的切面)在何时和何处完成其功能。
  4. 连接点(join point)
    连接点是在应用执行过程中能够插入切面的一个点。简单来说,可以是你所运行方法的一个代码段。
  5. 引入(Introduction)
    引入允许我们向现有的类添加新方法或属性。
  6. 织入(Weaving)
    织入是把切面应用到目标对象并创建新的代理对象的过程,在目标对象的生命周期里有多个时间点可以进行织入:
    • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
    • 类加载器:切面在目标类加载到JVM时被织入。也需要特使的类加载器,使目标类被引入应用之前增强该目标类的字节码。AspectJ 5的类加载器就支持
    • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面是,AOP容器会为目标对象动态创建一个代理对象。Spring AOP就是以这种方式织入切面的。

Spring AOP

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

  • 基于代理的经典Spring AOP
  • 纯POJO切面;
  • @AspectJ注解驱动的切面;
  • 注入式AspectJ切面(适用于Spring各版本)

前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。
在具体学习之前,还是需要了解Spring AOP框架的一些关键知识:

  • Spring通知是Java编写的
  • Spring在运行时通知对象
  • Spring只支持方法级别的连接点

由前面的知识已知,切面是由切点和通知组成,而引入和织入是针对切面和目标对象的。所以可以按照
切点构建->通知构建->切面构建->引入、织入
的顺序来学习Spring AOP

通过切点来选择连接点

Spring仅支持AspectJ切点指示器的一个子集 如下所示
AspectJ指示器 | 描述
————– | —
arg() | 限制连接点 匹配 参数为指定类型的执行方法
@args() | 限制连接点 匹配 参数由指定注解标注的执行方法
ececution() | 用于匹配是连接点的执行方法
this() | 限制连接点 匹配 AOP代理的bean应用为指定类型的类
target() | 限制连接点 匹配 目标对象为指定类型的类
@target() | 限制连接点 匹配 特定的执行对象,这些对象对应的类要具有指定类型的注解
within() | 限制连接点 匹配 指定的类型
@within() | 限制连接点 匹配 指定注解所标注的类型
@annotation() | 限制匹配 带有指定注解的连接点

当尝试使用其他AspectJ指示器时,会抛出IllegalArgument-Exception异常。
注意只有execution指示器时是实际执行匹配的,而其他都是限制匹配的

编写切点

为了阐述Spring中的切面,我们需要有个主题来定义切面的切点。为此,我们定义一个Performance接口:

package concert;

public interface Performance {
    public void perform();
}

我们可以创建如下的切点

在切点中选择bean

Spring除了支持AspectJ切点指示器上的子集外,子集还引入了一个新的bean()指示器,
它允许我们在切点表达式中使用bean ID来表示bean。bean()使用bean ID或bean名称来限制切点只匹配特定的bean。如下例

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

或只排除特定的bean

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

使用注解创建切面

切面可以被用于 织入 或 引入 ,下面分别来谈谈

用于织入(Weaving)

AspectJ提供了五个注解来定义通知在何时触发
注解 | 通知
————– | —
@After | 通知方法会在目标方法返回或抛出异常后调用
@AfterReturning | 通知方法会在目标方法返回后调用
@AfterThrowing | 通知方法会在目标方法抛出异常后调用
@Around | 通知方法会将目标方法封装起来
@Before | 通知方法会在目标方法之前调用

以下是个示例

package concert;
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");
    }

    @AfterReturning("execution(** concert.Performance.perform(..))")
    public void applause(){
        System.out.println("applause");
    }

    @AfterThrowing("execution(** concert.Performance.perform(..))")
    public void demandRefund(){
        System.out.println("bad");
    }
}

发现了这个定义切点的指示器execution(** concert.Performance.perform(..))重复出现了三次,这一点也不优美。幸运的是Spring定义了@PointCut注解,用来简化上述情况,以下是使用@PointCut的示例

package concert;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.PointCut;

@Aspect  //声明这是个切面
public class Audience {

    @PointCut("execution(** concert.Performance.perform(..))")
    pulic void performance(){} //该方法只是一个标识,里面的内容并不重要,应为空

    @PointCut("execution(* soundsystem.CompactDisc.playTrack(int))&& args(trackNumber)")
    pulic void trackPlayed(int trackNumber){} //该方法可以支持处理通知中的参数

    @Before("performance()")
    public void silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }

    @AfterReturning("performance()")
    public void applause(){
        System.out.println("applause");
    }

    @AfterThrowing("performance()")
    public void demandRefund(){
        System.out.println("bad");
    }

    /*切点定义中的参数与切点方法中的参数名称是一样的,这样就完成了从命名切点到通知方法的参数转移*/
    @Before("trackPlayed(trackNumber)")
    public void count(int trackNumber){
        return trackNumber;
    }
}

@Around注解会有点特殊,特别的为他举一个例子

package concert;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.PointCut;

@Aspect  //声明这是个切面
public class Audience {

    @PointCut("execution(** concert.Performance.perform(..))")
    pulic void performance(){} //该方法只是一个标识,里面的内容并不重要,应为空

    @Around("performance()")
    Public void watchePerformance(ProceedingJoinPoint jp) {
        tyr{
            System.out.println("Silencing cell phones");

            /*若不调用这个方法,被环绕的目标方法将会被阻塞,即不会调用目标方法*/
            jp.proceed(); 

            System.out.println("applause");
        } catch (Throwable e){
            System.out.println("bad");
        }
    }
}  

用于引入(Introduction)

切面不仅可以实现它们所包装bean相同接口的代理,该代理还能暴露新接口,使切面所统治的bean看起来像是实现了新的接口,即便底层实现类并没有实现这些接口也无所谓。
我们首先创建一个新接口,它是我们将要引入并暴露的新接口

package concert;

public interface Encoreable {
    void performEncore();
}

当你能访问目标对象的时候,你可以选择让该类实现这个接口以扩充新功能。
但大多数情况下,例如使用第三方实现并且没有源码的时候,你并不能访问或修改目标对象以实现新接口。但值得庆幸的是,你可以借助于AOP的引入功能,来实现新接口。
以下是实现:

package concert;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

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

@DeclareParents注解由三部分组成:

  • value属性指定了哪些类型的bean要引入该接口(即value指定了目标类),标记符号后面的“+”表示是Performance的所有子类型,而不是Performance本身
  • defaultImpl属性指定了为引入功能提供实现的类(即新接口的具体实现类)
  • @DeclareParents注解所标注的静态属性指明了要引入的接口(即新接口)

让切面生效

因为Spring AOP构建在动态代理基础之上,而Spring并不是默认自动启动 自动代理功能,所以需要在配置文件中进行配置,以开启自动代理功能:

  • 基于Java的配置文件(使用@EnableAspectJAutoProxy注解)
  • 基于XML的配置文件(使用<aop:aspectj-autoproxy />元素)

同时给将该切面注册为bean。

Spring的自动代理机制将会获取到它们的声明,当Spring发现了一个bean使用了@Aspect注解时,Spring就会创建一个代理,然后将调用委托给被代理的bean或被引入的实现,这取决于调用的方法属于被代理的bean还是属于布尔诶引入的接口

基于XML的AOP配置

文章作者: f1rry
文章链接: http://yoursite.com/2019/07/20/AOP/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 F1rry's blog