当前位置: 首页 > news >正文

JavaWeb笔记整理14——公共字段自动填充技术实现

目录


为什么需要公共字段自动填充?

步骤1 自定义注解AutoFill

步骤2 自定义切面AutoFillAspect

步骤3 在Mapper接口的方法上加入AutoFill注解

@Before("autoFillPointCut()")

JoinPoint

你能通过 JoinPoint 获取哪些信息?

例子中的 JoinPoint

获取方法签名和注解

获取被拦截方法的参数

反射

什么是反射

获取 Class 对象

获取 Method 对象

动态调用方法——invoke()


为什么需要公共字段自动填充?

  1. 避免手动重复操作:每次插入、更新数据库时,都要手动设置 createTimeupdateTimecreateUserupdateUser 等字段,容易出现遗漏或不一致的问题。自动填充可以保证这些公共字段在插入或更新时自动赋值,无需手动干预。

  2. 提高代码的可维护性:如果在每个数据插入和更新的地方都写上手动的字段赋值代码,当需求变化时,需要逐一修改所有相关的代码。这不仅增加了维护成本,还增加了出错的几率。通过自动填充,这些字段可以集中管理,方便维护。

  3. 保证数据一致性:自动填充可以保证所有数据记录的时间戳和用户信息是一致的,并且可以通过统一的逻辑进行约束,减少人为错误带来的数据不一致问题。

步骤1 自定义注解AutoFill

/*** 数据库操作类型*/
public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT}
/*** 自定义注解 用于表示某个方法需要进行功能字段自动填充处理*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {//数据库操作类型 update insertOperationType value();}

步骤2 自定义切面AutoFillAspect

/*** 自定义切面 实现公共字段自动填充处理逻辑*/
@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){//为四个公共字段赋值try {Method setCreatTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCreatUser = 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);//通过反射为对象属性赋值setCreatTime.invoke(entity,now);setCreatUser.invoke(entity,currentId);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {throw new RuntimeException(e);}}else if(operationType == OperationType.UPDATE){//为2个公共字段赋值try {Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);//通过反射为对象属性赋值setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {throw new RuntimeException(e);}}}

步骤3 在Mapper接口的方法上加入AutoFill注解

 /*** 根据主键来动态修改属性* @param employee*/@AutoFill(value = OperationType.UPDATE)void update(Employee employee);/*** 插入员工数据* @param employee*/@Insert("insert into employee (name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user) " +"values (" +"#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")@AutoFill(value = OperationType.INSERT)void insert(Employee employee);/*** 根据id修改分类* @param category*/@AutoFill(value = OperationType.UPDATE)void update(Category category);/*** 插入数据* @param category*/@AutoFill(value = OperationType.INSERT)@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +" VALUES" +" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")void insert(Category category);

@Before("autoFillPointCut()")

@Before("autoFillPointCut()") 中的参数 "autoFillPointCut()" 用来指定前置通知的切入点。它告诉 AOP 框架,这个前置通知(autoFill() 方法)应该在什么地方执行。 

"autoFillPointCut()" 是一个切入点表达式,引用了之前用 @Pointcut 注解定义的切入点方法 autoFillPointCut()

@Pointcut 定义的 autoFillPointCut() 方法:

@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}

 这个方法并没有实际的代码执行,它仅仅是一个标识符,用来描述一个切入点,指定了哪些方法应该被拦截。它定义了拦截 com.sky.mapper 包下所有带有 @AutoFill 注解的方法。

然后,@Before("autoFillPointCut()") 表示:

在所有符合 autoFillPointCut() 所定义的切入点表达式的目标方法之前,执行 autoFill() 方法。

JoinPoint

JoinPoint 是 AOP(面向切面编程)中的一个核心概念,它代表了在程序执行过程中的某个连接点。在 Spring AOP 中,JoinPoint 通常指的是拦截的方法调用,你可以通过 JoinPoint 获取很多与当前执行方法有关的信息,比如方法名、参数、目标对象等。

