掘金 后端 ( ) • 2024-04-27 14:40

刘亦菲-5.jpeg

一、概述

1.1 通知类型

为了符合各种流程处理,通知类型提供了5种,可以对目标方法进行全方位处理,如下所示:

通知类型 说明 前置通知(Before advice) 在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。 后置通知(After returning advice) 在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。 异常通知(After throwing advice) 在方法抛出异常退出时执行的通知。 最终通知(After advice) 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 环绕通知(Around Advice) 包围一个连接点的通知,如方法调用。

Spring提供所有类型的通知,个人推荐使用尽可能简单的通知类型来实现需要的功能。例如,如果只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。
  我们把这些术语串联到一起,方便理解,如下图所示:

20240427000024.png

1.2 @AspectJ支持

@AspectJ使用了Java 5的注解,可以将切面声明为普通的Java类。Spring 使用了和AspectJ 5一样的注解,并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器。为了在Spring配置中使用@AspectJ切面,首先必须启用Spring对@AspectJ切面配置的支持。

@Aspect
@Component
public class WebLogAspect {
    
}

在Spring AOP中,拥有切面的类本身不可能是其它切面中通知的目标。一个类上面的 @Aspect 注解标识它为一个切面,并且从自动代理中排除它。

二、声明通知

通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式。和XML方式的通知不同的是,注解通知采取在切面类的通知方法上面直接加上相应注解即可。主要有:@Before、@After、@AfterReturning、@AfterThrowing、@Around等5个注解。

2.1 前置通知

一个切面里使用 @Before 注解声明前置通知,其在目标类的方法执行之前执行,比如用于拦截记录用户的操作。在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

@Aspect
@Component
public class WebLogAspect {
	@Before
	public void doBefore(JoinPoint joinPoint) {

	}
}

上面的前置通知方法中用的了JoinPoint参数,在通知方法中可以声明一个JoinPoint类型的参数。通过JoinPoint可以访问连接点的细节。它的常用方法包括:

参数 说明 java.lang.Object[] getArgs() 获取连接点方法运行时的入参列表 Signature getSignature() 获取连接点的方法签名对象 java.lang.Object getTarget() 获取连接点所在的目标对象 java.lang.Object getThis() 获取代理对象本身 java.lang.Object proceed() 通过反射执行目标对象的连接点处的方法 java.lang.Object proceed(java.lang.Object[] args) 通过反射执行目标对象连接点处的方法,不过使用新的参数替换原来的参数。

2.2 后置返回通知

后置返回通知,当目标方法执行成功后执行该方法体,使用 @AfterReturning 注解来声明。正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行。

@Aspect
@Component
public class WebLogAspect {
    @AfterReturning
	public void doAfterReturning(JoinPoint joinPoint, Object keys) {
	}
}

2.3 异常通知

当目标方法抛出异常返回后,执行该方法体,使用 @AfterThrowing 注解来声明。

@Aspect
@Component
public class WebLogAspect {
    @AfterThrowing
	public void doAfterThrowing(JoinPoint joinPoint, Throwable exception) {
        
    }
}

2.4 最终(后置)通知

当目标方法执行后执行该方法体,不论是正常返回还是异常退出。其使用@After 注解来声明,最终通知必须准备处理正常返回和异常返回两种情况,通常用它来释放资源。

@Aspect
@Component
public class WebLogAspect {
    @After
	public void doAfter(JoinPoint joinPoint) {
	}
}

2.5 环绕通知

环绕通知在一个方法执行之前和之后执行,它使得通知有机会在一个方法执行之前和执行之后运行。而且它可以决定标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。环绕通知使用@Around注解来声明。通知的第一个参数必须是 ProceedingJoinPoint类型。在通知体内,调用 ProceedingJoinPointproceed()方法会导致 后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个 Object[]对象-该数组中的值将被作为方法执行时的参数。

@Aspect
@Component
public class WebLogAspect {
    @Around
	public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
		Object result = joinPoint.proceed();
		return result;
	}
}

三、附录

3.1 基于注解的切面类

package org.dllwh.service;

public interface CustomerService {
	public void save(String name);
	public void update();
}

实现类要记得添加@Service,让该类作为Spring的IOC容器对象。

package org.dllwh.service.impl;

import org.springframework.stereotype.Service;
import org.dllwh.service.CustomerService;

@Service
public class CustomerServiceImpl implements CustomerService {

	@Override
	public void save(String name) {
		System.out.println("执行save方法,name为:"+name);
	}

	@Override
	public void update() {
		System.out.println("执行update方法");
	}
}

编写纯注解的切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {

	// 前置通知
	@Before(value = "execution(public * org.dllwh.service.impl.CustomerServiceImpl.*(..))")
	public void before() {
		System.out.println("前置通知");
	}

	// 最终通知
	@After(value = "execution(public * org.dllwh.service.impl.CustomerServiceImpl.*(..))")
	public void after(JoinPoint joinPoint) {
		System.out.println("最终通知");
	}

	// 后置通知
	@AfterReturning(value = "execution(public * org.dllwh.service.impl.CustomerServiceImpl.*(..))")
	public void afterReturning() {
		System.out.println("后置通知");
	}

	// 异常通知
	@AfterThrowing(value = "execution(public * org.dllwh.service.impl.CustomerServiceImpl.*(..))")
	public void afterThrowing() {
		System.out.println("异常通知");
	}

	// 环绕通知
	@Around(value = "execution(public * org.dllwh.service.impl.CustomerServiceImpl.*(..))")
	public void around(ProceedingJoinPoint pjp) {
		System.out.println("前置通知--前面代码");
		//执行目标对象方法
		try {
			pjp.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("前置通知--后面代码");
	}

}

编写纯注解的切面类

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configurable
@ComponentScan
@EnableAspectJAutoProxy  // 开启AOP注解功能
public class SpringConfig {

}

小结

把今天最好的表现当作明天最新的起点…...~

投身于天地这熔炉,一个人可以被毁灭,但绝不会被打败!一旦决定了心中所想,便绝无动摇。迈向光明之路,注定荆棘丛生,自己选择的路,即使再荒谬、再艰难,跪着也要走下去!放弃,曾令人想要逃离,但绝境重生方为宿命。若结果并非所愿,那就在尘埃落定前奋力一搏!

划重点.gif

相关内容