Spring Aop的学习(一):Spring Aop的简单入门
1. 什么是AOP
AOP(Aspect Oriented Programming):面向切面编程,是OOP(面向对象编程)的一个延续,其和OOP一样,也是一种编程思想。不过AOP是一种横向开发模式。
2. AOP的作用及应用场景
- 作用
AOP的主要作用就是减少代码量,提高代码的可重用性,有利于未来的可操作性与可维护性。
主要操作就是将所有模块中共同拥有的代码,单独抽取出来,放在一块地方,在主代码运行之前或之后,或主程序运行的其他时间点执行这块代码。也可以理解成把这些单独抽出来的代码封装成一个个单独的方法,但是这些方法的执行不需要我们在程序中进行显示的调用,而是通过动态代理(jdk动态代理和cglib动态代理)的方式来帮助我们执行这些方法。 - 应用场景
AOP的主要应用场景是一些相似性代码比较高的场景,比如:权限认证,日志,事务等。
3. AOP的术语
1.连接点(Joinepoint):是程序执行的某个特定位置,如类开始初始化前、类初始化后、类的某个方法调用前/后、方法抛出异常后。一个类或者一段程序代码拥有一些具有边界性质的特定点,这些特定点就称为 “连接点” 。这边有个注意的点就是spring的链接点只支持方法,也就是只能是方法在调用前,调用后,抛出异常后。
2.切点(Pointcut):切点就是能够定位到特定连接点的点。也就是能够通过切点知道程序在哪个连接点之前或之后执行。
3.增强(Advice):增强说白了就是你想要在切点上执行的代码逻辑,也就是在连接点之前或之后执行的那段代码。
4.目标对象(Target):就是你要插入代码的类,也就是连接点所在的类。
5.引介(Introduction):是一种特殊的增强,为类添加一些属性和方法。
6.织入(Weaving):是一个过程,就是将增强添加到目标类具体连接点上的过程。
7.代理(Proxy):就是融合了目标类和增强逻辑后生成的那个对象
8.切面(Aspect):由切点和增强组成,包含了切点的定义与增强的代码逻辑的定义
4. 如何使用AOP
使用AOP的方式主要有两种,一种是基于xml文件进行开发,另一种是基于注解进行开发,这边主要是讲的基于注解的进行开发的方式。
4.1. 引入依赖
<!--AOP的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
4.2. 注解介绍
@Aspect:声明一个切面,标注在类,接口或枚举上。
@Pointcut:声明切入点,即切入到哪些目标类的目标方法,标注在方法上。
value的值是指定切入点表达式,常用的主要有两种表达式,一种是execution(),另一种是annotation():
- execution()表达式
语法:execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))
参数部分允许使用通配符:*
匹配任意字符,但只能匹配一个元素。..
匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用。+
必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类 - annotation()表达式
此方式是针对某个注解来定义切面,一般对于自定义注解常使用annotation()表达式,比如我们对具有 @PostMapping 注解的方法做切面,可以如下定义切面:@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
@Before:前置通知,在切入点执行之前执行,也就是在执行切入点之前执行这个方法中的代码,但是如果这个方法中抛出了异常那么就不会再执行切入点的方法。和@Pointcut一样作用在方法上。
其中value的值可以是指定切入点表达式,也可以是由@Pointcut注解标注的方法,如果是指定切入点表达式的话这边是和@Pointcut注解一样的。
@After:后置通知,在执行切入点方法之后执行,也就是执行了切入点的方法之后,会再执行@After注解标注的方法。其同样是作用在方法上的注解:
其中value的值可以是指定切入点表达式,也可以是由@Pointcut注解标注的方法,如果是指定切入点表达式的话这边是和@Pointcut注解一样的。这个的value与@Before注解的value值一样。
@AfterReturning:返回通知,在切入点返回结果之后执行,这个结果是在@After注解之后执行的,其同样是作用在方法上:
其中value的属性与@Before和@After注解的value一样。pointcut属性则是绑定同通知的契入点表达式,需要注意的是它的优先级高于value,也就是pointcut与value都填写的话会优先以pointcut的为准。
@AfterThrowing:异常通知,这个则是在抛出异常之后进行执行,另外,需要注意的是如果目标方法自己try-catch了异常,而没有继续往外抛出,则不会进入此回调函数。其同样是作用在方法上:
他的属性值和@AfterReturning的属性值基本上一样。同样是pointcut的优先级高于value的
@Around:环绕通知,这个则是在切入点执行之前或执行之后都分别执行一些代码,还可以控制目标方法的执行。它是早于前置通知,晚于返回通知。其同样是作用在方法上的注解:
其中value的属性与@Before注解和@After注解的value属性一样。需要注意的是环绕通知标注的方法有一个必须的参数ProceedingJoinPoint,这个参数主要是用来执行目标方法,以及获取目标方法的一些信息。
4.3. 定义切点、切面以及通知
package com.mcj.music.aspect; import com.mcj.music.utils.AspectUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Map; /** * @author mcj * @date 2022/10/23 9:54 * @description 日志的切面类,为controller类中的每个方法添加日志 */ @Aspect // 定义切面 @Component @Slf4j public class LogAspect { /** * execution函数用于匹配方法执行的连接点,语法为: * execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选)) * 参数部分允许使用通配符: * * 匹配任意字符,但只能匹配一个元素 * .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用 * + 必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类 * 此处表示返回类型、参数不限,只要是com.mcj.music.controller包中的任何类中的任何方法 */ @Pointcut("execution(* com.mcj.music.controller.*.*(..))") // 定义切点 public void logPointcut(){} /** * 环绕通知,在执行每个controller类中的方法之后打印相关的日志 * @param joinPoint * @return * @throws Throwable */ @Around("logPointcut()") // 进行环绕通知 public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { String paramNameAndValue = AspectUtils.getParamNameAndValue(joinPoint); String methodName = AspectUtils.getMethod(joinPoint); Object proceed = joinPoint.proceed(); log.info("当前请求方法:{},当前请求参数名与参数:{},当前请求结果:{}", methodName, paramNameAndValue, proceed); return proceed; } @Before("logPointcut()") public void doBefore() { System.out.println("这是前置通知"); System.out.println("--------------------------------------------"); } @After("logPointcut()") public void doAfter() { System.out.println("这是后置通知"); System.out.println("--------------------------------------------"); } @AfterReturning("logPointcut()") public void doAfterReturning() { System.out.println("这是返回通知"); System.out.println("--------------------------------------------"); } @AfterThrowing("logPointcut()") public void doAfterThrowing() { System.out.println("这是异常通知"); System.out.println("--------------------------------------------"); } }
AspectUtils工具类
package com.mcj.music.utils; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.CodeSignature; import java.util.HashMap; import java.util.Map; /** * @author mcj * @date 2022/10/23 10:15 * @description */ @Slf4j public class AspectUtils { /** * 获取当前切点的参数名称与值 * @param joinPoint * @return */ public static String getParamNameAndValue(ProceedingJoinPoint joinPoint) { Map<String, Object> paramNameAndValueMap = new HashMap<>(8); Object[] args = joinPoint.getArgs(); String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames(); for (int i = 0; i < parameterNames.length; i++) { paramNameAndValueMap.put(parameterNames[i], args[i]); } return JSONObject.toJSONString(paramNameAndValueMap); } /** * 获取当前切点的全限定方法名 * @param joinPoint * @return */ public static String getMethod(ProceedingJoinPoint joinPoint) { // 获取当前切点的全限定类型 String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName(); // 获取当前切点的方法名 String name = joinPoint.getSignature().getName(); return declaringTypeName + "." + name; } }
执行结果:
由于方法在执行的过程中出现异常,根本没有返回值,所以也不会有返回通知了。
5. 总结
spring AOP的简单入门使用并不是很难,基本上最主要的就是切面与切点的定义,至于它的几种通知类型,最主要的就是@Around环绕通知
转: https://www.cnblogs.com/mcj123/p/16819370.html