背景:近日,发现了一个解析excel的springboot-starter(pom依赖如下),挺好玩,使用尤其简单,复刻了下代码,了解一下
<dependency>
<groupId>com.pig4cloud.excel</groupId>
<artifactId>excel-spring-boot-starter</artifactId>
<version>0.5.0</version>
</dependency>
上述spring-boot-starter支持excel的导入导出。
先附上总的代码仓:
https://gitee.com/kwins/excelhttps://gitee.com/kwins/excel
第一部分,通过复刻一下excel的导入功能来了解基本知识
pom如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kwin.reqexcel</groupId>
<artifactId>reqexcel-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>reqexcel-spring-boot-starter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<poi.version>4.1.2</poi.version>
<easypoi.version>2.2.10</easypoi.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--引入AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easypoi.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
首先,最核心的代码实现如下,该部分代码实现了HandlerMethodArgumentResolver接口,该接口,顾名思义,是对handlerMethod的参数部分进行解析
这部分代码,就是对使用了RequestExcel注解的参数进行解析,从HttpServletRequest中获取到文件流,然后使用EasyExcel进行解析,生成对象列表并返回
/**
* @author : wangkai
* @version V1.0
* @Package com.kwin.reqexcel.filter
* @Description:
* @date 2021年10月19日 12:28
**/
@Slf4j
public class ReuqestExcelArgumentResolver implements HandlerMethodArgumentResolver {
/**
* 判断参数是否接收该解析器处理
* @param parameter
* @return
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestExcel.class);
}
/**
* 解析参数
* @param parameter
* @param mavContainer
* @param webRequest
* @param binderFactory
* @return
* @throws Exception
*/
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
if(!List.class.isAssignableFrom(paramType)) {
throw new IllegalArgumentException(
"Excel upload request resolver error, @RequestExcel parameter is not List " + paramType);
}
RequestExcel requestExcel = parameter.getParameterAnnotation(RequestExcel.class);
assert requestExcel != null;
Class<? extends ListAnalysisEventListener<?>> readListenerClass = requestExcel.readListener();
ListAnalysisEventListener<?> readListener = BeanUtils.instantiateClass(readListenerClass);
// 获取文件流
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
assert request != null;
InputStream inputStream;
if(request instanceof MultipartRequest) {
MultipartFile file = ((MultipartRequest) request).getFile(requestExcel.fileName());
assert file != null;
inputStream = file.getInputStream();
} else {
inputStream = request.getInputStream();
}
// 获取目标类型
Class<?> excelModelClass = ResolvableType.forMethodParameter(parameter).getGeneric(0).resolve();
// 这里需要指定读用哪个 class 去读,然后读取第一个 sheet 文件流会自动关闭
EasyExcel.read(inputStream, excelModelClass, readListener)
.registerConverter(LocalDateStringConverter.INSTANCE)
.registerConverter(LocalDateTimeStringConverter.INSTANCE).ignoreEmptyRow(requestExcel.ignoreEmptyRow())
.sheet().doRead();
// 校验失败的数据处理 交给 BindResult
WebDataBinder dataBinder = binderFactory.createBinder(webRequest, readListener.getErrors(), "excel");
ModelMap model = mavContainer.getModel();
model.put(BindingResult.MODEL_KEY_PREFIX + "excel", dataBinder.getBindingResult());
return readListener.getList();
}
}
这个HandlerMethodArgumentResolver的使用需要将其注册到RequestMappingHandlerAdapter的参数解析链中,实现如下
/**
* @author : wangkai
* @version V1.0
* @Package com.kwin.reqexcel
* @Description:
* @date 2021年10月19日 13:50
**/
@RequiredArgsConstructor
@Configuration
public class ExcelHandlerConfiguration {
private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;
/**
* 追加 Excel 请求处理器 到 springmvc 中
*/
@PostConstruct
public void addRequestExcelArgumentResolver() {
List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> resolverList = new ArrayList<>();
resolverList.add(new ReuqestExcelArgumentResolver());
resolverList.addAll(argumentResolvers);
requestMappingHandlerAdapter.setArgumentResolvers(resolverList);
}
}
这时,可能有小伙伴就奇怪了,这个Configuration配置类springboot是如何加载的呢?毕竟我们SpringbootApplication启动类并没有扫描到我们当前目录,这点就要了解一下springboot的bean加载机制了,springboot会扫描每个jar包下META-INF/spring.factories文件,然后做一些动作,如:会对 org.springframework.boot.autoconfigure.EnableAutoConfiguration 指定的类列表进行加载到bean容器池中。这一点也是我们需要用到的地方
我们再resources目录下创建META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.kwin.reqexcel.ExcelHandlerConfiguration
其它一些参数解析及校验的工具类我就不赘述了,大家可以翻看代码,比较简单
验证:
创建一个springboot项目,将上述上述项目打包后引入,
<dependency>
<groupId>com.kwin.reqexcel</groupId>
<artifactId>reqexcel-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
创建一个dto Test
/**
* @author : wangkai
* @version V1.0
* @Package com.example.demoexcel.dto
* @Description:
* @date 2021年10月19日 11:37
**/
@Data
public class Test {
@ExcelProperty("hhaa")
private String ha;
@NotBlank(message = "用户名不能为空")
@ExcelProperty("用户名")
private String username;
}
创建测试controller
/**
* @author : wangkai
* @version V1.0
* @Package com.kwin.reqexceltest.controller
* @Description:
* @date 2021年10月19日 13:59
**/
@Slf4j
@RestController
public class TestController {
@PostMapping("test")
public String test(@RequestExcel(fileName = "file") List<Test> list) {
return JSON.toJSONString(list);
}
}
excel表格准备如下:
启动项目,通过postman进行测试,结果如下:
下载excel的方法同样简单,和上面原理一致,通过实现HandlerMethodReturnValueHandler接口来对返回的list对象进行写入excel
https://gitee.com/kwins/excel/tree/master/excel-spring-boot-starter
这边就不细述了,没有什么了不得的东西。
测试代码如下:
引入项目依赖:
<dependency>
<groupId>com.kwin.excel</groupId>
<artifactId>excel-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
实体:
@Data
public class Test {
@ExcelProperty("hhaa")
private String ha;
@NotBlank(message = "用户名不能为空")
@ExcelProperty("用户名")
private String username;
}
测试controller,使用了ResponseExcel注解
/**
* @author : wangkai
* @version V1.0
* @Package com.example.demoexcel.controller
* @Description:
* @date 2021年10月19日 11:34
**/
@RestController
public class TestController {
@GetMapping("test2")
@ResponseExcel
public List<Test> test2() {
List<Test> list = new ArrayList<>();
Test test = null;
for(int i = 0 ; i < 10; i++) {
test = new Test();
test.setHa("ha" + i);
test.setUsername("name" + i);
list.add(test);
}
return list;
}
}
启动项目,调用http://localhost:8080/test2 接口
结果如下:
打开文件