You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过自定义注解实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 07:47:34