Spring AOP - ParadiseGuo/JavaLearning GitHub Wiki

AOP: 面向切面编程。 面向对象(OOP)是从静态角度考虑程序结构,面向切面编程是从动态角度考虑程序运行过程。

Spring有如下两种选择来定义切入点和增强处理:

  • 基于annotation的零配置当时:使用@Aspect,@Pointcut等annotation来标注切入点和增强处理
  • 基于XML配置文件的管理方式:使用Spring配置文件来定义切入点和增强点

OOP和AOP的关系

OOP针对业务处理过程的实体(Dog、Cat、Duck)及其属性和行为(run)进行抽象封装,以获得更加清晰高效的逻辑单元划分。而AOP则是针对业务处理过程中(run或jump)的切面(command和award)进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。

1.基于Annotation的零配置方式

(1) 首先启用Spring对_@AspectJ_切面配置的支持。

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

   `xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`

   `xmlns:aop="http://www.springframework.org/schema/aop"`

   `xsi:schemaLocation="http://www.springframework.org/schema/beans`

       `http://www.springframework.org/schema/beans/spring-beans-3.0.xsd`

       `http://www.springframework.org/schema/aop`

   `http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">`

    `<!-- 启动对@AspectJ注解的支持 -->`

    `<aop:aspectj-autoproxy/>`

</beans>

(2)定义切面Bean

当启动了@AspectJ支持后,只要在Spring容器中配置一个带@Aspect注释的Bean,Spring将会自动识别该Bean并作为切面处理。

// 使用@Aspect 定义一个切面类

@Aspect

public class LogAspect {

    `// 定义该类的其他内容`

    `...`

}

(3)定义Before增强处理

// 定义一个切面

@Aspect

public class BeforeAdviceTest {

`// 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点`

`@Before("execution(* com.wicresoft.app.service.impl.*.*(..))")`

`public void authorith(){`

    `System.out.println("模拟进行权限检查。");`

`}`

}

上面使用@Before Annotation时,直接指定了切入点表达式,指定匹配_com.wicresoft.app.service.impl_包下所有类的所有方法执行作为切入点。

(4)定义AfterReturning增强处理

// 定义一个切面

@Aspect

public class AfterReturningAdviceTest {

`// 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点`

`@AfterReturning(returning="rvt", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))")`

`public void log(Object rvt) {`

    `System.out.println("模拟目标方法返回值:" + rvt);`

    `System.out.println("模拟记录日志功能...");`

`}`

}

(5)定义AfterThrowing增强处理

// 定义一个切面

@Aspect

public class AfterThrowingAdviceTest {

`// 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点`

`@AfterThrowing(throwing="ex", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))")`

`public void doRecoverActions(Throwable ex) {`

    `System.out.println("目标方法中抛出的异常:" + ex);`

    `System.out.println("模拟抛出异常后的增强处理...");`

`}`

}

(6)定义After增强处理

After增强处理与AfterReturning增强处理的区别是:

  • AfterReturning处理只有在目标方法成功完成后才会被植入
  • After不管目标方法如何结束(成功执行完成或遇到异常终止),它都会被植入

// 定义一个切面

@Aspect

public class AfterAdviceTest {

`// 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点`

`@After("execution(* com.wicresoft.app.service.impl.*.*(..))")`

`public void release() {`

    `System.out.println("模拟方法结束后的释放资源...");`

`}`

}

(7) Around增强处理

// 定义一个切面

@Aspect

public class AroundAdviceTest {

`// 匹配 com.wicresoft.app.service.impl 包下所有类的所有方法作为切入点`

`@Around("execution(* com.wicresoft.app.service.impl.*.*(..))")`

`public Object processTx(ProceedingJoinPoint jp) throws java.lang.Throwable {`

    `System.out.println("执行目标方法之前,模拟开始事物...");`

    `// 执行目标方法,并保存目标方法执行后的返回值`

    `Object rvt = jp.proceed(new String[]{"被改变的参数"});`

    `System.out.println("执行目标方法之前,模拟结束事物...");`

    `return rvt + "新增的内容";`

`}`

}

(8)访问目标方法的参数

访问目标方法最简单的做法是定义增强处理方法时将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了植入增强处理的连接点。JoinPoint里包含了如下几个常用方法:

  • Object[] getArgs(): 返回执行目标方法时的参数。
  • Signature getSignature(): 返回被增强的方法的相关信息。
  • Object getTarget(): 返回被织入增强处理的目标对象。
  • Object getThis(): 返回 AOP 框架为目标对象生成的代理对象。

