AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)
个人理解:Aop是在业务流程中增加新的通用流程时,如添加日志,统计方法运行时间等时,做一个拦截,在方法执行前后,都可以做额外的动作,可以做一些无关业务流程的工作如添加日志,也可以对业务流程进行干涉,如对参数进行修改,返回值进行修改等。类似python的装饰器的作用。
1、定义一个切点Pointcut,切点主要是用来定义在哪里切入,比如对所有的get请求进行请求前添加日志,那么就需要针对所有@GetMapping注解的方法进行拦截
2.定义一个针对切点的处理方法Advice,这个主要来定义什么时候进行处理,方法执行前,方法执行后(专业术语:前置和后置处理)
org.springframework.boot
spring-boot-starter-aop
使用@Aspect进行注解,来标记是一个切面类
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
可以使用上边注解直接加切入点表达式的方式,也可以使用下边方式进行切入点表达式重用
使用@Pointcut进行方法注解,注解参数表明是对什么方法或类等进行拦截
该注解有两种类型的参数:一个是使用 execution()
,另一个是使用 annotation()
execution表达式可以详细参考
execution表达式详解
execution (* com.sample.service.impl..*.*(..))
annotation()
方式是针对某个注解来定义切点
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
有5个注解
1.@Before 标注的方法在切面切入目标方法之前执行
2.@After 标注的方法在切面切入目标方法之后执行
3.@Arround 可以自由选择增强动作与目标方法的执行顺序,也就是说可以在增强动作前后,甚至过程中执行目标方法,因为被@Arround标注的方法第一个形参必须是ProceedingJoinPoint
类型,调用ProceedingJoinPoint
参数的procedd()
方法才会执行目标方法,这个方法的调用时机自己可以进行控制,没有调用ProceedingJoinPoint
的proceed
方法,则目标方法不会执行。
@Around
可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值
调用ProceedingJoinPoint
的proceed
方法时,还可以传入一个Object[ ]
对象,该数组中的值将被传入目标方法作为实参,所以可以改变执行目标方法的参数。传入的Object[ ]
数组长度与目标方法所需要的参数必须相等。
4、@AfterReturning
注解和 @After
有些类似,区别在于 @AfterReturning
注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理
5、当被切方法执行过程中抛出异常时,会进入 @AfterThrowing
注解的方法中执行,在该方法中可以做一些异常的处理逻辑
6、多个
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
@After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:"+methodName);
}
@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}
@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
Object result = null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标对象(连接点)方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
} finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
}
return result;
}
}
package com.ljx.splearn.AopDemo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @author lijianxi
* @date 2022年11月09日 10:58 上午
*/
@Order(0)
//标注该类是切面类
@Aspect
//交给spring容器
@Component
public class LogAdvice {
//@Pointcut 注解定义什么时机进行拦截,要拦截的是什么东西,一个是使用 execution(),另一个是使用 annotation()。
//annotation() 方式是针对某个注解来定义切点,execution指明哪些类或包或方法被执行的表达式
//表明什么时机进行切入,本例中是被getmapping注解到的方法被调用时(参数是一个注解,表示被该注解标注的方法调用时进行切入)
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
private void pointCut(){}
public long t1 ;
public long t2;
//指定的方法在切面切入目标方法之前执行,注入后做什么动作,参数是切点方法
@Before("pointCut()")
public void logAdvice(JoinPoint joinPoint){
// 获取签名
Signature signature = joinPoint.getSignature();
// 获取切入的包名
String declaringTypeName = signature.getDeclaringTypeName();
// 获取即将执行的方法名
String funcName = signature.getName();
System.out.println("执行前开始记录");
t1 = System.currentTimeMillis();
}
//指定的方法在切面切入目标方法之后执行
@After("pointCut()")
public void logAdvice1(JoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
String name =(String) args[0];
System.out.println("结束记录1"+name);
t2=System.currentTimeMillis();
System.out.println("执行时间"+(t2-t1));
}
//自由选择增强动作与目标方法的执行顺序
@Around("pointCut()")
//方法参数必须是ProceedingJoinPoint,而不能是JoinPoint
public Object logAdvice2(ProceedingJoinPoint joinPoint) throws Throwable {
//获取请求参数
Object[] args = joinPoint.getArgs();
String name =(String) args[0];
System.out.println("结束记录2"+name);
t2=System.currentTimeMillis();
System.out.println("执行时间1"+(t2-t1));
//修改参数
args[0]="王五";
joinPoint.proceed(args);
//修改返回值
return "hello ,zhangsan";
}
/**
* 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强
* @param joinPoint joinPoint
* @param result result
* 属性 returning 的值必须要和参数保持一致
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
Signature signature = joinPoint.getSignature();
String classMethod = signature.getName();
System.out.println((String.format("方法%s执行完毕",classMethod)));
System.out.println(result);
// 实际项目中可以根据业务做具体的返回值增强
System.out.println("对返回参数进行业务上的增强:{}"+result + "增强版");
}
/**
* 在上面定义的切面方法执行抛异常时,执行该方法
* @param joinPoint jointPoint
* @param ex ex
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
Signature signature = joinPoint.getSignature();
String method = signature.getName();
// 处理异常的逻辑
System.out.println((String)(String.format("执行方法{}出错,异常为:{}", method, ex)));
}
}
package com.ljx.splearn.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.sql.Time;
/**
* @author lijianxi
* @date 2022年11月09日 10:55 上午
*/
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/hello")
String sayHello(String name) throws InterruptedException {
System.out.println("传递过来参数是"+name);
Thread.sleep(2000);
return "hello"+name;
}
}
发起请求,参数是lisi,中间对参数修改传递到controller层时是王五,最终结果被@Around修改
最终返回是zhangsan
日志打印