Java教程

Spring官方文档学习之Spring面向切面编程

本文主要是介绍Spring官方文档学习之Spring面向切面编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1.参看spring framework之面向切面编程官方文档
2.请参看github代码学习spring面向切面编程
3.请参看Spring 实战第四版书籍

文章目录

        • 背景(完成)
        • 一.简介(完成)
        • 二.Spring AOP with AspectJ pointcuts(带有AspectJ切入点的Spring AOP)
        • 三.AOP概念和术语(完成)
          • 3.1 这些概念和术语不是Spring特有的。然而,AOP术语不是特别直观。
          • 3.2 Spring AOP包括以下类型的advice:
          • 3.3 总结
        • 四.Spring AOP的能力和目标(完成)
        • 五.AOP代理(完成)
        • 六.@AspectJ支持(完成)
        • 六.声明一个切面(完成)
        • 七.声明切入点(完成)
        • 八.切入点表达式(只介绍execution,其余请自行了解)(完成)
        • 九.声明Advice
        • 十.Introduction(引入)
        • 十一.Schema-based AOP Support(基于XML的格式)
        • 十二.总结
        • 十三.代理机制
        • 十四.理解AOP代理(重点理解!!)
        • 十五.在Spring应用程序中使用AspectJ
        • 十六. lower-level Spring AOP APIs

背景(完成)

软件系统中的一些功能需要用到应用程序的多个地方,但是我们又不想在每个点都明确调用它们。日志、安全和事务管理的确都很重要,但它们是否为应用对象主动参与的行为呢?如果让应用对象只关注于自己所针对的业务领域问题,而其他方面的问题由其他应用对象来处理,这会不会更好呢?在软件开发中,散布于应用中多处的功能被称为横切关注点(cross-cutting concern)。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中)。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。DI(依赖注入)有助于应用对象之间的解耦,而AOP可以实现横切关注点与它们所影响的对象之间的解耦。日志是应用切面的常见范例,但它并不是切面适用的唯一场景。我们还会看到切面所适用的多个场景,包括声明式事务、安全和缓存。
在这里插入图片描述

一.简介(完成)

1.Aspect-oriented Programming(AOP,面向切面编程)通过提供另一种思考程序结构的方式,补充了面向对象编程(OOP)。在OOP中,模块化的关键单元是类,而在AOP中,模块化的单元是切面。切面支持跨多个类型和对象的关注点(如事务管理)的模块化(在AOP文献中这种关注通常被称为"横切"关注点)。

2.Spring的关键组件之一是AOP框架。虽然Spring IoC容器不依赖于AOP(这意味着如果您不想使用AOP的话,就不需要使用AOP),但是AOP补充了spring ioc,提供了一个非常强大的中间件解决方案

3.AOP在Spring框架中用于:(1)提供声明性企业服务。最重要的此类服务是声明式事务管理。(2)让用户实现自定义切面,用AOP补充OOP的使用。

二.Spring AOP with AspectJ pointcuts(带有AspectJ切入点的Spring AOP)

Spring通过使用基于模式(schema-based)的方法或@AspectJ注释样式提供了编写自定义切面的简单且强大的方法。这两种风格都提供了完整类型的advice和AspectJ切入点语言的使用,同时仍然使用Spring AOP进行编织。

三.AOP概念和术语(完成)

3.1 这些概念和术语不是Spring特有的。然而,AOP术语不是特别直观。

1.Aspect(切面):A modularization of a concern that cuts across multiple classes(跨多个类的关注的模块化)。事务管理是企业Java应用程序中crosscutting concern(横切关注点)的一个很好的例子。在Spring AOP中,切面通过使用常规类(基于模式schema-based的方法)或使用@Aspect注释的常规类(@AspectJ风格)来实现。

2.Join point(连接点):程序执行过程中的一个点,如方法的执行或异常的处理。在Spring AOP中,连接点总是表示方法的执行

3.Advice(通知):切面在一个特定的连接点上采取的操作,Different types of advice include “around”,“before” and “after” advice(advice的类型之后再讨论),许多AOP框架,包括Spring,将advice建模为拦截器,并在连接点周围维护拦截器链

4.Pointcut(切入点):匹配连接点的谓词。Advice与切入点表达式相关联,并在与切入点匹配的任何连接点上运行(例如,具有特定名称的方法的执行)。由切入点表达式匹配的连接点的概念是AOP的核心,Spring默认使用AspectJ切入点表达式语言

