背景

公司项目需要实现多语言,需要通过序列化返回值获取多语言的值,此处通过三种方式实现。

创建自定义注解

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) // 运行时保留,供反射读取
@JacksonAnnotationsInside // 告诉Jackson这是一个组合注解,内部注解生效
@JsonSerialize(using = I18nValueSerializer.class) // 指定使用的序列化器
public @interface I18nValue {
    // 需要拼接的源字段名称
    String sourceFieldName();

    // 需要拼接的类型标识
    String baseCodeType();
}

创建自定义序列化器

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.lang.reflect.Field;

public class I18nValueSerializer extends StdSerializer<Object> implements ContextualSerializer {
    
    private String targetFieldName; // 要获取值的字段名
    private String type;           // 要拼接的类型
    
    // 默认构造函数
    public I18nValueSerializer() {
        super(Object.class);
    }
    
    // 带参数的构造函数
    public I18nValueSerializer(String targetFieldName, String type) {
        super(Object.class);
        this.targetFieldName = targetFieldName;
        this.type = type;
    }
    
    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) 
        throws IOException {
        
        // 这里value是当前字段的值,但我们需要的是targetFieldName字段的值
        // 所以需要获取父对象来反射获取目标字段的值
        try {
            // 获取目标字段的值
            String targetValue = getTargetFieldValue(gen.getCurrentValue());
            String finalValue = targetValue + (type != null && !type.isEmpty() ? "_" + type : "");
            
            gen.writeString(finalValue);
        } catch (Exception e) {
            // 如果出错,写入默认值或错误信息
            gen.writeString("error_" + type);
        }
    }
    
    /**
     * 通过反射获取目标字段的值
     */
    private String getTargetFieldValue(Object currentObject) throws Exception {
        if (currentObject == null || targetFieldName == null) {
            return "";
        }
        
        Class<?> clazz = currentObject.getClass();
        Field targetField = null;
        
        // 遍历所有字段查找目标字段
        for (Field field : clazz.getDeclaredFields()) {
            if (targetFieldName.equals(field.getName())) {
                targetField = field;
                break;
            }
        }
        
        if (targetField == null) {
            throw new NoSuchFieldException("Field not found: " + targetFieldName);
        }
        
        targetField.setAccessible(true);
        Object fieldValue = targetField.get(currentObject);
        
        return fieldValue != null ? fieldValue.toString() : "";
    }
    
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property) 
        throws JsonMappingException {
        
        if (property == null) {
            return this;
        }
        
        // 获取字段上的I18nValue注解
        I18nValue i18nValue = property.getAnnotation(I18nValue.class);
        if (i18nValue == null) {
            i18nValue = property.getContextAnnotation(I18nValue.class);
        }
        
        if (i18nValue != null) {
            return new I18nValueSerializer(i18nValue.sourceFieldName(), i18nValue.baseCodeType());
        }
        
        return this;
    }
}

创建对象级别的序列化器(替代方案)

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.lang.reflect.Field;

public class I18nObjectSerializer extends JsonSerializer<Object> {
    
    @Override
    public void serialize(Object object, JsonGenerator gen, SerializerProvider provider) 
        throws IOException {
        
        gen.writeStartObject();
        
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            
            I18nValue i18nValue = field.getAnnotation(I18nValue.class);
            try {
                if (i18nValue != null) {
                    // 获取目标字段的值
                    String targetValue = getFieldValue(object, i18nValue.sourceFieldName());
                    String finalValue = targetValue + 
                                      (i18nValue.baseCodeType() != null && !i18nValue.baseCodeType().isEmpty() ? 
                                       "_" + i18nValue.baseCodeType() : "");
                    
                    gen.writeStringField(field.getName(), finalValue);
                } else {
                    // 正常序列化其他字段
                    Object fieldValue = field.get(object);
                    provider.defaultSerializeField(field.getName(), fieldValue, gen);
                }
            } catch (Exception e) {
                gen.writeStringField(field.getName(), "error");
            }
        }
        
        gen.writeEndObject();
    }
    
    private String getFieldValue(Object object, String fieldName) throws Exception {
        Field targetField = object.getClass().getDeclaredField(fieldName);
        targetField.setAccessible(true);
        Object value = targetField.get(object);
        return value != null ? value.toString() : "";
    }
}

实体类使用示例

此处可以使用@Data注解,减少代码量

// 方式一:在字段上使用注解和序列化器
public class UserDTO {
    private Long id;
    private String username;
    private Integer status;
    
