Spring Aop 详解(上)

简介: 面向切面编程(AOP)是面向对象编程(OOP)的补充,它提供了另一种关于程序结构的思考方式。OOP中模块化的关键单元是类,而在AOP中,模块化单元是切面。切面支持跨多个类型和对象的切点(如事务管理)的模块化。Spring AOP 是 Spring 框架的关键组件之一。Spring IOC 容器不依赖于AOP组件,如果不要我们项目中不需要 AOP 功能那么就可以不加载这个模块。AOP 补充了 Spring IOC,以提供一个非常强大的中间件解决方案。

Spring Aop


Spring Aop 核心概念


  1. Aspect: 切面是包含了多个切点,事务管理器(Transaction management)就是一个很好的例子,通常在 spring aop 中通过 @Aspect 来申明


  1. Join point: 连接点,表示具体的方式执行的一个点,异常处理或者方法执行。


  1. Advice: 通知,在特定的连接点才去操作,比如在通知之前,通知之后,通知过程中增加自己的逻辑。


  1. Pointcut: 切点,表示我们想再那个具体的方法,那个具体或者匹配一类 "select" 开头的方法。这里支持 EL 表达式进行匹配。


  1. Introduction: 引入,表示 AOP 可以在代理过程中,将一个已有的类引入新的接口。


  1. Target object: 当前对象,其实也是原始对象,如果发生 AOP 代理的时候,此时返回的始终是 AOP 代理对象。


  1. AOP proxy: AOP 创建的代理对象。


  1. Weaving: 切面植入时机,可以是运行时,运行前,运行后。


Spring AOP 包括以下几个类型的通知(Advice):


前置通知:在连接点之前运行的通知,但是它不能阻止执行流程前进到连接点(除非它引发异常)。


后置通知:在连接点正常完成之后要运行的通知(例如,如果方法返回而没有引发异常)。


异常通知:如果方法因引发异常而退出,则运行通知。


建议(最终)之后:无论连接点退出的方式如何(正常或特殊收益),均应执行通知。

环绕通知:围绕连接点的通知,例如方法调用。这是最有力的通知。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是返回连接点还是通过返回其自身的返回值或引发异常来结束方法的执行


环绕通知是最通用的通知。由于 Spring AOP 与 AspectJ 一样,提供了各种通知类型,建议使用功能最弱的建议类型,以实现所需的行为。例如,如果您只需要使用方法的返回值更新缓存,则最好使用后置通知而不是环绕通知,尽管环绕通知可以完成相同的事情。使用最具体的通知类型可以提供更简单的编程模型,并减少出错的可能性。例如,您不需要调用用于around通知的proceed() 方法JoinPoint,因此不会导致主业务失败。


所有建议参数都是具体的类型,因此您可以使用适当类型(例如,方法执行的返回值的类型)而不是Object数组的建议参数。


切入点匹配的连接点的概念是 AOP 的关键,它与仅提供拦截功能的旧技术有所不同。切入点使建议的目标独立于面向对象的层次结构。例如,可以将提供声明性事务管理的环绕建议应用于跨越多个对象(例如服务层中的所有业务操作)的一组方法。


Spring Aop 实现概述


Spring AOP默认将标准JDK动态代理用于AOP代理。这使得可以代理任何接口(或一组接口)。


Spring AOP也可以使用CGLIB代理。这对于代理类而不是接口是必需的。默认情况下,如果业务对象未实现接口,则使用CGLIB。由于最好是对接口而不是对类进行编程,因此业务类通常实现一个或多个业务接口。在某些情况下(可能极少数),当您需要建议未在接口上声明的方法或需要将代理对象作为具体类型传递给方法时,可以强制使用 CGLIB。


总结: Spring AOP 默认是采用 JDK 动态代理,通常情况下,如果一个类没有实现接口那么就是用 GCLIB 代理


Spring Aop 常用注解


1. 启动 @AspectJ支持


要在Spring配置中使用@AspectJ切面,您需要启用Spring支持以基于@AspectJ方面配置Spring AOP,并基于这些切面是否触发通知对Bean进行自动代理。通过自动代理,我们的意思是,如果Spring确定一个或多个切面通知使用bean,它将自动为该bean生成一个代理以拦截方法调用并确保按需运行建议。


