首先创建一个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() {}