在 Spring AOP 中,JoinPoint 表示一个拦截点,即某个被拦截的方法执行时的上下文信息。你可以把 JoinPoint 看作一个对象,里面存储了和当前拦截方法相关的各种数据。通过 JoinPoint,我们可以访问到很多和当前方法执行有关的详细信息。

你能通过 JoinPoint 获取哪些信息?

  • 目标方法的签名(方法名称、返回类型、参数类型等)。
  • 目标方法的参数
  • 目标对象(即该方法所属的对象)。
  • 方法执行的位置(前置通知、后置通知等)。

例子中的 JoinPoint

在代码中,JoinPoint 被传递到了 autoFill() 方法里。这个 JoinPoint 表示当前拦截的数据库操作方法(如插入或更新操作)。我们通过 JoinPoint 来获取方法的签名、注解、以及被调用的方法的参数。

获取方法签名和注解

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

joinPoint.getSignature():获取当前被拦截方法的签名信息。Signature 是方法的签名对象,包含方法名、返回类型、参数类型等信息。

MethodSignature:是 Signature 的子类,提供了更多关于方法的信息。这里通过类型转换,将 Signature 转为 MethodSignature

signature.getMethod().getAnnotation(AutoFill.class):获取方法上的 @AutoFill 注解对象。通过这个注解对象可以获取到注解的元数据,比如 value(数据库操作类型)。

autoFill.value():获取 @AutoFill 注解中的 value 属性,它表示数据库操作的类型(如 INSERT 或 UPDATE)。

获取被拦截方法的参数

Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {return;
}
Object entity = args[0];

joinPoint.getArgs():获取当前被拦截方法的参数列表(数组形式)。如果参数为空或参数长度为 0,说明没有实体对象需要填充,直接返回。

Object entity = args[0];:假设被拦截的方法的第一个参数是需要填充的实体对象,将其取出用于后续操作。

反射

什么是反射

反射是 Java 提供的一种功能,允许在运行时动态获取类的信息,并且可以操作这些类的信息,比如获取类的字段、方法、构造函数,甚至调用方法。它的关键在于灵活性,因为我们可以在编译时不知道类的细节,但在运行时操作它们。

通常情况下,我们编写代码时,类、方法、属性等信息都是在编译时就已经确定好的。但是在某些情况下,我们需要编写更加通用的代码,让代码在不知道具体类型的情况下,仍然能够操作这些对象。这种需求就可以通过反射实现。

获取 Class 对象

反射的核心在于获取类的**Class 对象**,通过这个对象可以获取类的各种信息。你有三种常用方式获取 Class 对象:

1.通过类名

Class<?> clazz = Class.forName("com.example.MyClass");

Class.forName() 通过类的全限定名(包名+类名)来获取类的 Class 对象。这种方式适用于你知道类名(可能从配置文件或数据库中读取)的情况。 

2.通过类的实例: 

MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();

通过一个对象实例来获取该对象的 Class 对象。这种方式适用于你已经有该类的实例对象的情况。 

3.通过类的字面量: (本次公共字段技术就是通过类的字面量)

Class<?> clazz = MyClass.class;

直接通过类名加 .class 获取 Class 对象。这种方式适用于在代码中直接指定类的情况。 

获取 Method 对象

一旦你有了 Class 对象,你可以通过**getDeclaredMethod()** 方法来获取类中的某个方法。getDeclaredMethod() 需要两个参数:

        第一个参数:方法名(字符串形式)。

        第二个参数:方法的参数类型(可以是多个,如果方法有多个参数)。

Method method = clazz.getDeclaredMethod("setCreateTime", LocalDateTime.class);

这个代码的作用是通过类的 Class 对象 clazz 获取名为 setCreateTime 的方法,该方法接受一个 LocalDateTime 类型的参数。

