☰
hsweb-framework 核心模块 hsweb-core 实战指南:FastBeanCopier、枚举数据字典与 ToString 脱敏
2026/9/25 5:08:13 网站建设 项目流程
  • 后端
  • 认证鉴权
  • 企业应用

【免费下载链接】hsweb-framework

hsweb (haʊs wɛb) 是一个基于spring-boot 2.x开发 ,首个使用全响应式编程的企业级后台管理系统基础项目。

项目地址:https://gitcode.com/gh_mirrors/hs/hsweb-framework
点击查看免费下载

hsweb-core是 hsweb-framework 的基础模块,定位是"系统核心、通用工具",提供了整个框架中被反复依赖的三件高频工具:基于字节码生成的高效 Bean 复制器FastBeanCopier、基于枚举实现的数据字典体系EnumDict,以及支持字段脱敏的ToString工具。读完本篇,你将掌握这三个工具的完整用法、存储与序列化的底层约定,以及它们在hsweb-core源码中的实现原理,从而在企业级后台项目中直接落地"复制、字典、脱敏"三类通用能力。

一、模块定位

hsweb-core/README.md 将本模块概括为"系统核心、通用工具等"。从源码目录结构看(见 org/hswebframework/web),模块内容覆盖 Bean 操作(bean包)、数据字典(dict包)、上下文(context包)、i18n、ID 生成器、事件、异常分析与回收器等,是hsweb-commons、hsweb-authorization、hsweb-system等上层模块的公共底座。本篇聚焦 README 中明确列出的三个核心能力:Bean 复制、数据字典与 ToString。

二、FastBeanCopier:高性能 Bean 复制

基本用法

README 给出的核心 API 只有两行,即可覆盖绝大多数复制场景(源码见 FastBeanCopier.java):

//将source对象中的属性复制到target中. FastBeanCopier.copy(source, target); //将source对象中的属性复制到target中.不复制id字段 FastBeanCopier.copy(source, target, "id");