可以使用XML或Java样式的配置来启用@AspectJ支持。无论哪种情况,您都需要确保AspectJ的aspectjweaver.jar库位于应用程序的类路径(版本1.8或更高版本)上。

代码例子:


@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}


2. 声明一个切面


启用@AspectJ支持后,@Aspect Spring会自动检测到在应用程序上下文中使用@AspectJ方面(具有注释)的类定义的任何bean,并用于配置Spring AOP。下面是一个声明切面的例子


代码例子:


@Aspect
public class LogAspect {
}


3. 声明一个切入点


切入点确定了拦截的连接点,从而使我们能够控制运行建议的时间。Spring AOP仅支持Spring Bean的方法执行连接点,因此您可以将切入点视为与Spring Bean上的方法执行相匹配。切入点声明由两部分组成:一个包含名称和任何参数的签名,以及一个切入点表达式,该切入点表达式精确地确定我们感兴趣的方法执行。在AOP的@AspectJ批注样式中,常规方法定义提供了切入点签名。 并通过使用@Pointcut 注解定义切入点表达式(用作切入点签名的方法必须具有void返回类型)。


一个例子:


@Pointcut("execution(* cn.edu.xxx.service.*.*(..))")
public void serviceOperation() {
}


Spring AOP支持以下在切入点表达式中使用的AspectJ切入点指示符:


  • execution:用于匹配方法执行的连接点。这是使用Spring AOP时要使用的主要切入点指示符。


  • within:将匹配限制为某些类型内的连接点(使用Spring AOP时,在匹配类型内声明的方法的执行)。


  • this:限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中bean引用(Spring AOP代理)是给定类型的实例。


  • target:在目标对象(代理的应用程序对象)是给定类型的实例的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。


  • args:将匹配限制为连接点(使用Spring AOP时方法的执行),其中参数是给定类型的实例。


  • @target:在执行对象的类具有给定类型的注释的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。


  • @args:限制匹配的连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。


  • @within:将匹配限制为具有给定注释的类型内的连接点(使用Spring AOP时,使用给定注释的类型中声明的方法的执行)。


  • @annotation:将匹配点限制为连接点的主题(在Spring AOP中运行的方法)具有给定注释的连接点。


4. 定义通知


议与切入点表达式关联,并且在切入点匹配的方法执行之前,之后或周围运行。切入点表达式可以是对命名切入点的简单引用,也可以是就地声明的切入点表达式。


在同一个定义的通知方法@Aspect的类,需要在同一连接点运行基于分配优先级上按以下顺序他们的通知类型,从最高到最低的优先级:@Around@Before@After@AfterReturning@AfterThrowing。但是请注意,由于Spring的实现风格,在同一方面中的any或advice方法之后AspectJAfterAdvice@Afteradvice方法将被有效地调用。@AfterReturning@AfterThrowing


当在同一类中@After定义的两个相同类型的建议(例如,两个建议方法)@Aspect都需要在同一连接点上运行时,其顺序是不确定的(因为无法通过以下方式检索源代码声明顺序) Javac编译类的反射)。请考虑将此类建议方法折叠为每个@Aspect类中每个连接点的一个建议方法,或将这些建议重构为单独的@Aspect类,您可以通过Ordered或 在方面级别进行排序@Order


定义通知的例子:


@Before("cn.edu.cqvie.aspect.LogAspect.serviceOperation()")
public void doServiceCheck() {
    System.out.println("doServiceCheck .....");
}
@After("cn.edu.cqvie.aspect.LogAspect.serviceOperation()")
public void doReleaseLock() {
    System.out.println("doReleaseLock .....");
}
@AfterReturning(
        pointcut="cn.edu.cqvie.aspect.LogAspect.serviceOperation()",
        returning="retVal")
public void doServiceCheck(Object retVal) {
    System.out.println("doServiceCheck ....." + retVal);
}


5. 引入


