在项目中很多小伙伴会用到请求接口参数打印等功能需求,那么如果在每个接口上面加日志逻辑这样就等于说是重复造轮子了,在这里给大家分享一个技巧:aop切面编程;
再说这个示例之前先说一下这里面的一些概念性知识;
参考 https://www.jianshu.com/p/570c5283b1fc
要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 Advice 的目标对象.。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。
然后举一个容易理解的例子:
看完了上面的理论部分知识, 我相信还是会有不少朋友感觉到 AOP 的概念还是很模糊, 对 AOP 中的各种概念理解的还不是很透彻. 其实这很正常, 因为 AOP 中的概念是在是太多了。
下面我以一个简单的例子来比喻一下 AOP 中 Aspect, Joint point, Pointcut 与 Advice之间的关系.
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.
来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系。
首先我们知道, 在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问。
为什么可以这样类比呢?
通过上面的例子理解,我相信大家应该对aop有个初步的认识。
org.aspectj
aspectjrt
1.8.6
org.aspectj
aspectjweaver
1.8.6
/**
* 定义日志切面
*
* @Author: Wangzhen
* @Date: 2019-11-16 16:11
* @Description:
* @Lazy 注解:容器一般都会在启动的时候实例化所有单实例 bean,如果我们想要 Spring 在启动的时候延迟加载 bean,需要用到这个注解
* value为true、false 默认为true,即延迟加载,@Lazy(false)表示对象会在初始化的时候创建
*/
@Log4j2
@Aspect // point cut 与 Advice 的组合,凡是标记@RequestMapping的方法要进入下一步逻辑操作。这个整一套动作称之为Aspect
@Component
@Lazy(value = false)
public class LoggerAspect {
/**
* 凡是带@RequestMapping注解的都把他作为切入点,如上文中的 “男性, 身高约七尺五寸”,都需要进行下一步审问
*
*/
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
private void requestMappingPointCut() {
}
/**
* 前置通知:在目标方法执行前调
* Advice 类似上文中的符合老王描述的都要抓过来审问动作,
*/
@Before("requestMappingPointCut()")
public void before(JoinPoint joinPoint) throws NoSuchMethodException {
log.debug("正在获取方法参数......");
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
// 反射获取目标类
Class> targetClass = joinPoint.getTarget().getClass();
// 拿到方法对应的参数类型
Class>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 根据类、方法、参数类型(防止重载)获取到方法的对象
Method objMethod = targetClass.getMethod(methodName, parameterTypes);
Annotation[][] parameterAnnotations = objMethod.getParameterAnnotations();
int i = 0;
for (Annotation[] annotations : parameterAnnotations) {
for1:
for (Annotation annotation : annotations) {
if (annotation instanceof RequestParam) {
log.debug("参数:{},值:{}", ((RequestParam) annotation).value(), args[i]);
i++;
break for1;
} else if (annotation instanceof PathVariable) {
log.debug("参数:{},值:{}", ((PathVariable) annotation).value(), args[i]);
i++;
break for1;
} else if (annotation instanceof RequestBody) {
log.debug("请求体参数:{}", args[i]);
i++;
break for1;
}
}
}
}
}
这样就实现了aop切面编程日志打印,以上只是aop的核心冰山一角,还可以实现很多业务功能比如:事务控制、动态数据源切换、用户token校验、对部分方法的调用进行日志记录等等还有很多。