阿里EasyExcel国际化解决方案,基于原本框架扩展
easyexcel
快速、简洁、解决大文件内存溢出的java处理Excel工具
项目地址:https://gitcode.com/gh_mirrors/ea/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);
};
}
}
快速、简洁、解决大文件内存溢出的java处理Excel工具
最近提交(Master分支:2 个月前 )
c42183df
Bugfix 1 年前
efa7dff6 * 重新加回 `commons-io`
1 年前
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)