简介(在AspectJ中称为类型间声明)使方面可以声明建议对象实现给定的接口,并代表那些对象提供该接口的实现。 您可以使用 @DeclareParents 进行引入。此批注用于声明匹配类型具有新的父类(因此具有名称)。


Spring Aop 核心原理


前面我们分析了 Spring AOP 的使用方法和基础概念,下面我们来继续分析一下 Spring AOP 的原理。


AnnotationAwareAspectJAutoProxyCreator  AOP 后置处理器


Spring 再初始化 bean 的过程中,完成初始化过后,就执行 AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization 来执行代理,所以,代理的时机是在 Spring 初始化完成之后,并且在将 Bean 完整对象放入 Spring Bean 容器之前其目的就是将 Bean 的原始对象 BeanWapper 转换为一个代理对象。执行的过程如下:


  1. 先判断当前的 bean 是不是要进行AOP,比如当前的Bean的类型是 Pointcut, Advice, Advisor 等那些就不需要 AOP。


  1. 如果匹配到 Advisors 不为 null, 那么进行代理并且返回代理对象。


  1. 判断代理实现如果实现如果没有设置为 GCLIB 并且实现了接口,那么就采用 JDK 代理,否则使用 GCLIB。


  1. 执行代理的代码。


ProxyFactory 代理工厂


ProxyFactory 是一个代理工厂类,我们在使用 AOP 代理之前首先需要通过代理工厂获取一个具体的 AOP 代理对象 AopProxy 的实例,Spring 的源码如下:


public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // config 就是 ProxyFactory 对象
    // optimize为 true,或 proxyTargetClass 为 true,或用户没有给 ProxyFactory 对象添加 interface
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        // targetClass是接口,直接使用Jdk动态代理
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        // 使用Cglib
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        // 使用Jdk动态代理
        return new JdkDynamicAopProxy(config);
    }
}


在这里我们需要关注两个类 JdkDynamicAopProxy, ObjenesisCglibAopProxy


相关文章
|
3天前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
1月前
|
Java Spring
在Spring Boot中使用AOP实现日志切面
在Spring Boot中使用AOP实现日志切面
|
3天前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
6天前
|
安全 Java 开发者
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
14 1
|
6天前
|
Java Spring
Spring的AOP组件详解
该文章主要介绍了Spring AOP(面向切面编程)组件的实现原理,包括Spring AOP的基础概念、动态代理模式、AOP组件的实现以及Spring选择JDK动态代理或CGLIB动态代理的依据。
Spring的AOP组件详解
|
19天前
|
Java API Spring
Spring Boot 中的 AOP 处理
对 Spring Boot 中的切面 AOP 做了详细的讲解,主要介绍了 Spring Boot 中 AOP 的引入,常用注解的使用,参数的使用,以及常用 api 的介绍。AOP 在实际项目中很有用,对切面方法执行前后都可以根据具体的业务,做相应的预处理或者增强处理,同时也可以用作异常捕获处理,可以根据具体业务场景,合理去使用 AOP。
|
27天前
|
Java Spring 容器
Spring问题之Spring AOP是如何实现面向切面编程的
Spring问题之Spring AOP是如何实现面向切面编程的
|
23天前
|
缓存 安全 Java
Spring高手之路21——深入剖析Spring AOP代理对象的创建
本文详细介绍了Spring AOP代理对象的创建过程,分为三个核心步骤:判断是否增强、匹配增强器和创建代理对象。通过源码分析和时序图展示,深入剖析了Spring AOP的工作原理,帮助读者全面理解Spring AOP代理对象的生成机制及其实现细节。
17 0
Spring高手之路21——深入剖析Spring AOP代理对象的创建
|
3天前
|
XML Java 数据库
Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
这篇文章是Spring5框架的实战教程,详细解释了AOP的关键术语,包括连接点、切入点、通知、切面,并展示了如何使用AspectJ注解来开发AOP实例,包括切入点表达式的编写、增强方法的配置、代理对象的创建和优先级设置,以及如何通过注解方式实现完全的AOP配置。
|
1月前
|
Java Spring
在Spring Boot中使用AOP实现日志切面
在Spring Boot中使用AOP实现日志切面