5.Introduction(引入):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何advised object(被通知的对象)引入新接口(和相应的实现)。

6.Target object(目标对象):An object being advised by one or more aspects,也被称为 “advised object”。因为Spring AOP是通过使用运行时代理实现的,所以这个对象总是一个被代理的对象。

7.AOP proxy(AOP代理):由AOP框架创建的一个对象,in order to implement the aspect contracts (advise method executions and so on)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理

8.Weaving(编织/织入):将切面与其他应用程序类型或对象链接起来,以创建advised object。这可以在编译时(例如使用AspectJ编译器)、加载时或运行时完成。与其他纯Java AOP框架一样,Spring AOP在运行时执行编织/织入。

3.2 Spring AOP包括以下类型的advice:

1.Before advice:通知在连接点之前运行,但不能阻止执行流继续流向连接点(除非它引发异常)。

2.After returning advice:在连接点正常完成后运行的通知(例如,如果方法返回时没有抛出异常)。

3.After throwing advice:如果方法通过抛出异常而退出,则执行advice。

4.After (finally) advice:不管连接点以何种方式退出(正常或异常返回)都要执行的adive。

5.Around advice(环绕通知):围绕连接点(如方法调用)的advice。这是最有力的advice。Around advice可以在方法调用之前和之后执行自定义行为。它还负责选择是继续到连接点,还是通过返回自己的返回值或抛出异常来简化被建议的方法的执行(whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception)

3.3 总结

1.Around advice是最普遍的一种advice。因为像AspectJ一样,Spring AOP提供了全方位的通知类型,我们建议您使用功能最弱的advice类型来实现所需的行为。例如,如果只需要使用方法的返回值更新缓存,那么最好实现after returning advice,而不是around advice,尽管around建议可以完成相同的任务。使用最具体的建议类型提供了一个更简单的编程模型,减少了出错的可能性。

2.由切入点匹配的连接点的概念是AOP的关键,这将它与仅提供拦截的旧技术区别开来。切入点使 advice 能够独立于面向对象的层次结构作为目标。例如,您可以将提供声明性事务管理的Around advice应用于跨越多个对象的一组方法(例如服务层中的所有业务操作)。

四.Spring AOP的能力和目标(完成)

1.Spring AOP是用纯Java实现的。不需要特殊的编译过程。Spring AOP不需要控制类加载器的层次结构,因此适合在Servlet容器或应用程序服务器中使用。

2.Spring AOP目前只支持方法执行连接点(建议在spring bean上执行方法)。尽管可以在不破坏核心Spring AOP API的情况下添加对字段拦截的支持,但并未实现字段拦截。 If you need to advise field access and update join points,请考虑使用诸如AspectJ之类的语言。

3.Spring AOP的AOP方法不同于大多数其他AOP框架。其目的不是提供最完整的AOP实现(尽管spring aop非常有能力)。相反,其目的是在AOP实现和Spring IoC之间提供紧密的集成,以帮助解决企业应用程序中的常见问题。因此,例如,Spring框架的AOP功能通常与Spring IoC容器一起使用。切面通过使用普通的bean定义语法进行配置(although this allows powerful “auto-proxying” capabilities)。这是与其他AOP实现的一个关键区别。使用Spring AOP不能轻松或高效地完成某些事情,例如advise(通知)非常细粒度的对象(通常是域对象)。在这种情况下,AspectJ是最佳选择。

4.Spring AOP从未试图与AspectJ竞争,以提供一个全面的AOP解决方案。我们相信基于代理的框架(如Spring AOP)和成熟的框架(如AspectJ)都是有价值的,它们是互补的,而不是竞争的。

五.AOP代理(完成)

1.Spring AOP默认使用标准的JDK动态代理作为AOP代理。这允许代理任何接口(或一组接口)。Spring AOP也可以使用CGLIB代理。这对于代理类而不是接口来说是必要的。默认情况下,如果业务对象没有实现接口,则使用CGLIB。

2.重要的是要理解Spring AOP是基于代理的。
在这里插入图片描述

六.@AspectJ支持(完成)

