- 首先创建一个springboot项目
- 然后添加aop的依赖和lombok依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
- 编写LogInfo
package com.cxp.utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Angelou
* @date 2021/10/28
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogInfo {
String value() default "";
}
- 编写Log的切面
package com.cxp.utils;
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 java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* log的切面
*
* @author Angelou
* @date 2021/10/28
*/
@Aspect
@Component
@Slf4j
public class LogAspect {
/**
* 配置切入点,该方法无方法体,主要为了方便同类中其他方法使用此处配置的切入点
*/
@Pointcut("@annotation(com.cxp.utils.LogInfo)")
public void pointcut() {
}
/**
* aop前置
*
* @param joinPoint
*/
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
log.info("before通知:" + joinPoint);
}
/**
* aop后置
*
* @param joinPoint
*/
@After("pointcut()")
public void after(JoinPoint joinPoint) {
log.info("after通知:" + joinPoint);
}
/**
*
*/
@Around("pointcut()")
public Object around(JoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogInfo annotation = method.getAnnotation(LogInfo.class);
String value = annotation.value();
//拿注解括号的内容
log.info(value);
log.info("after通知" + joinPoint);
log.info("打印目标方法名" + joinPoint.getSignature().getName());
log.info("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
log.info("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
log.info("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
// log.info("获取目标传入目标方法的参数"+joinPoint.getArgs());
return ((ProceedingJoinPoint) joinPoint).proceed();
}
/**
* 方法return后
*
* @param joinPoint
*/
@AfterReturning("pointcut()")
public void afterReturn(JoinPoint joinPoint) {
log.info("afterReturn 通知" + joinPoint);
}
/**
* 方法抛出异常是调用
*
* @param joinPoint
* @param ex
*/
@AfterThrowing(pointcut = "pointcut()", throwing = "ex")
public void afterThrow(JoinPoint joinPoint, Exception ex) {
log.info("afterThrow 通知" + joinPoint + "\t" + ex.getMessage());
}
}
- 编写LogController
package com.cxp.controller;
import com.cxp.utils.LogInfo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Angelou
* @date 2021/10/28
*/
@RestController
@RequestMapping("/log")
public class LogController {
@LogInfo("进入了logDemo1")
@RequestMapping("/logDemo1")
public void logDemo1(){
System.out.println("logDemo1方法体内部");
}
}
- 测试
输入http://localhost:8080/log/logDemo1 - 控制台的输出
2021-10-28 18:06:55.347 INFO 4308 --- [nio-8080-exec-1] com.cxp.utils.LogAspect : 进入了logDemo1
2021-10-28 18:06:55.347 INFO 4308 --- [nio-8080-exec-1] com.cxp.utils.LogAspect : after通知execution(void com.cxp.controller.LogController.logDemo1())
2021-10-28 18:06:55.347 INFO 4308 --- [nio-8080-exec-1] com.cxp.utils.LogAspect : 打印目标方法名logDemo1
2021-10-28 18:06:55.347 INFO 4308 --- [nio-8080-exec-1] com.cxp.utils.LogAspect : 目标方法所属类的简单类名:LogController
2021-10-28 18:15:53.930 INFO 4308 --- [nio-8080-exec-1] com.cxp.utils.LogAspect : 目标方法所属类的类名:com.cxp.controller.LogController
2021-10-28 18:15:53.930 INFO 4308 --- [nio-8080-exec-1] com.cxp.utils.LogAspect : 目标方法声明类型:public
2021-10-28 18:15:53.931 INFO 4308 --- [nio-8080-exec-1] com.cxp.utils.LogAspect : before通知:execution(void com.cxp.controller.LogController.logDemo1())
logDemo1方法体内部
2021-10-28 18:15:53.935 INFO 4308 --- [nio-8080-exec-1] com.cxp.utils.LogAspect : afterReturn 通知execution(void com.cxp.controller.LogController.logDemo1())
2021-10-28 18:15:53.935 INFO 4308 --- [nio-8080-exec-1] com.cxp.utils.LogAspect : after通知:execution(void com.cxp.controller.LogController.logDemo1())
附录
AOP术语
AOP 领域中的特性术语:
- 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
- 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
- 切点(PointCut): 可以插入增强处理的连接点。
- 切面(Aspect): 切面是通知和切点的结合。
- 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
- 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
@interface注解详解
@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)——包
@Retention:注解的保留位置
RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
@Aspect注解详解
Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
Aspect:切面,即Pointcut和Advice。
Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。
@Pointcut注解详解
@Pointcut 注解指定一个切点,定义需要拦截的东西,这里介绍两个常用的表达式:一种是用execution() ,另一个是使用annotation()
execution()表达式:
以execution(* com.cxp.controller..*.*(..)))表达式为例:
第一个 * 号的位置:表示返回值类型,* 表示所有类型。
包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,在本例中指 com.mutest.controller包、子包下所有类的方法。
第二个 * 号的位置:表示类名,* 表示所有类。
(…):这个星号表示方法名, 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
annotation()表达式:
annotation() 方式是针对某个注解来定义切点,比如我们对具有 @PostMapping 注解的方法做切面,可以如下定义切面:
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void annotationPointcut() {}
然后使用该切面的话,就会切入注解是 @PostMapping 的所有方法。这种方式很适合处理 @GetMapping、@PostMapping、@DeleteMapping不同注解有各种特定处理逻辑的场景。
还有就是如上面案例所示,针对自定义注解来定义切面。
@Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
private void permissionCheck() {}