外卖项目–Springboot回顾Day3

公共字段自动填充

因为creat_time,create_user,update_time,update_user在每个mapper层中都有,造成了代码冗余并且不利于维护,所以本节使用切面将他们统一处理

思路:

  1. 自定义注解AutoFill,用于标识需要公共字段自动填充的方法
  2. 自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
  3. 在Mapper的方法上加入AutoFill注解

技术点:注解,AOP,反射

代码实现:happy:

AutoFill.java

1
2
3
4
5
6
7
8
9
10
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();

}
  • @Target :指定当前代码会加在什么位置

  • @Retention :

    @Retention注解在Java中扮演着非常重要的角色,它用于指定自定义注解的保留策略,即定义注解在Java程序中的生命周期。具体来说,@Retention注解决定了注解信息被保留到什么阶段:是仅保留在源代码中,还是被编译到class文件中,或者是在运行时仍然保留并可被JVM访问。

    @Retention注解有一个参数RetentionPolicy,它是一个枚举类型,包含以下三个值:

    1. **RetentionPolicy.SOURCE**:

      • 这是最短的保留策略。注解仅保留在源代码中,不会被编译进class文件,也不会对运行时产生任何影响。
      • 这种策略通常用于那些只需要在编译时存在,而在运行时不需要的注解,比如用于生成文档或进行编译时检查的注解。
    2. **RetentionPolicy.CLASS**:

      • 注解会被编译进class文件中,但在运行时不会被JVM保留,因此不能通过反射机制在运行时访问这些注解。
      • 这是默认的保留策略,如果自定义注解没有显式指定@Retention注解,那么就会采用这种策略。
      • 这种策略适用于那些需要在编译时或类加载时通过其他机制(非反射)访问的注解。
    3. **RetentionPolicy.RUNTIME**:

      • 注解会被编译进class文件中,并在运行时被JVM保留,因此可以通过反射机制在运行时访问这些注解。
      • 这种策略通常用于那些需要在运行时通过反射读取注解信息的场景,比如实现自定义的注解处理器或框架。

    使用@Retention注解可以帮助开发者更好地控制注解的生命周期,从而在不同的阶段(编译时、类加载时、运行时)根据需要来访问和处理注解信息。这对于开发框架、库或需要高度灵活性和动态性的应用程序来说尤为重要。

AutoFillAspect.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点:对哪些类的哪些方法进行切入
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}

/**
* 前置通知,在通知中进行公共字段赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");

//获取到当前被拦截的数据库操作类型
MethodSignature signature = (MethodSignature)joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型

//获取到当前被拦截的方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if(args==null || args.length == 0){
return;
}

Object entity = args[0];

//准备复制数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();

//根据当前不同的操作类型,为外卖对应的属性栏进行赋值
if(operationType == OperationType.INSERT){
//为4个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER,Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);

} catch (Exception e) {
e.printStackTrace();
}

}else if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);

} catch (Exception e) {
e.printStackTrace();
}
}
}
}
  • @Pointcut:对哪些包的哪些方法进行拦截

这个注释是一个AspectJ的@Pointcut注解,用于在面向切面编程(AOP)中定义一个切入点(Pointcut)。切入点是AOP中的一个核心概念,它定义了哪些类的哪些方法将被拦截(或称为“被增强”)以及何时被拦截。具体来说,这个@Pointcut注解定义了一个特定的切入点表达式,让我们逐步解析这个表达式:

1
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")

组成部分

  1. execution( com.sky.mapper.

    .*(..))**:

    • 这是切入点表达式的主要部分,使用了execution模式匹配器。

    • * com.sky.mapper.*.*(..)
      

      指定了匹配的方法签名。

      • 第一个*表示匹配任何返回类型。
      • com.sky.mapper指定了包名,表示匹配该包及其子包下的类。
      • 第二个*表示匹配com.sky.mapper包下的任何类。
      • 第三个*表示匹配任何方法名。
      • (..)表示匹配任何数量的参数(包括零个参数)。
    • 总的来说,这部分表达式匹配com.sky.mapper包及其子包下所有类的所有方法。

  2. && @annotation(com.sky.annotation.AutoFill)

    :

    • 这部分使用了逻辑与(&&)操作符来进一步限制匹配的方法。
    • @annotation(com.sky.annotation.AutoFill)指定了另一个条件,即被匹配的方法上必须带有@AutoFill注解,该注解位于com.sky.annotation包下。
    • 因此,只有当方法既位于com.sky.mapper包或其子包下的类中,又带有@AutoFill注解时,它才会被这个切入点表达式匹配。
  • @Before(“autoFillPointCut()”)

    用于定义一个前置通知(Before Advice),该通知会在目标方法执行之前运行。当你看到@Before("autoFillPointCut()")这样的代码时,它意味着定义了一个前置通知,该通知将会在任何被autoFillPointCut()切入点表达式匹配的方法执行之前运行。

为什么使用到反射?

因为自动复制填充的表不是都是员工表,所以不可以向下转型成员工表,然后调用set方法直接修改,只能用反射动态调用set进行需改。反射允许程序在运行时动态地访问和操作类的属性和方法。通过使用反射,你可以不知道具体类名的情况下,调用任何对象的任何方法,只要这个方法存在且可访问。