当时使用 Around 处理时,我们需要将第一个参数定义为 ProceedingJoinPoint 类型,该类型是 JoinPoint 类型的子类。

(9) 定义切入点

所谓切入点,其实质就是为一个切入点表达式起一个名称,从而允许在多个增强处理中重用该名称。 Spring切入点定义包含两个部分:

  • 一个切入点表达式
  • 一个包含名字和任意参数的方法签名

// 使用@Pointcut Annotation 时指定切入点表达式

@pointcut("execution * transfer(..)")

// 使用一个返回值为void,方法体为空的方法来命名切入点

private void anyOldTransfer(){}

// 使用上面定义的切入点

@AfterReturning(pointcut="anyOldTransfer()", returning="reVal")

public void writeLog(String msg, Object reVal){

`...`

}

2. 基于XML配置文件的管理方式

  • 不配置切入点

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

   `xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`

   `xmlns:aop="http://www.springframework.org/schema/aop"`

   `xsi:schemaLocation="http://www.springframework.org/schema/beans`

       `http://www.springframework.org/schema/beans/spring-beans-3.0.xsd`

       `http://www.springframework.org/schema/aop`

   `http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">`

    `<aop:config>`

        `<!-- 将 fourAdviceBean 转换成切面 Bean, 切面 Bean 的新名称为:fourAdviceAspect,指定该切面的优先级为2 -->`

        `<aop:aspect id="fourAdviceAspect" ref="fourAdviceBean" order="2">`

            `<!-- 定义个After增强处理,直接指定切入点表达式,以切面 Bean 中的 Release() 方法作为增强处理方法 -->`

            `<aop:after pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="release" />`
            
            `<!-- 定义个Before增强处理,直接指定切入点表达式,以切面 Bean 中的 authority() 方法作为增强处理方法 -->`

            `<aop:before pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="authority" />`
            
            `<!-- 定义个AfterReturning增强处理,直接指定切入点表达式,以切面 Bean 中的 log() 方法作为增强处理方法 -->`

            `<aop:after-returning pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="log" />`
            
            `<!-- 定义个Around增强处理,直接指定切入点表达式,以切面 Bean 中的 processTx() 方法作为增强处理方法 -->`

            `<aop:around pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="processTx" />`
            
        `</aop:aspect>`

    `</aop:config>`
    
    `<!-- 省略各个Bean 的配置 -->`

    `<!-- ... -->`

</beans>

  • 配置切入点

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

   `xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`

   `xmlns:aop="http://www.springframework.org/schema/aop"`

   `xsi:schemaLocation="http://www.springframework.org/schema/beans`

       `http://www.springframework.org/schema/beans/spring-beans-3.0.xsd`

       `http://www.springframework.org/schema/aop`

   `http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">`

    `<aop:config>`

        `<!-- 定义一个切入点,myPointcut,直接知道它对应的切入点表达式 -->`

        `<aop:pointcut id="myPointcut" expression="execution(* com.wicresoft.app.service.impl.*.*(..))"`

method="release" />

        `<aop:aspect id="afterThrowingAdviceAspect" ref="afterThrowingAdviceBean" order="1">`

            `<!-- 使用上面定于切入点定义增强处理 -->`

            `<!-- 定义一个AfterThrowing 增强处理,指定切入点以切面 Bean 中的 doRecovertyActions() 方法作为增强处理方法 -->`

            `<aop:after-throwing pointcut-ref="myPointcut" method="doRecovertyActions" throwing="ex" />`

        `</aop:aspect>`

    `</aop:config>`
    
    `<!-- 省略各个Bean 的配置 -->`

    `<!-- ... -->`

</beans>

如果不能表达在应用系统的什么地方应用通知的话,通知将毫无用处,这就是切入点的用处。切入点决定了一个特点类的特定方法是否满足一定的规则。若符合,通知就应用到该方法上。

Spring根据需要植入通知的类和方法来定义切入点。通知是根据其特性植入目标类和方法。Spring的切入点框架的核心接口是Pointcut。

接口MethodMathcer三个方法,用在被代理对象生命周期的特定时期

  1. matches(Method,Class)方法根据目标和方法决定一个方法是否被通知,在AOP代理被创建时调用一次,其结果决定通知是否被植入
  2. 若matches(Method,Class)返回true,isRuntime()调用来决定MethodMatcher的类型:静态和动态(静态切入点通知总是被执行,如一个切入点是静态的,isRuntime()方法返回false;动态切入点根据运行时方法的参数值决定通知是否被执行,若一个切入点是动态的,isRuntime()方法返回true),在代理类创建时调用一次。
⚠️ **GitHub.com Fallback** ⚠️