如何通过自定义注解实现Java类字段值的掩码与格式化?
这需求挺实用的!要实现自定义注解来动态处理字段的掩码或格式化,核心思路是利用Java的反射机制(运行时读取注解信息),如果是Spring项目还可以结合AOP来自动拦截Getter调用,不用手动写处理逻辑。下面给你一套完整的实现方案,从基础到进阶都有:
实现方案
1. 定义自定义@Mask注解
首先我们需要定义一个运行时生效的注解,指定它只能作用在字段上,并允许传入格式化模板:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Mask { // 格式化规则模板,比如"XXXX XXXX XXXX XXXX" String value(); }
2. 编写注解处理器工具类
这个工具类负责读取字段上的@Mask注解,根据模板规则处理原始字段值。我们以银行卡号格式化为例,实现一个通用的模板替换逻辑:
import java.lang.reflect.Field; public class MaskProcessor { public static Object getProcessedValue(Object targetObj, String fieldName) throws NoSuchFieldException, IllegalAccessException { // 通过反射获取字段 Field field = targetObj.getClass().getDeclaredField(fieldName); field.setAccessible(true); String originalValue = (String) field.get(targetObj); // 检查字段是否标注了@Mask注解 Mask maskAnnotation = field.getAnnotation(Mask.class); if (maskAnnotation == null) { return originalValue; } String template = maskAnnotation.value(); // 校验模板与原始值的匹配性:模板中X的数量必须等于原始值长度 int xCount = (int) template.chars().filter(c -> c == 'X').count(); if (originalValue.length() != xCount) { throw new IllegalArgumentException("原始值长度与模板中X的数量不匹配,请检查配置"); } // 替换模板中的X为原始值对应位置的字符 StringBuilder result = new StringBuilder(); int valueIndex = 0; for (char c : template.toCharArray()) { result.append(c == 'X' ? originalValue.charAt(valueIndex++) : c); } return result.toString(); } }
3. 基础使用示例
在你的业务类中使用注解,然后通过工具类获取处理后的值:
public class MyClass { @Mask("XXXX XXXX XXXX XXXX") private String cardNumber = "4871049999999910"; public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { MyClass myObj = new MyClass(); String formattedCard = (String) MaskProcessor.getProcessedValue(myObj, "cardNumber"); System.out.println(formattedCard); // 输出:4871 0499 9999 9910 } }
4. 进阶优化:Spring项目用AOP自动处理
如果是Spring环境,我们可以用AOP拦截所有带@Mask注解字段的Getter方法,自动返回处理后的值,不用每次手动调用工具类:
定义AOP切面
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Field; @Aspect public class MaskAspect { @Around("execution(* *..get*(..))") public Object interceptGetter(ProceedingJoinPoint joinPoint) throws Throwable { // 先执行原始Getter方法获取值 Object originalResult = joinPoint.proceed(); if (!(originalResult instanceof String)) { return originalResult; } // 从方法名推导字段名(比如getCardNumber -> cardNumber) MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String methodName = signature.getName(); String fieldName = methodName.substring(3).replaceFirst("^[A-Z]", match -> match.group().toLowerCase()); try { // 检查字段是否有@Mask注解 Field field = joinPoint.getTarget().getClass().getDeclaredField(fieldName); Mask maskAnnotation = field.getAnnotation(Mask.class); if (maskAnnotation == null) { return originalResult; } // 复用之前的模板处理逻辑 String template = maskAnnotation.value(); String originalValue = (String) originalResult; int xCount = (int) template.chars().filter(c -> c == 'X').count(); if (originalValue.length() != xCount) { return originalResult; } StringBuilder processed = new StringBuilder(); int idx = 0; for (char c : template.toCharArray()) { processed.append(c == 'X' ? originalValue.charAt(idx++) : c); } return processed.toString(); } catch (NoSuchFieldException e) { // 字段名与Getter方法不匹配时,直接返回原始值 return originalResult; } } }
业务类使用示例
现在只需要正常调用Getter方法,AOP会自动处理:
public class MyClass { @Mask("XXXX XXXX XXXX XXXX") private String cardNumber = "4871049999999910"; public String getCardNumber() { return cardNumber; } public static void main(String[] args) { // 开启Spring容器后,调用Getter自动返回格式化后的值 MyClass myObj = new MyClass(); System.out.println(myObj.getCardNumber()); // 输出:4871 0499 9999 9910 } }
5. 扩展:支持灵活掩码规则
如果需要更复杂的掩码(比如只显示前4位和后4位,中间用*填充),可以扩展注解的属性:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Mask { // 模板格式化优先级高于前缀后缀配置 String value() default ""; // 保留前缀长度 int prefix() default 0; // 保留后缀长度 int suffix() default 0; // 掩码字符 char maskChar() default '*'; }
然后在处理器中添加对应的逻辑,判断是用模板还是前缀后缀规则处理即可。
内容的提问来源于stack exchange,提问作者Eduardo Lopes
相关产品推荐
相关产品推荐