1.@AspectJ将切面声明为带有注释的常规Java类。@AspectJ风格是AspectJ项目作为AspectJ 5发行版的一部分引入的。Spring使用AspectJ提供的库进行切入点解析和匹配,解释与AspectJ 5相同的注解。但是,AOP运行时仍然是纯spring aop,并且不依赖于AspectJ编译器或编织器。使用AspectJ编译器和编织器可以使用完整的AspectJ语言,之后将在AspectJ与Spring应用程序结合使用中讨论。

2.启用@AspectJ支持
(1)要在Spring配置中使用@AspectJ切面,您需要启用Spring支持,以便基于@AspectJ切面配置Spring AOP,并根据是否得到这些切面的通知启用自动代理bean。通过自动代理,如果Spring确定一个bean被一个或多个切面advice,它会自动为该bean生成一个代理来拦截方法调用,并确保在需要时执行advice

(2)可以通过XML或java风格的配置启用@AspectJ支持。无论是哪种情况,您还需要确保AspectJ的aspectjweaver.jar位于应用程序的类路径上。比如:

		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.9.5</version>
		</dependency>

在这里插入图片描述

六.声明一个切面(完成)

1.启用@AspectJ支持后,Spring会自动检测应用程序上下文中定义的具有@Aspect注释的类的任何bean,并将其用于配置Spring AOP。请注意,@Aspect注释不足以在类路径中进行自动检测。为此,您需要将其装配为Spring中的bean。
在这里插入图片描述

2.切面(用@Aspect注释的类)可以有方法和字段,与任何其他类一样。它们还可以包含切入点、advice和introduction (类型间)声明。

3.在Spring AOP中,切面本身不能成为来自其他切面的advice的目标。类上的@Aspect注释将其标记为切面,因此将其排除在自动代理之外

七.声明切入点(完成)

1.切入点确定了感兴趣的连接点,从而使我们能够控制advice何时执行。Spring AOP只支持Spring bean的方法执行连接点,所以可以把切入点看作是与Spring bean上的方法执行相匹配的。切入点声明有两部分:由名称和任何参数组成的签名,以及确定我们感兴趣的方法执行的切入点表达式。在AOP的@AspectJ注释风格中,切入点签名是由一个常规方法定义提供的,切入点表达式是通过使用@Pointcut注释来表示的(作为切入点签名的方法必须具有void返回类型)

    //声明一个切入点
    @Pointcut("execution(* com.mashirro.springaop_learn_demo.service.impl.UserServiceImpl.*(..))")  //切入点表达式
    private void pointcut(){}   //切入点签名

在这里插入图片描述

2.Spring AOP支持在切入点表达式中使用以下AspectJ切入点指示符(PCD):

  • 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中执行的方法)具有给定的注释

在这里插入图片描述

3.完整的AspectJ切入点语言支持Spring中不支持的其他切入点指示符:call、get、set、preinitialization、staticinitialization、initialization、handler、adviceecution、withincode、cflow、cflowbellow、if、@this和@withincode。在Spring AOP解释的切入点表达式中使用这些切入点指示符会导致抛出IllegalArgumentException。Spring AOP支持的切入点指示符集可能在将来的版本中得到扩展,以支持更多的AspectJ切入点指示符。

4.由于Spring AOP只将匹配限制为方法执行连接点,因此前面关于切入点指示符的讨论给出了比您在AspectJ编程指南中可以找到的更窄的定义。此外,AspectJ本身具有基于类型的语义,在执行连接点,this和target都引用同一个对象:执行方法的对象。Spring AOP是基于代理的系统,可区分代理对象本身(绑定到this)和代理后面的目标对象(绑定到target)

5.由于Spring的AOP框架基于代理的特性,根据定义,目标对象内的调用不会被拦截。对于JDK代理,只有该代理上的 public 接口方法调用可以被拦截。使用CGLIB,将拦截代理上的public(公共)方法和 protected( 受保护的)方法调用(必要时,甚至是包可见的方法)

6.如果您的拦截需求包括目标类中的方法调用甚至构造函数,请考虑使用Spring驱动的本地AspectJ编织,而不是使用Spring的基于代理的AOP框架。。

八.切入点表达式(只介绍execution,其余请自行了解)(完成)

