如何通过单个Converter实现ModelMapper多类型转换以避免冗余代码?
解决ModelMapper类型转换的代码重复与异常处理问题
核心问题分析
- 你写的
String→Number转换器不生效,是因为ModelMapper的转换器匹配规则是精确匹配源/目标的具体类型,而你的目标字段类型是Integer/Long等Number子类,不是Number本身,所以不会触发这个泛型转换器。 - 重复代码的根源是每个转换器的核心逻辑一致,只是目标类型不同,所以可以通过封装通用转换逻辑+批量注册转换器来解决。
- 数值间转换需要额外处理范围校验,超出目标类型范围时返回
null而非抛出异常。
实现方案
1. 封装通用转换工具方法
把字符串转数值、数值间转换的核心逻辑抽成工具方法,避免重复代码:
import org.apache.commons.lang3.StringUtils; public class ConversionUtils { // String转各种数值类型,空值/格式错误/超出范围返回null @SuppressWarnings("unchecked") public static <T extends Number> T parseStringToNumber(String source, Class<T> targetType) { if (StringUtils.isBlank(source)) { return null; } // 根据目标类型清理字符串:整数去掉非数字,浮点数保留数字和小数点 String cleaned; if (Double.class.isAssignableFrom(targetType)) { cleaned = source.replaceAll("[^\\d.]", ""); // 处理多个小数点的异常情况 if (cleaned.indexOf('.') != cleaned.lastIndexOf('.')) { cleaned = cleaned.substring(0, cleaned.lastIndexOf('.')); } } else { cleaned = source.replaceAll("\\D", ""); } if (StringUtils.isBlank(cleaned)) { return null; } try { Number result; if (Integer.class.isAssignableFrom(targetType)) { long value = Long.parseLong(cleaned); if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { return null; } result = (int) value; } else if (Long.class.isAssignableFrom(targetType)) { result = Long.parseLong(cleaned); } else if (Short.class.isAssignableFrom(targetType)) { long value = Long.parseLong(cleaned); if (value > Short.MAX_VALUE || value < Short.MIN_VALUE) { return null; } result = (short) value; } else if (Byte.class.isAssignableFrom(targetType)) { long value = Long.parseLong(cleaned); if (value > Byte.MAX_VALUE || value < Byte.MIN_VALUE) { return null; } result = (byte) value; } else if (Double.class.isAssignableFrom(targetType)) { result = Double.parseDouble(cleaned); } else { return null; } return (T) result; } catch (NumberFormatException e) { return null; } } // 数值类型间转换,空值/超出范围返回null @SuppressWarnings("unchecked") public static <S extends Number, T extends Number> T convertNumber(S source, Class<T> targetType) { if (source == null) { return null; } try { Number result; if (Integer.class.isAssignableFrom(targetType)) { long value = source.longValue(); if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { return null; } result = source.intValue(); } else if (Long.class.isAssignableFrom(targetType)) { result = source.longValue(); } else if (Short.class.isAssignableFrom(targetType)) { long value = source.longValue(); if (value > Short.MAX_VALUE || value < Short.MIN_VALUE) { return null; } result = source.shortValue(); } else if (Byte.class.isAssignableFrom(targetType)) { long value = source.longValue(); if (value > Byte.MAX_VALUE || value < Byte.MIN_VALUE) { return null; } result = source.byteValue(); } else if (Double.class.isAssignableFrom(targetType)) { result = source.doubleValue(); } else { return null; } return (T) result; } catch (ClassCastException e) { return null; } } }
2. 批量注册转换器
基于工具方法,用Lambda表达式简化转换器注册,大幅减少代码量:
ModelMapper modelMapper = new ModelMapper(); // 注册String转各类数值的转换器 modelMapper.addConverter((Converter<String, Integer>) ctx -> ConversionUtils.parseStringToNumber(ctx.getSource(), Integer.class)); modelMapper.addConverter((Converter<String, Long>) ctx -> ConversionUtils.parseStringToNumber(ctx.getSource(), Long.class)); modelMapper.addConverter((Converter<String, Short>) ctx -> ConversionUtils.parseStringToNumber(ctx.getSource(), Short.class)); modelMapper.addConverter((Converter<String, Byte>) ctx -> ConversionUtils.parseStringToNumber(ctx.getSource(), Byte.class)); modelMapper.addConverter((Converter<String, Double>) ctx -> ConversionUtils.parseStringToNumber(ctx.getSource(), Double.class)); // 注册数值间转换的转换器(按需添加) modelMapper.addConverter((Converter<Integer, Short>) ctx -> ConversionUtils.convertNumber(ctx.getSource(), Short.class)); modelMapper.addConverter((Converter<Long, Integer>) ctx -> ConversionUtils.convertNumber(ctx.getSource(), Integer.class)); modelMapper.addConverter((Converter<Long, Short>) ctx -> ConversionUtils.convertNumber(ctx.getSource(), Short.class)); // 其他需要的数值转换组合同理
3. 可选优化:动态注册所有需要的转换器
如果需要处理的类型组合非常多,可以用反射动态遍历类型组合,自动注册转换器,彻底避免手动写重复代码:
import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; // 定义需要处理的数值类型 List<Class<? extends Number>> numberTypes = Arrays.asList(Integer.class, Long.class, Short.class, Byte.class, Double.class); // 注册String转所有数值类型的转换器 for (Class<? extends Number> targetType : numberTypes) { modelMapper.addConverter(new Converter<String, Number>() { @Override public Number convert(MappingContext<String, Number> context) { return ConversionUtils.parseStringToNumber(context.getSource(), targetType); } // 重写getType方法,指定具体的目标类型,让ModelMapper能精确匹配 @Override public Type getType() { return new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return new Type[]{String.class, targetType}; } @Override public Type getRawType() { return Converter.class; } @Override public Type getOwnerType() { return null; } }; } }); } // 注册所有数值间的转换组合 for (Class<? extends Number> sourceType : numberTypes) { for (Class<? extends Number> targetType : numberTypes) { if (sourceType != targetType) { modelMapper.addConverter(new Converter<Number, Number>() { @Override public Number convert(MappingContext<Number, Number> context) { return ConversionUtils.convertNumber(context.getSource(), targetType); } @Override public Type getType() { return new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return new Type[]{sourceType, targetType}; } @Override public Type getRawType() { return Converter.class; } @Override public Type getOwnerType() { return null; } }; } }); } } }
关键说明
- 重写
Converter.getType()方法是动态注册的核心:它告诉ModelMapper当前转换器对应的具体源/目标类型,确保能精确匹配字段类型。 - 工具方法中已经处理了空值、格式错误、范围溢出等情况,所有异常场景都返回
null,符合你的需求。 - 若不需要所有数值组合的转换,可手动注册需要的类型对,避免不必要的转换器。
内容的提问来源于stack exchange,提问作者Rafael Lima
相关产品推荐
相关产品推荐

