21. AOP实现自定义注解记录日志(实战篇1)
一、前言
环境说明:Windows10 + Idea2021.3.2 + Jdk1.8 + SpringBoot 2.3.1.RELEASE
在一期中,我们是重点介绍了AOP的概念及一些相关知识点,如果还有小伙伴是不太清楚的,那么不要急,通过这一期的教学内容,你就会对它有一些深入的认知了,毕竟都是纯概念的东西,让未接触或者接触过一点点的小伙伴完全吸收,肯定是很困难很困难的事情。
这一期我们将迎来实战练习,手把手带着大家怎么在项目中集成AOP并且简单使用它,后一期我将真正带着大家 如何使用AOP实现自定义注解的方式记录接口日志 业务。好吧?咱们一下不能就注解上手业务相关的代码练习,这样容易噎着。
言归正传,我这就开始动手进行实战教学吧!大家请看下面~
二、AOP使用
1、自定义注解类实现:
package com.example.demo.annotation;
import com.example.demo.enums.LogTypeEnum;
import java.lang.annotation.*;
/**
* 自定义注解类 @UseAop
*
* @Author luoYong
* @version 1.0
* @Date 2022-01-20 17:29
*/
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD表示注解作用范围在方法上;具体使用可以对照下边拓展
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface UseAop {
}
拓展一下:
@Target注解的作用目标有以下几种:(即描述的注解的使用范围)
- @Target(ElementType.TYPE) //用于接口、类、枚举、注解
- @Target(ElementType.FIELD) //字段、枚举的常量
- @Target(ElementType.METHOD) //方法
- @Target(ElementType.PARAMETER) //方法的参数
- @Target(ElementType.CONSTRUCTOR) //构造函数
- @Target(ElementType.LOCAL_VARIABLE) //局部变量
- @Target(ElementType.ANNOTATION_TYPE) //注解
- @Target(ElementType.PACKAGE) //包
自定义注解已经定义好了,接下来我们还要再定义一个切面类。
2、切面类实现:
package com.example.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 作用是把当前类标识为一个切面供容器读取
*
* @author luoYong
* @version 1.0
* @date 2022/1/24 15:32
*/
@Aspect
@Component
public class UseAopAspect {
/**
* 定义切点 @Pointcut;在注解的位置切入代码
*
* @annotation 表示标注了某个注解的所有方法
* 值填你自定义注解的项目目录
*/
@Pointcut("@annotation( com.example.demo.annotation.UseAop)")
public void printWord() {
}
/**
* 标识一个前置增强方法,相当于BeforeAdvice的功能
*/
@Before("printWord()")
public void beforeAdvice() {
System.out.println("---执行3:@Before---");
}
/**
* final增强,不管是抛出异常或者正常退出都会执行
*/
@After("printWord()")
public void afterAdvice() {
System.out.println("---执行4:@After---");
}
/**
* 在目标方法成功执行之后调用通知
*/
@AfterReturning("printWord()")
public void afterReturningAdvice() {
System.out.println("---执行5:@AfterReturning---");
}
/**
* 在目标方法抛出异常后调用通知。
*/
@AfterThrowing("printWord()")
public void afterAdviceThrowingAdvice() {
System.out.println("---执行6:@AfterThrowing---");
}
/**
* 环绕增强,相当于MethodInterceptor
*/
@Around("printWord()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("---执行1:@Around---");
try {
Object result = proceedingJoinPoint.proceed();
//注意,如果不把结果return,则接口无返回值!
return result;
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println("---执行2:@Around:异常报错---");
return new Object();
}
}
以上切面就创建好了,那接下来就到了重要环节了,我们先随便定义一个接口方法,然后用我们刚定义好的注解进行修饰,然后看看控制台到底会发生什么事情?
3、添加一个控制器
添加一个控制器,进行测试访问。这里咱们就以UserController为例吧。这个你们就随意创建一个,或者直接写个空方法或者返回个字符串都行。
比如我就是直接拿现成的查询用户列表接口来做个测试啊。请看如下:
package com.example.demo.controller;
import com.example.demo.annotation.UseAop;
import com.example.demo.entity.UserEntity;
import com.example.demo.vo.UserInfoVo;
import com.example.demo.dao.UserMapper;
import com.example.demo.model.QueryUserInfoModel;
import com.example.demo.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 用户管理分发器
*/
@RestController@RequestMapping("/user")
@Api(tags = "用户管理模块",description = "用户管理模块")
public class UserController {
@Autowired
private UserService userService;
/**
* 不分页查询db1所有用户信息
*/
@UseAop //切入
@GetMapping("/get-users1")
@ApiOperation(value = "不分页查询db1所有用户信息",notes = "不分页查询db1所有用户信息")
public List<UserEntity> getUserList() {
return userService.getUsers();
}
}
你们可以看到,我直接在我定义的getUserList()
方法上加了我们刚才自定义的注解@UseAop
,对吧。你们也记得把它,然后在加上自定义注解与不加上自定义注解,二者进行一下对比,看看是否会成功执行到切面的方法进行输出打印?让我们期待一下
- 如下是未加上自定义注解时控制台打印的截图:
- 如下是已加上自定义注解时控制台打印的截图:
如上结果,你们看到了什么,切面类中的通知(advice)已经都被执行且输出打印,要注意的是,假如全部都通知,可以看到是有先后通知顺序的,但一般这五种通知类型不会同时都放在一起使用的,常见的玩法在项目中就是使用@AfterReturning
的 比较多,比如记录接口日志的具体调用情况(比如:获取调用人账户、调用时接口参数、调用者ip、调用时间、调用返回数据、调用操作类型等),就是在目标方法成功执行后调用通知进行获取,这操作一般比较多。还有就是权限校验,那就是在调用目标方法前进行切入,然后获取调用者账户信息再进行权限校验判断该用户是否有权限进行接口访问等。