1.execution执行表达式格式如下:
execution(修饰符 返回值类型 声明类型 方法名称(参数列表) 抛出的异常列表)

  • 修饰符:可选。
  • 返回值类型:*最常用作返回类型。它匹配任何返回类型。仅当方法返回给定类型时,完全限定类型名才匹配。
  • 声明类型:可选。如果指定了声明类型模式,请在其末尾添加".",以将其连接到方法名称。
  • 方法名称:可以使用*通配符作为方法名称的全部或部分。
  • 抛出的异常列表:可选。比如throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException
  • 参数列表:如下说法
()匹配不带参数的方法,而(..)匹配任何数量(零个或多个)的参数。
(*)模式与接受任何类型的一个参数的方法匹配。(*,String)匹配接受两个参数的方法。第一个可以是任何类型,而第二个必须是字符串。

2.最普遍的写法如下:
此包中定义的任何方法的执行:

execution(* com.mashirro.springaop_learn_demo.service.impl.*.*(..))

九.声明Advice

advice与切入点表达式关联,并在与切入点匹配的方法执行之前、之后或周围运行。

1.有时,您需要在通知主体中访问返回的实际值。你可以使用@AfterReturning的形式来绑定返回值来获得访问。 returning 属性中使用的名称必须与通知方法中的参数名称相对应。当方法执行返回时,返回值作为相应的参数值传递给advice方法。返回子句还限制只匹配那些返回指定类型值的方法执行(在本例中是Object,它匹配任何返回值)

    @AfterReturning(pointcut = "pt1()", returning = "retVal")
    public void afterReturningAdvice(Object retVal) {
        System.out.println(retVal);
        System.out.println("afterReturningAdvice...");
    }

2.通常,您希望通知仅在抛出给定类型的异常时才运行,并且还经常需要访问通知体中抛出的异常。可以使用throwing属性来限制匹配并将抛出的异常绑定到一个通知参数。throwing属性中使用的名称必须与通知方法中的参数名称相对应。当抛出异常退出方法执行时,该异常将作为相应的参数值传递给通知方法。throwing 子句还将匹配限制为仅对那些抛出指定类型异常(本例中为DataAccessException)的方法执行进行匹配。

	@AfterThrowing(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }

3.Around advice"围绕"一个匹配方法的执行运行。它有机会在方法运行之前和之后工作,并确定该方法何时、如何、甚至是否真正开始运行。。Around advice是通过使用@Around注释声明的。该方法应该声明Object为其返回类型,并且该方法的第一个参数必须是ProceedingJoinPoint类型。在通知方法的主体中,对ProceedingJoinPoint调用proceed()将导致底层方法执行。不带参数调用proceed()将导致调用者的原始参数在调用时被提供给底层方法。有一个proceed()方法的重载变体,它接受一个参数数组(Object[])。数组中的值将被用作底层方法调用时的参数。

4.如果您将around通知方法的返回类型声明为void,则始终将返回null给调用者,忽略了proceed()的任何调用的结果。因此,建议使用around通知方法声明Object的返回类型。通知方法通常应该返回从proceed()调用返回的值,即使底层方法的返回类型是空的。然而,根据用例的不同,通知可以选择性地返回缓存的值、包装的值或其他一些值。

5.任何advice方法都可以将org.aspectj.lang.JoinPoint类型的参数声明为它的第一个参数。请注意,around通知需要声明一个类型为ProceedingJoinPoint的第一个参数,它是JoinPoint的一个子类。JoinPoint接口提供了许多有用的方法:

  • getArgs():返回方法参数
  • getThis():返回代理对象。
  • getTarget():返回目标对象。
  • getSignature():返回通知方法的描述。

6.我们已经看到了如何绑定返回值或异常值(使用@AfterThrowing和@AfterReturning)。要使参数值可用于通知正文(advice body),可以使用args的绑定形式。如果在args表达式中使用参数名代替类型名,则在调用通知时,相应参数的值将作为参数值传递
在这里插入图片描述
在这里插入图片描述

7.当多个通知都希望在同一连接点上运行时,会发生什么情况?
(1)当在不同切面定义的两个通知都需要在同一个连接点上运行时,除非另有说明,否则执行顺序是未定义的。您可以通过指定优先级来控制执行顺序。这是通过在切面类中实现org.springframework.core.Ordered接口或使用@Order注释来完成的,这是Spring的常规方式。给定两个切面,从Ordered.getOrder()返回较低值(或注释值)的切面具有较高的优先级。

