1. 首先创建一个springboot项目
  2. 然后添加aop的依赖和lombok依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
</dependency>
  1. 编写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 "";
}
  1. 编写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());

    }
}
  1. 编写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方法体内部");
    }
}
  1. 测试
    输入http://localhost:8080/log/logDemo1
  2. 控制台的输出
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:说明子类可以继承父类中的该注解

java 自定义注解 默认实现接口 java自定义注解记录日志_后端

@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() {}