AOP日志切面
AOP(Aspect-Oriented Programming)其实是OOP(Object-Oriented Programing) 思想的补充和完善。我们知道,OOP引进"抽象"、"封装"、"继承"、"多态"等概念,对万事万物进行抽象和封装,来建立一种对象的层次结构,它强调了一种完整事物的自上而下的关系。但是具体细粒度到每个事物内部的情况,OOP就显得无能为力了。比如日志功能。日志代码往往水平地散布在所有对象层次当中,却与它所散布到的对象的核心功能毫无关系。对于其他很多类似功能,如事务管理、权限控制等也是如此。这导致了大量代码的重复,而不利于各个模块的重用。 而AOP技术则恰恰相反,它利用一种称为"横切"的技术,能够剖解开封装的对象内部,并将那些影响了多个类并且与具体业务无关的公共行为 封装成一个独立的模块(称为切面)。更重要的是,它又能以巧夺天功的妙手将这些剖开的切面复原,不留痕迹的融入核心业务逻辑中。这样,对于日后横切功能的编辑和重用都能够带来极大的方便。 AOP技术的具体实现,无非也就是通过动态代理技术或者是在程序编译期间进行静态的"织入"方式。
AOP的基本概念
-
Advice(通知、切面): 某个连接点所采用的处理逻辑,也就是向连接点注入的代码, AOP在特定的切入点上执行的增强处理。
1.1 @Before: 标识一个前置增强方法,相当于BeforeAdvice的功能.
1.2 @After: final增强,不管是抛出异常或者正常退出都会执行.
1.3 @AfterReturning: 后置增强,似于AfterReturningAdvice, 方法正常退出时执行.
1.4 @AfterThrowing: 异常抛出增强,相当于ThrowsAdvice.
1.5 @Around: 环绕增强,相当于MethodInterceptor. -
JointPoint(连接点):程序运行中的某个阶段点,比如方法的调用、异常的抛出等。
-
Pointcut(切入点): JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式。
-
Advisor(增强): 是PointCut和Advice的综合体,完整描述了一个advice将会在pointcut所定义的位置被触发。
-
@Aspect(切面): 通常是一个类的注解,里面可以定义切入点和通知
-
AOP Proxy:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。
@PointCut
这个注解包含两部分,PointCut表达式和PointCut签名。表达式是拿来确定切入点的位置的,说白了就是通过一些规则来确定,哪些方法是要增强的,也就是要拦截哪些方法。
表示式(expression)和签名(signature)
//Pointcut表示式
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
//Point签名
private void log(){}
由下列方式来定义或者通过 &&、 ||、 !、 的方式进行组合:
- execution:用于匹配方法执行的连接点;
- within:用于匹配指定类型内的方法执行;
- this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
- target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
- args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
- @within:用于匹配所以持有指定注解类型内的方法;
- @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
- @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
- @annotation:用于匹配当前执行方法持有指定注解的方法;
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
- 其中后面跟着“?”的是可选项括号中各个pattern分别表示:
- 修饰符匹配(modifier-pattern?)
- 返回值匹配(ret-type-pattern): 可以为*表示任何返回值, 全路径的类名等
- 类路径匹配(declaring-type-pattern?)
- 方法名匹配(name-pattern):可以指定方法名 或者 代表所有, set 代表以set开头的所有方法
- 参数匹配((param-pattern)):可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用"" 来表示匹配任意类型的参数,"…"表示零个或多个任意参数。
如(String)表示匹配一个String参数的方法;(,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型。 - 异常类型匹配(throws-pattern?)
- 任意公共方法的执行:execution(public * *(…))
- 任何一个以“set”开始的方法的执行:execution(* set*(…))
- AccountService 接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(…))
- 定义在service包里的任意方法的执行: execution(* com.xyz.service..(…))
- 定义在service包和所有子包里的任意类的任意方法的执行:execution(* com.xyz.service….(…))
第一个表示匹配任意的方法返回值, …(两个点)表示零个或多个,第一个…表示service包及其子包,第二个表示所有类, 第三个*表示所有方法,第二个…表示方法的任意参数个数 - 定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:execution(* com.test.spring.aop.pointcutexp…JoinPointObjP2.*(…))")
- pointcutexp包里的任意类: within(com.test.spring.aop.pointcutexp.*)
- pointcutexp包和所有子包里的任意类:within(com.test.spring.aop.pointcutexp…*)
- 实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类:this(com.test.spring.aop.pointcutexp.Intf)
- 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型
- 带有@Transactional标注的所有类的任意方法:
@within(org.springframework.transaction.annotation.Transactional)
@target(org.springframework.transaction.annotation.Transactional) - 带有@Transactional标注的任意方法:@annotation(org.springframework.transaction.annotation.Transactional)
@within和@target针对类的注解,@annotation是针对方法的注解 - 参数带有@Transactional标注的方法:@args(org.springframework.transaction.annotation.Transactional)
- 参数为String类型(运行是决定)的方法: args(String)
实现
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
package com.yuantiaokj.demo.aop.aspect;
import com.alibaba.fastjson.JSON;
import com.yuantiaokj.common.base.SysRes;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* ************************************************************
* Copyright © 2021 cnzz Inc.All rights reserved. * **
* ************************************************************
*
* @program: demo
* @description:
* @author: cnzz
* @create: 2021-04-29 14:50
**/
@Aspect
@Component
@Slf4j
public class WebLogAspect {
//@Pointcut("execution(public * com.yuantiaokj.demo.aop.controller.*.*(..))")
//@Pointcut("@within(com.yuantiaokj.demo.aop.anotation.ControllerLog)")
//@Pointcut("@within(org.springframework.web.bind.annotation.RestController)||@within(org.springframework.stereotype.Controller)")
@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
public void controllerLog() {
}
@Pointcut("execution(public * com.yuantiaokj.showtable.dao.*.*(..))")
public void datacentreDaoLog() {
}
// /**
// * 在连接点之前的通知(返回通知和异常通知的的异常)
// **/
// @Before("controllerLog()")
// public void logBeforeController(JoinPoint joinPoint) {
// //这个RequestContextHolder是Springmvc提供来获得请求的东西
// RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
//
// String method = request.getMethod();
// // 记录下请求内容
// log.info("################URL : " + request.getRequestURL().toString());
// log.info("################HTTP_METHOD : " + request.getMethod());
// log.info("################IP : " + request.getRemoteAddr());
// log.info("################THE ARGS OF THE CONTROLLER : " + Arrays.toString(joinPoint.getArgs()));
//
// //下面这个getSignature().getDeclaringTypeName()是获取包+类名的 然后后面的joinPoint.getSignature.getName()获取了方法名
// log.info("################CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
// //logger.info("################TARGET: " + joinPoint.getTarget());//返回的是需要加强的目标类的对象
// //logger.info("################THIS: " + joinPoint.getThis());//返回的是经过加强后的代理类的对象
// log.info("{} 入参打印 {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
// }
//
// @After("controllerLog()")
// public void logAfterController(JoinPoint joinPoint) {
//
// log.info("{} 出参打印 {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
// }
//
// @AfterReturning(returning = "ret", pointcut = "controllerLog()")
// public void doAfterReturning(JoinPoint joinPoint, Object ret) {
// // 处理完请求,返回内容
// log.info("{} 出参打印 {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(), JSON.toJSONString(ret));
// }
@Around(value = "controllerLog()")
public Object recordLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime= System.currentTimeMillis();
String path = proceedingJoinPoint.getSignature().getDeclaringTypeName() + "." + proceedingJoinPoint.getSignature().getName();
log.info("{} 入参打印{}", path, Arrays.toString(proceedingJoinPoint.getArgs()));
//发生异常后,没有手动捕获,抛出后由全局异常捕获统
Object object = proceedingJoinPoint.proceed();
log.info("{} 耗时{}ms 出参打印{}", path,System.currentTimeMillis()-startTime, object);
return object;
}
}
package com.yuantiaokj.demo.aop.anotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ControllerLog注解
*
* @author cnzz
* @date 2021-04-29
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ControllerLog {
String value() default "";
}
package com.yuantiaokj.demo.aop.controller;
import com.yuantiaokj.common.base.SysRes;
import com.yuantiaokj.common.code.PubCode;
import com.yuantiaokj.common.exception.BizException;
import com.yuantiaokj.demo.aop.bo.AopLogTestBO;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* ************************************************************
* Copyright © 2021 cnzz Inc.All rights reserved. * **
* ************************************************************
*
* @program: demo
* @description: aop controler 日志测试
* @author: cnzz
* @create: 2021-04-29 15:36
**/
@RestController
//@Controller
@RequestMapping("/AopLogController")
@Slf4j
@Api
//@ControllerLog
public class AopLogController {
@PostMapping("aopLogTest")
public SysRes aopLogTest(@RequestBody AopLogTestBO input) {
return SysRes.success(input);
}
@PostMapping("aopLogError")
public SysRes aopLogError(@RequestBody AopLogTestBO input) {
throw new BizException(PubCode.API_99001_参数校验不通过);
}
}
效果图

参考链接:
https://www.cnblogs.com/wangshen31/p/9379197.html
https://blog.csdn.net/reee112/article/details/84937386
本文详细介绍了AOP(面向切面编程)在日志管理中的应用,通过Spring AOP实现切入点表达式、通知类型和切面的整合,展示了如何通过@Aspect、@Pointcut、@Before/After/Around等通知增强核心业务逻辑。
467

被折叠的 条评论
为什么被折叠?



