阿里EasyExcel国际化解决方案,基于原本框架扩展


第八更,阿里 EasyExcel 国际化解决方案,基于原本框架扩展
项目中原本使用的是 EasyPoi,发现了性能问题,要切换到 EasyExcel,之前 EasyPoi 的国际化也是我扩展的,最近看了一两天 EasyExcel 源码,终于有思路来扩展下国际化。
使用方式,在实体类上面打上 EasyExcel 提供的注解 @ExcelProperty ,只不过 value 值 写成 占位符形式 ,匹配 i18n 文件里面的编码。

Maven依赖

	<dependencies>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.3</version>
        </dependency>
        
   </dependencies>

占位符解析器

在网上找到的一位大神提供的代码



/**
 * 占位符解析器
 *
 * @author meilin.huang
 * @version 1.0
 * @date 2018-11-13 1:42 PM
 */
public class PlaceholderResolver {
    /**
     * 默认前缀占位符
     */
    public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";

    /**
     * 默认后缀占位符
     */
    public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";

    /**
     * 默认单例解析器
     */
    private static PlaceholderResolver defaultResolver = new PlaceholderResolver();

    /**
     * 占位符前缀
     */
    private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;

    /**
     * 占位符后缀
     */
    private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;


    private PlaceholderResolver() {
    }

    private PlaceholderResolver(String placeholderPrefix, String placeholderSuffix) {
        this.placeholderPrefix = placeholderPrefix;
        this.placeholderSuffix = placeholderSuffix;
    }

    /**
     * 获取默认的占位符解析器,即占位符前缀为"${", 后缀为"}"
     *
     * @return
     */
    public static PlaceholderResolver getDefaultResolver() {
        return defaultResolver;
    }

    public static PlaceholderResolver getResolver(String placeholderPrefix, String placeholderSuffix) {
        return new PlaceholderResolver(placeholderPrefix, placeholderSuffix);
    }

    /**
     * 解析带有指定占位符的模板字符串,默认占位符为前缀:${  后缀:}<br/><br/>
     * 如:template = category:${}:product:${}<br/>
     * values = {"1", "2"}<br/>
     * 返回 category:1:product:2<br/>
     *
     * @param content 要解析的带有占位符的模板字符串
     * @param values  按照模板占位符索引位置设置对应的值
     * @return
     */
    public String resolve(String content, String... values) {
        int start = content.indexOf(this.placeholderPrefix);
        if (start == -1) {
            return content;
        }
        //值索引
        int valueIndex = 0;
        StringBuilder result = new StringBuilder(content);
        while (start != -1) {
            int end = result.indexOf(this.placeholderSuffix);
            String replaceContent = values[valueIndex++];
            result.replace(start, end + this.placeholderSuffix.length(), replaceContent);
            start = result.indexOf(this.placeholderPrefix, start + replaceContent.length());
        }
        return result.toString();
    }

    /**
     * 解析带有指定占位符的模板字符串,默认占位符为前缀:${  后缀:}<br/><br/>
     * 如:template = category:${}:product:${}<br/>
     * values = {"1", "2"}<br/>
     * 返回 category:1:product:2<br/>
     *
     * @param content 要解析的带有占位符的模板字符串
     * @param values  按照模板占位符索引位置设置对应的值
     * @return
     */
    public String resolve(String content, Object[] values) {
        return resolve(content, Stream.of(values).map(String::valueOf).toArray(String[]::new));
    }

    /**
     * 根据替换规则来替换指定模板中的占位符值
     *
     * @param content 要解析的字符串
     * @param rule    解析规则回调
     * @return
     */
    public String resolveByRule(String content, Function<String, String> rule) {
        int start = content.indexOf(this.placeholderPrefix);
        if (start == -1) {
            return content;
        }
        StringBuilder result = new StringBuilder(content);
        while (start != -1) {
            int end = result.indexOf(this.placeholderSuffix, start);
            //获取占位符属性值,如${id}, 即获取id
            String placeholder = result.substring(start + this.placeholderPrefix.length(), end);
            //替换整个占位符内容,即将${id}值替换为替换规则回调中的内容
            String replaceContent = placeholder.trim().isEmpty() ? "" : rule.apply(placeholder);
            result.replace(start, end + this.placeholderSuffix.length(), replaceContent);
            start = result.indexOf(this.placeholderPrefix, start + replaceContent.length());
        }
        return result.toString();
    }

