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 字段赋值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/53473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue如何实现路由缓存

&#xff08;以下示例皆是以vue3vitets项目为例&#xff09; 场景一&#xff1a;所有路由都可以进行缓存 在渲染路由视图对应的页面进行缓存设置&#xff0c;代码如下&#xff1a; <template><router-view v-slot"{ Component, route }"><transiti…

大数据-119 - Flink Window总览 窗口机制-滚动时间窗口-基于时间驱动基于事件驱动

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

二、主流的架构方法论

在企业软件开发和系统设计中&#xff0c;架构方法论提供了指导原则、最佳实践和框架来帮助架构师和开发团队设计和实施高质量的软件系统。以下是一些主流的架构方法论及其特点分析&#xff1a; 1. TOGAF&#xff08;The Open Group Architecture Framework&#xff09; 特点&…

损坏SD数据恢复的8种有效方法

SD卡被用于许多不同的产品来存储重要数据&#xff0c;如图片和重要的商业文件。如果您的SD卡坏了&#xff0c;您需要SD数据恢复来获取您的信息。通过从损坏的SD卡中取回数据&#xff0c;您可以确保重要文件不会永远丢失&#xff0c;这对于工作或个人原因是非常重要的。 有许多…

【Qt笔记】QTableWidget控件详解

目录 引言 一、QTableWidget的特点 二、QTableWidget基础 2.1 引入QTableWidget 2.2 基本属性 三、代码示例&#xff1a;初始化QTableWidget 四、编辑功能 4.1 设置单元格为只读 4.2 响应内容更改 五、选择模式 六、样式定制 七、与其他控件的交互 7.1 在单元格…

[SUCTF 2018]annonymous1

知识点&#xff1a; 匿名函数创建其实有自己的名字&#xff08;%00lambda_%d&#xff09; 进入页面开始代码审计. <?php // 使用 create_function 创建一个匿名函数&#xff0c;该函数调用 die() 函数并执行 cat flag.php 命令&#xff08;在服务器上执行&#xff0c;如果…

破解资源利用困境!麒麟信安支撑吉林市中医院完成云化转型

吉林市中医院在数字化转型的过程中&#xff0c;面临着一系列挑战。此前&#xff0c;该医院采用传统数据中心物理服务器部署模式&#xff0c;虽然在初期为医院的信息化建设提供了基础&#xff0c;但随着时间的推移&#xff0c;其局限性逐渐显现。每个应用系统都占用了大量的新旧…

如何在 DigitalOcean Droplet 云服务器上部署 Next.js 应用

Next.js 是一个流行的 React 框架&#xff0c;可轻松构建服务器渲染的 React 应用程序。在本教程中&#xff0c;我们将介绍如何使用 Nginx 作为反向代理&#xff0c;在 DigitalOcean 的 droplet 云主机上部署 Next.js 应用程序。以下是逐步指南&#xff0c;假设你已经准备好部署…

基于SpringBoot+Vue+MySQL的牙科医就诊管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范化管理。这样的…

HTML/CSS/JS学习笔记 Day2(HTML--网页标签 上)

跟着该视频学习&#xff0c;记录笔记&#xff1a;【黑马程序员pink老师前端入门教程&#xff0c;零基础必看的h5(html5)css3移动端前端视频教程】https://www.bilibili.com/video/BV14J4114768?p12&vd_source04ee94ad3f2168d7d5252c857a2bf358 Day2 内容梳理&#xff1a;…

儿童孤独症康复学校:打破孤岛,关爱与成长

在世界的某个角落&#xff0c;有一群孩子&#xff0c;他们如同夜空中最亮的星&#xff0c;却往往因孤独症的屏障&#xff0c;而难以与周围的世界建立连接。这些孩子&#xff0c;如同被无形的岛屿环绕&#xff0c;渴望着被理解、被接纳。而正是在这样的背景下&#xff0c;星贝育…

[C++11#48][智能指针] RAII原则 | 智能指针的类型 | 模拟实现 | shared_ptr | 解决循环引用

目录 一.引入 1. 为什么需要智能指针&#xff1f; 2. 什么是内存泄漏&#xff1f; 内存泄漏分类 3.回忆 this 二. 原理 1. RAII 资源获取即初始化 2.像指针一样 三. 使用 1. 问题&#xff1a; string 的浅拷贝 2.解决 auto_ptr 自定义 auto_ptr unique_ptr - 独占…

原生 iOS 引入 Flutter 报错 kernel_blob.bin 找不到

情况 在一次原生 iOS 项目中引入 Flutter 的过程中&#xff0c;在模拟器中运行出现报错&#xff1a; 未能打开文件“kernel_blob.bin”&#xff0c;因为它不存在。 如下图&#xff1a; 模拟器中一片黑 原因&解决方案 这个是因为 Flutter 的打包 iOS framework 命令中…

ES之三:springboot集成ES

一.选择版本很重要&#xff0c;不然会找不到好多方法 明明有Timeout方法&#xff0c;不报红&#xff0c;运行时&#xff0c;报错&#xff0c;找不到该类 ClassNotFoundException 为了避免使用的Elasticsearch版本和SpringBoot采用的版本不一致导致的问题&#xff0c;尽量使用…

统计学习方法与实战——统计学习方法概论

统计学习方法概论 文章目录 统计学习方法概论前言章节目录导读 实现统计学习方法的步骤统计学习方法三要素模型模型是什么? 策略损失函数与风险函数常用损失函数ERM与SRM 算法 模型评估与模型选择过拟合与模型选择 正则化与交叉验证泛化能力生成模型与判别模型生成方法判别方法…

高校大模型实验室大模型应用平台

大模型应用平台是一款专为高校大模型应用场景教学和科研打造的知识库问答系统。该平台易于使用&#xff0c;知识库支持常见的txt、doc、pdf、md等数据文件上传&#xff0c;同时提供了简洁易懂的操作配置界面&#xff0c;使用户可以轻松地搭建和训练AI应用&#xff0c;并快速进行…

arm64高速缓存基础知识

高速缓存的替换策略 随机法&#xff1a;随机地确定替换的高速缓存行&#xff0c;由一个随机数产生器产生随机数来确认替换行 FIFO法&#xff1a;选择最先调入的高速缓存行进行替换 LRU法&#xff1a;最少使用的行优先替换。 高速缓存的共享属性 内部共享的高速缓存通常指的…

时间序列处理方法

对于时间序列数据进行多分类任务&#xff0c;RNN对于顺序建模不理想&#xff0c;可以考虑以下模型和改进方法&#xff1a; Transformers&#xff08;时序数据版&#xff09; 优势: 相较于RNN&#xff08;如GRU、LSTM&#xff09;&#xff0c;Transformers 擅长捕捉长距离依赖关…

Python 多线程访问数据库正确使用dbutils PooledDB数据库连接池

1.安装DBUtils pip install DBUtils 2.db_helper.py的代码如下 import pymysql from dbutils.pooled_db import PooledDB from config import configclass DBHelper:def __init__(self):""":param mincached:连接池中空闲连接的初始数量:param maxcached:连接…

Flutter基本组件Text使用

Text是一个文本显示控件&#xff0c;用于在应用程序界面中显示单行或多行文本内容。 Text简单Demo import package:flutter/material.dart;class MyTextDemo extends StatelessWidget {const MyTextDemo({super.key});overrideWidget build(BuildContext context) {return Sca…