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
* @email [email protected]
* @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
* @email [email protected]
* @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环绕通知。至于AOP的实现原理动态代理,这个后面再找时间看吧。
标签:joinPoint,入门,Spring,Aop,切点,注解,import,执行,方法 From: https://www.cnblogs.com/mcj123/p/16819370.html