    /**
     * 替换模板中占位符内容,占位符的内容即为map key对应的值,key为占位符中的内容。<br/><br/>
     * 如:content = product:${id}:detail:${did}<br/>
     * valueMap = id -> 1; pid -> 2<br/>
     * 经过解析返回 product:1:detail:2<br/>
     *
     * @param content  模板内容。
     * @param valueMap 值映射
     * @return 替换完成后的字符串。
     */
    public String resolveByMap(String content, final Map<String, Object> valueMap) {
        return resolveByRule(content, placeholderValue -> String.valueOf(valueMap.get(placeholderValue)));
    }

    /**
     * 根据properties文件替换占位符内容
     *
     * @param content
     * @param properties
     * @return
     */
    public String resolveByProperties(String content, final Properties properties) {
        return resolveByRule(content, placeholderValue -> properties.getProperty(placeholderValue));
    }

}

导出扩展

public class I18nCellWriteHandler implements CellWriteHandler {

    private final MessageSource messageSource;

    private final Locale locale;

    public I18nCellWriteHandler(MessageSource messageSource, Locale locale) {
        this.messageSource = messageSource;
        this.locale = locale;
    }

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
        if (isHead) {
            List<String> originHeadNames = head.getHeadNameList();
            if (CollectionUtils.isNotEmpty(originHeadNames)) {
                List<String> newHeadNames = originHeadNames.stream().
                        map(headName ->
                                PlaceholderResolver.getDefaultResolver().resolveByRule(headName,
                                        (name) -> messageSource.getMessage(name, null, locale))).
                        collect(Collectors.toList());
                head.setHeadNameList(newHeadNames);
            }
        }
    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {

    }
}

导入扩展

public abstract class I18nAnalysisListener<T> extends AnalysisEventListener<T> {

    private final MessageSource messageSource;

    private final Locale locale;

    private final Class<T> clazz;

    protected I18nAnalysisListener(MessageSource messageSource, Locale locale, Class<T> clazz) {
        this.messageSource = messageSource;
        this.locale = locale;
        this.clazz = clazz;
    }

    @Override
    public void invokeHead(Map<Integer, CellData> headMap, AnalysisContext context) {
        ReadRowHolder readRowHolder = context.readRowHolder();
        int rowIndex = readRowHolder.getRowIndex();
        int currentHeadRowNumber = context.readSheetHolder().getHeadRowNumber();
        if (currentHeadRowNumber == rowIndex + 1) {
            buildHeadAgain(context, headMap);
        }

        invokeHeadMap(ConverterUtils.convertToStringMap(headMap, context), context);
    }


    private void buildHeadAgain(AnalysisContext analysisContext, Map<Integer, CellData> headMap) {
        ExcelReadHeadProperty excelHeadPropertyData = analysisContext.readSheetHolder().excelReadHeadProperty();
        Map<Integer, Head> nowHeadMapData = excelHeadPropertyData.getHeadMap();
        // 如果 nowHeadMapData 不为空,说明头的顺序已经确定 ,不需要重新构建头
        if (MapUtils.isNotEmpty(nowHeadMapData)) {
            return;
        }
        // 框架层面把HeadMapData替换掉了,这里要重新解析拿到原始的 HeadMapData 和 ExcelContentProperty
        ExcelReadHeadProperty originExcelHeadPropertyData = new ExcelReadHeadProperty(analysisContext.currentReadHolder(), clazz,
                null, analysisContext.readWorkbookHolder().getConvertAllFiled());
        Map<Integer, Head> originHeadMapData = originExcelHeadPropertyData.getHeadMap();
        Map<Integer, ExcelContentProperty> originContentPropertyMapData = originExcelHeadPropertyData.getContentPropertyMap();
        // 下面代码就是 copy的 com.alibaba.excel.read.processor.DefaultAnalysisEventProcessor#buildHead
        Map<Integer, String> dataMap = ConverterUtils.convertToStringMap(headMap, analysisContext);
        Map<Integer, Head> tmpHeadMap = new HashMap<>(originHeadMapData.size() * 4 / 3 + 1);
        Map<Integer, ExcelContentProperty> tmpContentPropertyMap =
                new HashMap<>(originContentPropertyMapData.size() * 4 / 3 + 1);
        for (Map.Entry<Integer, Head> entry : originHeadMapData.entrySet()) {
            Head headData = entry.getValue();
            List<String> headNameList = headData.getHeadNameList();
            String headName = PlaceholderResolver.getDefaultResolver().resolveByRule(headNameList.get(headNameList.size() - 1),
                    (name) -> messageSource.getMessage(name, null, locale));
            for (Map.Entry<Integer, String> stringEntry : dataMap.entrySet()) {
                if (stringEntry == null) {
                    continue;
                }
                String headString = stringEntry.getValue();
                Integer stringKey = stringEntry.getKey();
                if (StringUtils.isEmpty(headString)) {
                    continue;
                }
                if (analysisContext.currentReadHolder().globalConfiguration().getAutoTrim()) {
                    headString = headString.trim();
                }
                if (headName.equals(headString)) {
                    headData.setColumnIndex(stringKey);
                    tmpHeadMap.put(stringKey, headData);
                    tmpContentPropertyMap.put(stringKey, originContentPropertyMapData.get(entry.getKey()));
                    break;
                }
            }
        }
        excelHeadPropertyData.setHeadMap(tmpHeadMap);
        excelHeadPropertyData.setContentPropertyMap(tmpContentPropertyMap);
    }

}