copy的第三个参数String... ignore用于排除不需要复制的属性,内部会转换为Set<String>后传入复制逻辑(FastBeanCopier.java#L108-L130)。除最简形式外,源码还提供了一组重载:

  • copy(S source, Class<T> target, String... ignore):自动实例化目标对象后复制;
  • copy(S source, Supplier<T> target, String... ignore):通过 Supplier 提供目标对象;
  • copy(S source, T target, Converter converter, String... ignore):自定义类型转换器。

Cloneable 约定:README 特别强调——如果属性类实现了Cloneable接口,复制时会调用其clone()方法,因此实现了Cloneable就必须重写clone方法并声明为public。这一点在源码中可以直接印证:ClassProperty.createGetterFunction()在源与目标类型相同时,若类型实现了Cloneable,生成的复制代码就是getter().clone()(FastBeanCopier.java#L592-L602)。

原理:javassist 动态生成复制类,而非反射

README 的概括是:"使用工具类Proxy,通过javassist去动态构造一个类,通过原生的方式调用 get/set 方法,而不是通过低效的反射"。落到源码上,这条链路是:

  1. 缓存查找:FastBeanCopier持有静态ConcurrentHashMap<CacheKey, Copier> CACHE,以"源类型 + 目标类型"组成的CacheKey为键(FastBeanCopier.java#L42-L44)。getCopier(source, target, true)通过computeIfAbsent保证同一对类型只会动态生成一次 Copier,后续调用直接命中缓存,因此"首次构建、后续零反射"。
  2. 代码拼装:createCopierCode遍历源类与目标类的PropertyDescriptor(要求读、写方法均存在,并按声明字段顺序排序),为每个同名字段生成形如if(!ignore.contains("xx")){ target.setX(source.getX()); }的纯 Java 代码片段(FastBeanCopier.java#L372-L444)。
  3. 字节码生成:Proxy.java 是一个URLClassLoader,构造函数中用 javassist 的ClassPool.makeClass创建以类名$ProxyN命名的新类(Proxy.java#L115-L153);addMethod(code)将上一步拼出的代码作为方法注入(Proxy.java#L155-L160);最后getTargetClass()调用toBytecode()并用defineClass完成加载(Proxy.java#L253-L267)。

由此得到一次"编译期"开销换"运行期"纯方法调用的设计,这也是其相对BeanUtils.copyProperties(每次反射)在批量场景下的优势来源。

源码中的扩展能力

README 未展开、但源码已实现并值得知道的能力包括:

  • 类型转换:当源字段与目标字段类型不一致时,生成的代码会调用converter.convert(value, targetType, genericType)。默认实现DefaultConverter(FastBeanCopier.java#L760-L949)支持:Date与字符串/数字互转、字符串按逗号拆分转集合、数字解析、枚举(含EnumDict)按值或名称匹配、数组构造、集合泛型逐项转换、Bean 转 Map、以及最终兜底的 Apache Commons BeanUtils 转换器。这对应了 README 中"支持复杂结构、类型转换、集合泛型"的说法。
  • Bean 与 Map 互转:source或target为Map时直接走putAll/逐键写入路径(FastBeanCopier.java#L138-L150);对易扩展对象(Extendable)还包装了ExtendableToMapCopier、MapToExtendableCopier、ExtendableToBeanCopier,可将多余字段写入扩展区(FastBeanCopier.java#L256-L262)。
  • Record 支持:JDK 14+ 的 record 不可变,源码单独维护了RECORD_CACHE与RecordCopier,按组件名重新构造新实例(FastBeanCopier.java#L157-L194)。
  • 冷启动并发:测试类 FastBeanCopierColdStartContentionTest.java 专门验证多线程并发首次copy时缓存构建的正确性,常规功能则由 FastBeanCopierTest.java、FastBeanCopierBeanTest.java 覆盖,可作为行为基准参考。

三、数据字典:用枚举定义并管理字典

定义枚举字典

README 的标准做法是:定义一个枚举并实现EnumDict接口:

@AllArgsConstructor @Getter @Dict(value = "data-status") //定义一个id,默认为由枚举类名转换而来 public enum DataStatusEnum implements EnumDict<Byte> { ENABLED((byte) 1, "正常"), DISABLED((byte) 0, "禁用"), LOCK((byte) -1, "锁定"), DELETED((byte) -10, "删除"); private Byte value; private String text; }

说明:@Dict注解(Dict.java)的属性为value(字典 ID)、alias(别名)与comments(备注),可用于方法、字段或类型。不显式指定 ID 时,DefaultDictDefineRepository.parseEnumDict 会将枚举简单名按"驼峰转下划线、下划线换连字符"生成 ID(如DataStatusEnum→data-status),并把类名本身作为别名。

在实体中使用:单选与多选

@Data public class User { private String id; //单选 private DataStatusEnum status; //多选 private DataStatusEnum[] statusArr; }

README 明确了三种存储约定,这也是使用枚举字典前必须理解的规则:

  1. 单选:持久化时自动存储字典的value值,数据库字段类型应与value字段类型一致(上例为字节型数字)。
  2. 多选且选项数小于 64 个:值会经过位运算(EnumDict的 mask 机制)后以单个数字存储,查询也用位运算过滤,例如where().in("statusArr", 0, -1)生成where status_arr & {bit} != {bit}形式的 SQL,因此数据库字段应为数字类型。
  3. 选项数大于等于 64 个:需要自行实现存储与查询,可改用中间表,或采用hsweb-system/hsweb-system-dictionary模块的实现。

需要注意 README 的限定:第 1、2 项的持久化行为由 hsweb 自带的 dao 实现负责完成,如果项目没有使用该 dao 层,则只会用到字典的模型与序列化能力。

位运算 API 与 64 项上限

EnumDict.java 中,每个选项的掩码由getMask() = 1L << index()决定,index()默认取ordinal()。这带来一条硬约束:枚举选项数量不得超过 64 个,且定义顺序不能随意变动(源码 Javadoc 中也有相同警告)。常用静态方法如下:

方法语义
toMask(dict...)将若干选项合并为一个 mask
in(target, dict...)目标值是否包含任一给定选项(选项 ≥64 时自动退化为集合包含判断)
maskIn(mask, dict...)mask 是否全部包含给定选项
maskInAny(mask, dict...)mask 是否至少包含一个给定选项
getByMask(type, mask)由存储值还原选项列表;选项 ≥64 时抛出UnsupportedOperationException
findByValue / findByText / find按值、文本或宽松匹配查找,返回Optional

此外eq(Object)提供宽松的等价判断:同一对象、值相等、数字忽略类型、value/text 忽略大小写、支持集合/Map 入参,可推断它被广泛用于参数绑定与查询比较场景(EnumDict.java#L94-L124)。

JSON 序列化约定

EnumDict默认在序列化为 JSON 时以对象形式输出{"value":..., "text":...}(text 会走 i18n 解析),对应@JsonValue标注的getWriteJSONObject()(EnumDict.java#L305-L317)。如需改回"只输出 value"的紧凑形式,可以通过 JVM 参数关闭:

java -jar -Dhsweb.enum.dict.disableWriteJSONObject=true

反序列化方向则由内置的EnumDictJSONDeserializer处理,同时适配 fastjson 与 Jackson:支持数字(按值或 ordinal)、字符串(按 value/name/text,忽略大小写)、对象(取value键,回退text键)等形态;无法匹配时在参数绑定场景抛出ValidationException。EnumDictTest.java 验证了"E1"、"e1"、0三种输入都能正确还原为TestEnum.E1。

国际化字典 I18nEnumDict

若字典文本需要多语言,可实现 I18nEnumDict.java,其getI18nCode()默认返回"全限定类名.枚举名",并按resources/i18n/{path}/{name}_zh_CN.properties约定加载消息文件,例如:

com.domain.dict.DeviceState.online=在线

约定中{name}不能包含下划线,且不同文件不能完全重名(见该接口 Javadoc 中的正反示例)。

字典注册:DictDefineRepository

README 指出:"所有的字典都会注册到DictDefineRepository,可通过此类去获取字典,以提供给前端或者其他地方使用。" 默认实现 DefaultDictDefineRepository.java 用ConcurrentHashMap按 ID 保存解析结果,提供响应式的getDefine(id)(Mono)与getAllDefine()(Flux),前端字典下拉、表单校验等能力即以此为数据源。

四、ToString:Bean 转字符串与字段脱敏

ToString.java 提供"Bean 转 String"的能力,包括字段脱敏(打码)。README 的标准用法:

@Getter @Setter public class MyEntity { //敏感字段,在ToString的时候会给字段打码.比如: 185*****234 @org.hswebframework.web.bean.ToString.Ignore private String userPhone; public String toString() { return org.hswebframework.web.bean.ToString.toString(this); } }

结合源码可以补充以下细节:

  • @Ignore注解支持打在类或字段上,cover()默认true(脱敏遮盖),value()可指定额外忽略的属性;类级标注可让整个类型的字段默认被处理(ToString.java#L38-L47)。
  • 默认行为:DEFAULT_FEATURE启用了coverIgnoreProperty(排除字段用*遮盖)与nullPropertyToEmpty(null 字符串转""、null 集合转[])两项(ToString.java#L15-L19),可通过类/字段上的@Features注解调整。
  • Feature 全集:empty、ignoreNullProperty、nullPropertyToEmpty、coverIgnoreProperty、disableNestProperty(关闭嵌套对象递归 toString)、jsonFormat、writeClassname(ToString.java#L56-L105)。
  • 脱敏算法:默认 Operator DefaultToStringOperator.java 对标记字段调用coverString(str, 80),保留约 80% 的字符、中间打码(如18502314087 => 185****087);每个类型的 Operator 通过静态cache缓存,避免重复构建属性描述符。

典型落地场景是把日志打印、异常信息中的实体对象统一收敛到toString(this),敏感字段自动脱敏,避免手机号、姓名等信息在日志中裸露。

五、适用前提小结

  • 三个工具均为hsweb-core的静态工具/接口,不依赖 Spring 容器即可完成复制与字典判断;setBeanFactory允许注入自定义BeanFactory以控制目标对象实例化(FastBeanCopier.java#L59-L62)。
  • 枚举字典的数据库 value 存储与位运算查询依赖 hsweb 自带的 dao 实现;未使用时仅模型、比较与 JSON 序列化能力可用。
  • 多选位运算方案要求选项数小于 64 且顺序稳定;超限场景请改用中间表或hsweb-system/hsweb-system-dictionary方案。
  • 版本行为请以当前仓库源码为准,本文所有路径均位于 hsweb-core 模块内,可直接对照阅读。
  • 后端
  • 认证鉴权
  • 企业应用

【免费下载链接】hsweb-framework

hsweb (haʊs wɛb) 是一个基于spring-boot 2.x开发 ,首个使用全响应式编程的企业级后台管理系统基础项目。

项目地址:https://gitcode.com/gh_mirrors/hs/hsweb-framework
点击查看免费下载

相关推荐

上一篇:tf_efficientnetv2_b1.in1k核心功能全解析:图像分类、特征映射与嵌入提取
下一篇:终极指南:Activiti流程事件溯源实战——从EventDispatcher到状态重建全解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询