关于什么是AOP,AOP用来做什么,AOP里面的两种代理这里都不做讨论。本文只是在应用的层面去讲解AOP
一、关于AOP里面的一些概念
- Joinpoint(连接点): 类里面可以被增强的方法,这些方法称为连接点
- Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
- Advice(通知):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- Aspect(切面): 是切入点和通知(引介)的结合
- Target(目标对象):代理的目标对象(要增强的类)
- Weaving(织入): 是把增强应用到目标的过程,把advice 应用到 target的过程
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
- Before(前置通知):在目标方法之前执行
- After(后置通知):在目标方法之后执行(不管目标方法是否执行成功都会执行)
- AfterReturning(正常后置通知):目标方法正常执行结束后执行
- AfterThrowing(异常后置通知):目标方法异常后执行
- Around(环绕通知):目标方法执行前后都执行
二、Demo搭建
<!-- spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
2-1、切面定义
@Aspect
@Component
public class LogAspect {
}
2-2、Pointcut定义
也就是我们要对那些方法进行AOP处理
2-2-1、使用匹配方式
就是配置我们要把哪些包下面的方法进行AOP拦截了。
下面这个就是对com.koron.workorder包下面的servlet包下面的类全部进行拦截
具体的配置百度一下SpringAOP切点配置就行
private static final String POINT_CUT_INCLUDE = "execution(public * com.koron.workorder..servlet.*.*(..))";
@Pointcut(POINT_CUT_INCLUDE)
public void controllerLog() {
}
2-2-2、注解方式拦截
在方法上面使用了这个注解就会被AOP拦截
自定义注解
这个注解里面有三个参数,当然了这个是自定义的,你想要几个完全根据业务来定义即可
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogDefined {
String operate() default "";
String module() default "";
String desc() default "";
}
pointCut书写
@Pointcut("@annotation(com.xdx97.mianshiba.common.aop.log.LogDefined)")
public void controllerLog() {
}
2-3、advice通知
2-3-1、前置通知(Before)
在执行目标方法之前执行
// 前置通知
@Before("controllerLog()")
public void logBefore(JoinPoint joinPoint){
System.out.println("before");
}
2-3-2、后置通知(After)
在目标方法之后执行(不管目标方法是否执行成功都会执行)
@After("controllerLog()")
public void logAfter(JoinPoint point){
System.out.println("after");
}
2-3-3、正常后置通知(AfterReturning)
目标方法正常执行结束后执行,result是目标方法返回的结果
@AfterReturning(pointcut = "controllerLog()", returning = "result")
public void logAfterReturning(JoinPoint point,String result){
System.out.println("result = " + result);
System.out.println("logAfterReturning");
}
2-3-4、异常后置通知(AfterThrowing)
目标方法异常后执行
@AfterThrowing("controllerLog()")
public void logAfterThrowing(JoinPoint point){
System.out.println("logAfterThrowing");
}
2-3-5、环绕通知(Around)
目标方法执行前后都执行
@Around("controllerLog()")
public Object logAround(ProceedingJoinPoint point) throws Throwable {
System.out.println("目标方法执行前...");
Object proceed = point.proceed();
System.out.println("目标方法执行后...");
return proceed;
}
point.proceed();
这句代码,就是去执行目标方法, proceed 是目标方法的返回值。
2-3-6、其它
我们看到每一个通知方法里面都有JoinPoint参数,我们可以从里面获取数据,比如获取请求的参数
Object[] args = joinPoint.getArgs();
for (Object item : args){
System.out.println(item);
}
2-3-7、测试
TestController
import com.xdx97.mianshiba.common.aop.log.LogDefined;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test")
@LogDefined(operate = "测试",module = "测试模块",desc="简单描述")
public String fun(@RequestParam String str){
System.out.println("active" + str);
return "success";
}
}
执行结果
http://127.0.0.1:8888/test?str=31231
目标方法执行前...
31231
before
active31231
目标方法执行后...
after
result = success
logAfterReturning
完整的切面
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAspect {
@Pointcut("@annotation(com.xdx97.mianshiba.common.aop.log.LogDefined)")
public void controllerLog() {
}
// 前置通知
@Before("controllerLog()")
public void logBefore(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
for (Object item : args){
System.out.println(item);
}
System.out.println("before");
}
// 后置通知 不管异常不异常都会执行
@After("controllerLog()")
public void logAfter(JoinPoint point){
System.out.println("after");
}
// 返回通知--当代码运行正常后执行
@AfterReturning(pointcut = "controllerLog()", returning = "result")
public void logAfterReturning(JoinPoint point,String result){
System.out.println("result = " + result);
System.out.println("logAfterReturning");
}
// 异常通知 -- 发送异常后执行
@AfterThrowing("controllerLog()")
public void logAfterThrowing(JoinPoint point){
System.out.println("logAfterThrowing");
}
// 环绕通知
@Around("controllerLog()")
public Object logAround(ProceedingJoinPoint point) throws Throwable {
System.out.println("目标方法执行前...");
Object proceed = point.proceed();
System.out.println("目标方法执行后...");
return proceed;
}
}
三、实现log
import com.xdx97.mianshiba.bean.pojo.common.BUser;
import com.xdx97.mianshiba.common.utils.RedisUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
public class LogAspect {
@Pointcut("@annotation(com.xdx97.mianshiba.common.aop.log.LogDefined)")
public void controllerLog() {
}
// 返回通知--当代码运行正常后执行
@AfterReturning(pointcut = "controllerLog()", returning = "result")
public void logAfterReturning(JoinPoint point,String result){
// 1、从request里面获取token
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
String token = request.getHeader("token");
// 2、使用token从Redis里面获取用户信息
BUser user = (BUser)RedisUtils.get(token);
Map<String,Object> map = new HashMap<>();
map.put("userId",user.getId());
map.put("userName", user.getUserName());
// 3、获取拦截的参数
MethodSignature signature = (MethodSignature) point.getSignature();//从切面织入点处通过反射机制获取织入点处的方法
Method logMethod = signature.getMethod();//获取切入点所在的方法
LogDefined logDefined = logMethod.getAnnotation(LogDefined.class); //获取操作
if (logDefined != null) { //记录操作信息
try {
map.put("operate",logDefined.operate());
map.put("module",logDefined.module());
map.put("desc",logDefined.desc());
} catch (Exception e) {
throw new RuntimeException("操作日志字段名填写有误");
}
}
// 4、把数据插入数据库 map
}
}
四、其它
4-1、AOP执行顺序
我们可以在切面上加入 @Order(1) 注解,里面的数值越小,执行顺序越前。