封装工厂类

public class EasyExcelI18n {

    public static ExcelWriterBuilder write(OutputStream outputStream, Class head, MessageSource messageSource, Locale locale) {
        ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder();
        excelWriterBuilder.file(outputStream);
        if (head == null) {
        throw new IllegalArgumentException("head must not be null");
    }
        excelWriterBuilder.head(head);
        excelWriterBuilder.registerWriteHandler(new I18nCellWriteHandler(messageSource, locale));
        return excelWriterBuilder;
}

    public static ExcelReaderBuilder read(InputStream inputStream, Class head, I18nAnalysisListener analysisListener) {
        ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();
        excelReaderBuilder.file(inputStream);
        if (head == null || analysisListener == null) {
            throw new IllegalArgumentException("head or analysisListener must not be null");
        }
        excelReaderBuilder.head(head);
        excelReaderBuilder.registerReadListener(analysisListener);
        return excelReaderBuilder;
    }

}

实体类

public class User {

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @ExcelProperty(value = "${user.name}")
    private String name;

    @ExcelProperty(value = "${user.age}")
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

i18n文件

resources\i18n\messages_zh_CN.properties

# 姓名
user.name = \u59D3\u540D
# 年龄
user.age = \u5E74\u9F84

resources\i18n\messages.properties

# 姓名
user.name = name
# 年龄
user.age = age

打开方式

@SpringBootApplication
public class EasyExcelI18nApplication {

    public static void main(String[] args) {
        SpringApplication.run(EasyExcelI18nApplication.class, args);
    }


//	@Bean
//	public ApplicationRunner myRunner(MessageSource messageSource){
//		return args -> {
//			List<User> userList = new ArrayList<>();
//			userList.add(new User("王xx",10));
//			userList.add(new User("李xx",11));
//			OutputStream outputStream = new FileOutputStream("D:\\用户.xlsx");
//			Locale locale = new Locale("zh-CN");
//			EasyExcelI18n.write(outputStream, User.class,messageSource,locale).sheet(1).doWrite(userList);
//
//		};
//	}

    @Bean
    public ApplicationRunner myRunner(MessageSource messageSource) {
        return args -> {

            InputStream inputStream = new FileInputStream("D:\\用户.xlsx");
            Locale locale = new Locale("zh-CN");
            List<User> userList = new ArrayList<>();
            EasyExcelI18n.read(inputStream, User.class, new I18nAnalysisListener<User>(messageSource, locale, User.class) {
                @Override
                public void invoke(User user, AnalysisContext context) {
                    userList.add(user);
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext context) {

                }
            }).sheet().doRead();

            System.out.println(userList);
        };
    }

}
GitHub 加速计划 / ea / easyexcel
24
5
下载
快速、简洁、解决大文件内存溢出的java处理Excel工具
最近提交(Master分支:2 个月前 )
c42183df Bugfix 1 年前
efa7dff6 * 重新加回 `commons-io` 1 年前
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