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

如何通过单个Converter实现ModelMapper多类型转换以避免冗余代码?

解决ModelMapper类型转换的代码重复与异常处理问题

核心问题分析

  1. 你写的String→Number转换器不生效,是因为ModelMapper的转换器匹配规则是精确匹配源/目标的具体类型,而你的目标字段类型是Integer/Long等Number子类,不是Number本身,所以不会触发这个泛型转换器。
  2. 重复代码的根源是每个转换器的核心逻辑一致,只是目标类型不同,所以可以通过封装通用转换逻辑+批量注册转换器来解决。
  3. 数值间转换需要额外处理范围校验,超出目标类型范围时返回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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 19:44:54