    // 这个字段会显示status字段的值 + "_text"
    @I18nValue(fieldName = "status", type = "text")
    @JsonSerialize(using = I18nValueSerializer.class)
    private String statusText;
    
    // 这个字段会显示username字段的值 + "_name"
    @I18nValue(fieldName = "username", type = "name")
    @JsonSerialize(using = I18nValueSerializer.class)
    private String userNameDisplay;
    
    // 正常字段
    private String email;
    
    // getter和setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public Integer getStatus() { return status; }
    public void setStatus(Integer status) { this.status = status; }
    
    public String getStatusText() { return statusText; }
    public void setStatusText(String statusText) { this.statusText = statusText; }
    
    public String getUserNameDisplay() { return userNameDisplay; }
    public void setUserNameDisplay(String userNameDisplay) { this.userNameDisplay = userNameDisplay; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

// 方式二:在整个类上使用序列化器
@JsonSerialize(using = I18nObjectSerializer.class)
public class ProductDTO {
    private String productCode;
    private String productName;
    private Double price;
    
    @I18nValue(fieldName = "productCode", type = "code")
    private String displayCode;
    
    @I18nValue(fieldName = "productName", type = "name")
    private String displayName;
    
    // getter和setter...
}

控制器示例

@RestController
public class DemoController {
    
    @GetMapping("/user")
    public UserDTO getUser() {
        UserDTO user = new UserDTO();
        user.setId(1L);
        user.setUsername("zhangsan");
        user.setStatus(1);
        user.setEmail("zhangsan@example.com");
        // statusText和userNameDisplay字段会自动序列化
        
        return user;
    }
    
    @GetMapping("/product")
    public ProductDTO getProduct() {
        ProductDTO product = new ProductDTO();
        product.setProductCode("P001");
        product.setProductName("iPhone");
        product.setPrice(5999.0);
        
        return product;
    }
}

预期的JSON输出

UserDTO输出:

{
  "id": 1,
  "username": "zhangsan",
  "status": 1,
  "statusText": "1_text",
  "userNameDisplay": "zhangsan_name",
  "email": "zhangsan@example.com"
}

ProductDTO输出:

{
  "displayCode": "P001_code",
  "displayName": "iPhone_name",
  "productCode": "P001",
  "productName": "iPhone",
  "price": 5999.0
}

高级版本:支持方法获取值

需要将I18nValueSerializer 的targetFieldName和type和getTargetFieldValue改为public

public class EnhancedI18nValueSerializer extends I18nValueSerializer {
    
    @Override
    private String getTargetFieldValue(Object currentObject) throws Exception {
        if (currentObject == null || targetFieldName == null) {
            return "";
        }
        
        Class<?> clazz = currentObject.getClass();
        
        // 先尝试通过字段获取
        try {
            Field targetField = clazz.getDeclaredField(targetFieldName);
            targetField.setAccessible(true);
            Object fieldValue = targetField.get(currentObject);
            return fieldValue != null ? fieldValue.toString() : "";
        } catch (NoSuchFieldException e) {
            // 如果字段不存在,尝试通过getter方法获取
            String getterName = "get" + targetFieldName.substring(0, 1).toUpperCase() + 
                              targetFieldName.substring(1);
            try {
                java.lang.reflect.Method getter = clazz.getMethod(getterName);
                Object result = getter.invoke(currentObject);
                return result != null ? result.toString() : "";
            } catch (NoSuchMethodException ex) {
                throw new NoSuchFieldException("Field or getter not found: " + targetFieldName);
            }
        }
    }
}

配置全局序列化器(可选)

@Configuration
public class JacksonConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        
        // 注册注解查找器
        module.setSerializerModifier(new BeanSerializerModifier() {
            @Override
            public List<BeanPropertyWriter> changeProperties(
                    SerializationConfig config, 
                    BeanDescription beanDesc, 
                    List<BeanPropertyWriter> beanProperties) {
                
                for (BeanPropertyWriter writer : beanProperties) {
                    I18nValue i18nValue = writer.getAnnotation(I18nValue.class);
                    if (i18nValue != null) {
                        writer.assignSerializer(
                            new I18nValueSerializer(i18nValue.fieldName(), i18nValue.type())
                        );
                    }
                }
                return beanProperties;
            }
        });
        
        mapper.registerModule(module);
        return mapper;
    }
}

总结

个人实践的I18nObjectSerializer 可以,I18nValueSerializer 不行,具体原因还在排查中(I18nValueSerializer失效的原因是序列号的字段,比如displayName是null的,序列号的时候会忽略null值,所以不生效,建议使用I18nObjectSerializer )

更多推荐