(2)从Spring Framework 5.2.7开始,在同一@Aspect类中定义的、需要在同一连接点上运行的通知方法根据其通知类型按以下顺序分配优先级:从最高优先级到最低优先级:@Around、@Before、@After、@AfterReturning、@AfterThrowing。然而,请注意,@After advice方法将在同一切面的任何@AfterReturning或@AfterThrowing advice方法之后有效地被调用,遵循AspectJ对@After的"After finally advice"语义。

十.Introduction(引入)

1.引入(在AspectJ中称为类型间声明)使切面能够声明被通知的对象实现给定的接口,并代表这些对象提供该接口的实现。您可以使用@DeclareParents注释进行引入。此注释用于声明匹配类型有一个新的父类级(因此而得名)。例如,给定一个名为UsageTracked的接口和一个名为DefaultUsageTracked的该接口的实现,以下切面声明服务接口的所有实现者也实现了UsageTracked接口:

@Aspect
public class UsageTracking {
    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;
    
    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }
}

在这里插入图片描述
**加粗样式**
在这里插入图片描述
在这里插入图片描述

十一.Schema-based AOP Support(基于XML的格式)

略,请自行学习

十二.总结

1.选择Spring AOP or Full AspectJ?
Spring AOP比使用完整的AspectJ更简单,因为不需要在开发和构建过程中引入AspectJ编译器/编织器。如果只需要通知在spring bean上执行操作,那么Spring AOP是正确的选择。如果需要通知不由Spring容器管理的对象(例如通常是域对象),则需要使用AspectJ。如果希望通知除简单方法执行之外的连接点(例如,field get或set join points等),还需要使用AspectJ。

2.选择@AspectJ or XML for Spring AOP?
如果您选择使用Spring AOP,则可以选择@AspectJ或XML风格。具体如何选择略

十三.代理机制

1.Spring AOP使用JDK动态代理或CGLIB为给定的目标对象创建代理。JDK动态代理内置在JDK中,而CGLIB是一个通用的开源类定义库(重新打包到spring-core)。

2.如果要代理的目标对象至少实现一个接口,则使用JDK动态代理。目标类型实现的所有接口都被代理。如果目标对象未实现任何接口,则创建CGLIB代理。

3.如果您想强制使用CGLIB代理(例如,代理为目标对象定义的每个方法,而不仅仅是由其接口实现的方法),您可以这样做。但是,你应该考虑以下问题:

  • 对于CGLIB,不能通知final方法,因为它们不能在运行时生成的子类中重写
  • 从Spring 4.0开始,代理对象的构造函数不再调用两次,因为CGLIB代理实例是通过Objenesis创建的。

十四.理解AOP代理(重点理解!!)

1.首先考虑这样一个场景:你有一个普通的、没有代理的、没什么特别的、直接的对象引用,如下代码片段所示:

public class SimplePojo implements Pojo {
    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }
    public void bar() {
        // some logic...
    }
}

2.如果你在一个对象引用上调用一个方法,这个方法会直接在那个对象引用上被调用,如下图和清单所示:

public class Main {
    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}

在这里插入图片描述
3.当客户端代码的引用是代理时,情况会略有变化。考虑以下图表和代码片段:

public class Main {
    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

在这里插入图片描述

4.这里需要理解的关键是,main类的main(…)方法中的客户端代码具有对代理的引用。这意味着对该对象引用的方法调用就是对代理的调用。因此,代理可以委托给与该特定方法调用相关的所有拦截器(通知)。然而,一旦调用最终到达目标对象(在本例中是SimplePojo引用),它可能对自身进行的任何方法调用,例如this.bar()或this.foo(),都将对这个引用进行调用,而不是对代理进行调用。这具有重要的意义。这意味着自调用不会导致与方法调用关联的通知有机会运行

十五.在Spring应用程序中使用AspectJ

到目前为止,我们在本章中介绍的所有内容都是纯 Spring AOP。在本节中,如果您的需求超出了 Spring AOP 单独提供的功能,我们将了解如何使用 AspectJ 编译器或编织器来代替 Spring AOP 或作为 Spring AOP 的补充。

本节略,请自行学习!

十六. lower-level Spring AOP APIs

请参看spring文档!

这篇关于Spring官方文档学习之Spring面向切面编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!