用lombok插件,驼峰属性第一个是一个字母的,属性没有接收到值,使用@JsonProperty解决(工作遇到的坑)

本文档展示了在Java SpringCloud项目中遇到的一个问题:用户实体(User)的`iName`属性在测试接口时未被正确赋值。问题的根源在于缺少了`@JsonProperty`注解来映射JSON字段。通过引入`@JsonProperty(iName)`,成功解决了属性绑定问题,确保了数据的正确传递。

一、实体

import lombok.*;
/**
 * 用户实体
 *
 * @author caden
 * @since 2022-11-12
 *
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
    private String oid;

    private String iName;
}

二、 测试接口
import com.mengxuegu.springcloud.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;


/**
 * 用户控制器
 * 
 * @author caden 
 * @since 2022-11-12
 * 
 */
@RestController
public class UserController {
    @RequestMapping(value = "/user/test", method = RequestMethod.POST)
    public User add(@RequestBody User user) {
        return user;
    }
}

三、 测试

 

<think> 我们需要将一个Java对象的属性复制到另一个对象,但是属性名的命名规则不同:一个驼峰命名,另一个是全大写(通常用下划线连接)。 常见的做法是使用对象映射工具,比如Spring的BeanUtils,但是它们默认要求属性名相同。因此,我们需要自定义映射规则。 我们可以使用以下方法: 1. 使用Apache BeanUtils配合自定义转换器(但需要注册转换器,且较复杂) 2. 使用MapStruct(推荐,因为它可以自定义映射规则) 3. 使用Spring的BeanWrapper或者自定义反射工具 4. 手动编写getter/setter(不推荐,但简单场景可用) 这里我们使用MapStruct,因为它性能高且易于自定义映射。 假设我们有两个类: 源对象:SourceObject,属性驼峰式,例如:userName, birthDate 目标对象:TargetObject,属性为大写下划线式,例如:USER_NAME, BIRTH_DATE 步骤: 1. 引入MapStruct依赖(如果项目中没有的话) 2. 定义一个映射接口,使用@Mapper注解 3. 在接口中定义映射方法 4. 使用@Mapping注解来指定字段名的对应关系 但是,如果两个对象的属性很多,且命名规则有规律(驼峰转下划线大写),我们可以自定义一个命名策略,避免为每个字段都写@Mapping。 MapStruct提供了@Mapper的componentModel属性以及自定义策略的方式。我们可以使用内置的驼峰转下划线策略,并转换为大写。 实际上,MapStruct在1.4版本之后支持使用AccessorNamingStrategy,但我们也可以在@Mapper注解中指定一个自定义的映射策略。 然而,更简单的方式是使用MapStruct的表达式来自动转换属性名。但是,MapStruct默认并没有提供从驼峰转大写下划线的内置方法。 我们可以这样实现: 方式一:为每个字段添加@Mapping注解(适用于字段少的情况) 方式二:使用自定义的映射规则(通过表达式)但这样比较复杂。 这里我们采用方式一,因为简单直接。但若字段很多,可以考虑写一个辅助方法生成映射配置。 示例代码: 源对象: ```java public class SourceObject { private String userName; private LocalDate birthDate; // getters and setters } ``` 目标对象: ```java public class TargetObject { private String USER_NAME; private LocalDate BIRTH_DATE; // getters and setters } ``` 映射接口: ```java import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; @Mapper public interface CustomObjectMapper { CustomObjectMapper INSTANCE = Mappers.getMapper(CustomObjectMapper.class); @Mappings({ @Mapping(source = "userName", target = "USER_NAME"), @Mapping(source = "birthDate", target = "BIRTH_DATE") }) TargetObject sourceToTarget(SourceObject source); } ``` 如果字段很多,这样写会非常繁琐。因此,我们可以利用MapStruct的组件模型和自定义匹配策略来避免手写每一个映射。 另一种方法是实现一个自定义的AccessorNamingStrategy,但这样比较复杂。我们可以考虑使用MapStruct的@BeanMapping和@MappingTarget,但这里不展开。 或者,我们可以使用一个中间转换:先把源对象转成一个Map,然后再从Map转换成目标对象。但这样效率低。 考虑到问题中要求的是两个相同属性的对象,只是命名规则不同,我们可以通过反射来动态设置,但反射性能较差。 为了简化,假设我们字段不多,就用手动映射。 但是,如果字段很多,我们可以用Lombok的@Builder或者使用反射工具类,如Spring的BeanUtils配合自定义属性名转换。 这里提供一个通用的反射方法(不推荐在性能敏感的地方使用): 使用Spring的BeanWrapper: ```java import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import java.beans.PropertyDescriptor; import java.util.HashMap; import java.util.Map; public class PropertyUtils { public static void copyPropertiesWithNamingConvention(Object source, Object target) { BeanWrapper srcWrap = new BeanWrapperImpl(source); BeanWrapper trgWrap = new BeanWrapperImpl(target); // 获取源对象的所有属性 PropertyDescriptor[] srcDescriptors = srcWrap.getPropertyDescriptors(); Map<String, String> propertyMap = new HashMap<>(); for (PropertyDescriptor srcDescriptor : srcDescriptors) { String srcName = srcDescriptor.getName(); if ("class".equals(srcName)) { continue; } // 将驼峰转为大写下划线 String targetName = convertCamelToUpperUnderscore(srcName); propertyMap.put(srcName, targetName); } // 现在,根据propertyMap进行复制 for (Map.Entry<String, String> entry : propertyMap.entrySet()) { String srcProperty = entry.getKey(); String targetProperty = entry.getValue(); if (trgWrap.isWritableProperty(targetProperty)) { Object value = srcWrap.getPropertyValue(srcProperty); trgWrap.setPropertyValue(targetProperty, value); } } } private static String convertCamelToUpperUnderscore(String camelCase) { // 将驼峰命名转换为大写下划线 StringBuilder result = new StringBuilder(); for (int i = 0; i < camelCase.length(); i++) { char c = camelCase.charAt(i); if (Character.isUpperCase(c)) { result.append('_'); result.append(Character.toUpperCase(c)); } else { result.append(Character.toUpperCase(c)); } } return result.toString(); } } ``` 然后这样调用: ```java SourceObject src = new SourceObject(); src.setUserName("John"); src.setBirthDate(LocalDate.now()); TargetObject target = new TargetObject(); PropertyUtils.copyPropertiesWithNamingConvention(src, target); ``` 但是,这种方法性能不如MapStruct,而且有反射开销。 因此,如果项目允许,建议使用MapStruct,并且如果字段多,可以写一个代码生成器(比如用MapStruct的注解处理器)或者使用Lombok配合MapStruct。 总结:根据项目需求和性能要求选择方案。 如果让我选择,我会在字段数量少时用MapStruct手动映射,字段数量多时用MapStruct配合自定义的映射规则(可能需要自己写插件或扩展),或者使用上述反射工具(如果不频繁调用)。 注意:上面的反射示例中,转换方法`convertCamelToUpperUnderscore`需要更严谨,比如多个连续大写字母和数字的情况,这里仅做简单转换。 更严谨的驼峰转下划线方法可以参考: ```java private static String convertCamelToUpperUnderscore(String camelCase) { String regex = "([a-z])([A-Z]+)"; String replacement = "$1_$2"; String underscore = camelCase.replaceAll(regex, replacement); return underscore.toUpperCase(); } ``` 但是注意:如果原属性名已经是全大写,比如"ID",那么转换后为"I_D",这显然不对。所以需要处理连续大写的情况。这比较复杂。 因此,如果属性名规范不一致,建议还是用MapStruct显式映射。 根据问题,我们选择最简单直接的方式:使用MapStruct并显式指定映射。 所以,我们给出MapStruct的解决方案。 如果项目中没有MapStruct,需要添加依赖(以Maven为例): ```xml <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> </dependencies> ``` 然后,定义映射接口,如上所示。 因此,最终答案: 使用MapStruct,为每个属性添加@Mapping注解,将源对象的驼峰属性名映射到目标对象的大写属性名。 示例接口代码: ```java @Mapper public interface ObjectMapper { ObjectMapper INSTANCE = Mappers.getMapper(ObjectMapper.class); @Mapping(source = "userName", target = "USER_NAME") @Mapping(source = "birthDate", target = "BIRTH_DATE") // 添加其他字段的映射 TargetObject toTarget(SourceObject source); } ``` 然后通过`ObjectMapper.INSTANCE.toTarget(source)`进行转换。 如果字段非常多,而且命名规范统一(驼峰转大写下划线),我们可以使用MapStruct的`@Mapping`注解的`expression`属性,但这样并不简单。因此,在字段多且命名规范的情况下,可以编写一个辅助方法生成映射代码,或者使用MapStruct的插件(如mapstruct-processor)配合自定义的命名策略(需要自己实现)。 另一种思路:在目标对象中,我们其实可以通过MapStruct的`@Mappings`注解和通配符?不能直接使用,但我们可以使用`@BeanMapping`的`ignoreByDefault`然后配合`@InheritInverseConfiguration`等,这并不直接。 因此,对于大量字段,我们可能还是需要手动写映射,或者使用代码生成工具生成映射接口。 所以,我建议对于少量字段使用上面的方式,对于大量字段,可以写一个代码生成器(例如,利用注解处理器在编译时生成映射代码)。 但是,由于问题中没有说明字段数量,我们给出一个通用的建议:使用MapStruct并显式写映射。 如果字段数量巨大,可以考虑使用反射,但要注意性能。 下面,我们给出一个基于反射的工具类,用于复制属性驼峰转大写下划线): ```java import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import java.beans.PropertyDescriptor; import java.util.HashSet; import java.util.Set; public class PropertyCopyUtil { public static void copyProperties(Object source, Object target) { if (source == null || target == null) { return; } BeanWrapper srcWrap = new BeanWrapperImpl(source); BeanWrapper trgWrap = new BeanWrapperImpl(target); PropertyDescriptor[] srcDescriptors = srcWrap.getPropertyDescriptors(); Set<String> srcProperties = new HashSet<>(); for (PropertyDescriptor pd : srcDescriptors) { if (pd.getName().equals("class")) continue; srcProperties.add(pd.getName()); } for (String srcProperty : srcProperties) { if (srcWrap.isReadableProperty(srcProperty)) { Object value = srcWrap.getPropertyValue(srcProperty); String targetProperty = convertCamelToUpperUnderscore(srcProperty); if (trgWrap.isWritableProperty(targetProperty)) { trgWrap.setPropertyValue(targetProperty, value); } } } } private static String convertCamelToUpperUnderscore(String str) { if (str == null || str.isEmpty()) { return str; } StringBuilder result = new StringBuilder(); result.append(Character.toUpperCase(str.charAt(0))); for (int i = 1; i < str.length(); i++) { char c = str.charAt(i); if (Character.isUpperCase(c)) { result.append('_'); } result.append(Character.toUpperCase(c)); } return result.toString(); } } ``` 注意:这个转换方法假设属性名是标准的驼峰(首字母小写),如果遇到连续大写,可能会转换错误(如"userID"会变成"USER_I_D")。所以,需要根据实际属性名规范调整。 因此,在实际应用中,建议明确属性命名规范,或者使用MapStruct进行明确的映射。 综上所述,根据实际场景选择方案。 §§相关问题§§ 1. 在MapStruct中如何批量映射属性名遵循特定命名规则的字段? 2. 使用反射复制属性时,如何处理属性名转换中的特殊情况(例如连续大写字母)? 3. 除了MapStruct和反射,还有哪些工具可以实现不同命名规则的属性复制? 4. 如何优化反射复制属性的性能,使其适用于高频调用场景? 5. 在分布式系统中,不同服务间的对象属性复制有哪些注意事项? </think>### Java对象属性复制:驼峰命名转全大写命名 以下是两种高效实现方案,根据需求选择: #### 方案1:使用MapStruct映射框架(推荐) ```java // 1. 引入依赖(pom.xml) <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> // 2. 定义映射器接口 @Mapper public interface ObjectConverter { @Mapping(target = "USER_NAME", source = "userName") @Mapping(target = "BIRTH_DATE", source = "birthDate") @Mapping(target = "ADDRESS_INFO", source = "addressInfo") // 添加所有字段映射... TargetObject convert(SourceObject source); } // 3. 使用映射器 SourceObject source = new SourceObject("张三", LocalDate.now(), "北京"); TargetObject target = Mappers.getMapper(ObjectConverter.class).convert(source); ``` #### 方案2:使用Spring BeanUtils + 自定义转换(无框架依赖) ```java public class PropertyMapper { public static TargetObject copyProperties(SourceObject source) { TargetObject target = new TargetObject(); BeanUtils.copyProperties(source, target, getIgnoreProperties()); return target; } private static String[] getIgnoreProperties() { return new String[]{"USER_NAME", "BIRTH_DATE", "ADDRESS_INFO" /* 所有大写属性名 */}; } } // 实体类需添加特殊setter方法 public class TargetObject { private String USER_NAME; private LocalDate BIRTH_DATE; // 添加驼峰属性接收方法 public void setUserName(String userName) { this.USER_NAME = userName.toUpperCase(); } public void setBirthDate(LocalDate birthDate) { this.BIRTH_DATE = birthDate; } // 标准getter/setter... } ``` #### 方案3:使用反射动态转换(通用性强) ```java public class PropertyCopier { public static void copy(Object source, Object target) throws Exception { Class<?> sourceClass = source.getClass(); Class<?> targetClass = target.getClass(); for (Field sourceField : sourceClass.getDeclaredFields()) { String camelName = sourceField.getName(); String upperName = camelToUpper(camelName); try { Field targetField = targetClass.getDeclaredField(upperName); sourceField.setAccessible(true); targetField.setAccessible(true); Object value = sourceField.get(source); targetField.set(target, value); } catch (NoSuchFieldException ignored) { // 字段不匹配时跳过 } } } private static String camelToUpper(String camel) { // 驼峰转全大写(保留原始大小写) return camel.toUpperCase(); } } // 使用示例 SourceObject source = new SourceObject(...); TargetObject target = new TargetObject(); PropertyCopier.copy(source, target); ``` ### 关键注意事项 1. **命名转换规则**: - 简单转换:`userName → USER_NAME` - 特殊处理:`HTTPRequest → HTTP_REQUEST`(需自定义逻辑) - 缩写词:`userID → USER_ID`(避免转换为`USER_I_D`) 2. **性能对比**: | 方案 | 首次执行 | 后续执行 | 适用场景 | |---|---|----|---| | MapStruct | 慢(生成代码) | **极快** | 高性能需求 | | Spring BeanUtils | 中等 | 快 | Spring项目 | | 反射 | 快 | 慢 | 动态字段处理 | 3. **最佳实践**: - 使用MapStruct的`@AfterMapping`处理复杂转换 - 对日期/数等特殊类型添加自定义转换器 - 使用`Enum`管理字段映射关系避免硬编码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青春1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值