方法名和参数类型必须完全匹配,否则会抛出 NoSuchMethodException。所以在使用时,你需要确保方法名称和参数类型一致。

动态调用方法——invoke()

Method 对象不仅仅能用来描述类中的某个方法,它还提供了一个功能强大的方法——invoke(),用来在运行时调用方法。

invoke() 方法需要两个参数:

  1. 调用哪个对象上的方法:也就是你想在哪个对象上执行该方法。
  2. 传递给方法的参数值:传递给方法的实际参数值(如果方法有多个参数,这些参数依次传入)。

假设有如下代码:

public class User {private LocalDateTime createTime;private Long createUser;public void setCreateTime(LocalDateTime createTime) {this.createTime = createTime;}public void setCreateUser(Long createUser) {this.createUser = createUser;}
}

 我们希望通过反射调用 setCreateTimesetCreateUser 方法:

// 获取 User 类的 Class 对象
Class<?> clazz = user.getClass();// 获取 setCreateTime 方法对象
Method setCreateTime = clazz.getDeclaredMethod("setCreateTime", LocalDateTime.class);// 获取 setCreateUser 方法对象
Method setCreateUser = clazz.getDeclaredMethod("setCreateUser", Long.class);// 创建要传入的方法参数
LocalDateTime now = LocalDateTime.now();
Long currentUserId = 12345L;// 通过反射调用 setCreateTime 方法,给 createTime 字段赋值
setCreateTime.invoke(user, now);// 通过反射调用 setCreateUser 方法,给 createUser 字段赋值
setCreateUser.invoke(user, currentUserId);

在这里,我们通过反射动态调用了 user 对象的 setCreateTimesetCreateUser 方法,分别给 createTimecreateUser 字段赋值。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 比特币网络和支付
  • Linux网络编程IO管理
  • 使用Docker快速启动Nacos集群
  • 微信小程序页面制作——个人信息
  • 探究:为什么JavaScript要在body标签尾部引入?
  • 韦季李输入法_屏幕键盘第二个选择
  • 社群空间站9.9付费入群系统二开源码 易支付版全套搭建教程
  • 2024上学期--实验室学习计划
  • Android终端如何快速接入GB28181平台实现实时音视频回传
  • 计算机网络(二) —— 网络编程套接字
  • Windows下Java环境配置教程
  • 2024下学期学习总结加今日学习总结
  • 数据结构-堆-详解
  • 【机器学习】K近邻
  • 基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模
  • [iOS]Core Data浅析一 -- 启用Core Data
  • Create React App 使用
  • Golang-长连接-状态推送
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • JDK 6和JDK 7中的substring()方法
  • log4j2输出到kafka
  • Python爬虫--- 1.3 BS4库的解析器
  • ubuntu 下nginx安装 并支持https协议
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 模型微调
  • 前端之React实战:创建跨平台的项目架构
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 写代码的正确姿势
  • 白色的风信子
  • ​2020 年大前端技术趋势解读
  • ​2021半年盘点,不想你错过的重磅新书
  • ​DB-Engines 12月数据库排名: PostgreSQL有望获得「2020年度数据库」荣誉?
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • #define、const、typedef的差别
  • #stm32整理(一)flash读写
  • #我与Java虚拟机的故事#连载08:书读百遍其义自见
  • (poj1.3.2)1791(构造法模拟)
  • (附源码)spring boot智能服药提醒app 毕业设计 102151
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (切换多语言)vantUI+vue-i18n进行国际化配置及新增没有的语言包
  • (四)js前端开发中设计模式之工厂方法模式
  • .NET 5种线程安全集合
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .net MySql
  • .NET 表达式计算:Expression Evaluator
  • .NET 动态调用WebService + WSE + UsernameToken
  • .net 流——流的类型体系简单介绍
  • .net 中viewstate的原理和使用
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • .NET面试题解析(11)-SQL语言基础及数据库基本原理
  • .NET命名规范和开发约定
  • @Bean有哪些属性