参考博客:
https://blog.csdn.net/qq_34988540/article/details/83050187
https://blog.csdn.net/breakaway_01/article/details/103895099
前言
最近由于JavaWeb期末作品的需求,在SSM项目中引入了EasyPoi,用于将订单数据导出为Excel。说一下遇到的两个坑
手动导入jar包
由于项目是和组员共同编码的,一开始就用的手动导入jar包,没有使用maven。EasyPoi官方文档并没有说手动导入应该包含哪些jar包,报错java.lang.NoClassDefFoundError,显然是缺少jar包。
在Eclipse,手动导入jar包,是没有办法追踪源码的。看报错我并不知道缺少了哪个jar包。
后来没办法,我就新建了一个maven项目,使用maven引入EasyPoi,然后导出为war包。然后将war包解压,发现 EasyPoi 3.2.0一共需要导入下面 jar包
commons-lang3-3.2.1.jar
guava-16.0.1.jar
easypoi-base-3.2.0.jar
easypoi-annotation-3.2.0.jar
easypoi-web-3.2.0.jar
图片导出报错ArrayIndexOutOfBoundsException
至于原因,前言中的参考博客中,讲的非常详细了,这里我说说,在 SSM 项目中,如何替换EasyPoi的默认配置,从而解决路径解析问题。
package easymall.component;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import javax.imageio.ImageIO;
import org.apache.commons.io.IOUtils;
import com.google.common.cache.CacheLoader;
import cn.afterturn.easypoi.cache.manager.POICacheManager;
/**
* 自定义EasyPoiFixedCacheLoader,以替换EasyPoi的默认CacheLoader实现
* 解决图片路径解析的bug
* 参考博客:https://blog.csdn.net/qq_34988540/article/details/83050187
*
* @author passerbyYSQ
* @date 2020-12-29 15:26:04
*/
public class EasyPoiFixedCacheLoader extends CacheLoader<String, byte[]> {
@Override
public byte[] load(String imagePath) throws Exception {
InputStream is = POICacheManager.getFile(imagePath);
BufferedImage bufferImg = ImageIO.read(is);
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
try {
ImageIO.write(bufferImg,
// lastIndexOf 为修改源码的地方
imagePath.substring(imagePath.lastIndexOf(".") + 1, imagePath.length()),
byteArrayOut);
return byteArrayOut.toByteArray();
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(byteArrayOut);
}
}
}
package easymall.component;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.afterturn.easypoi.cache.manager.FileLoaderImpl;
import cn.afterturn.easypoi.cache.manager.IFileLoader;
/**
* 解决EasyPoi网络图片下载并导出的问题
* 参考博客:https://blog.csdn.net/breakaway_01/article/details/103895099
*
* @author passerbyYSQ
* @date 2020-12-29 15:33:22
*/
public class EasyPoiFixedFileLoaderImpl implements IFileLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(FileLoaderImpl.class);
@Override
public byte[] getFile(String url) {
InputStream fileis = null;
ByteArrayOutputStream baos = null;
try {
//判断是否是网络地址
if (url.startsWith("http")) {
URL urlObj = new URL(url);
URLConnection urlConnection = urlObj.openConnection();
urlConnection.setConnectTimeout(3 * 1000);
urlConnection.setReadTimeout(60 * 1000);
urlConnection.setDoInput(true);
urlConnection.setRequestProperty("Accept-Charset", "UTF-8");
fileis = urlConnection.getInputStream();
} else {
//先用绝对路径查询,再查询相对路径
try {
fileis = new FileInputStream(url);
} catch (FileNotFoundException e) {
//获取项目文件
fileis = FileLoaderImpl.class.getClassLoader().getResourceAsStream(url);
// if (fileis == null) {
// //最后再拿相对文件路径
// String path = PoiPublicUtil.getWebRootPath(url);
// fileis = new FileInputStream(path);
// }
}
}
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = fileis.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
baos.flush();
return baos.toByteArray();
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(fileis);
IOUtils.closeQuietly(baos);
}
LOGGER.error(fileis + "这个路径文件没有找到,请查询");
return null;
}
}
tomcat启动时会读取web.xml文件的配置,初始化我们的Spring容器,在Spring容器初始化完成之后,我们可以进行“替换”操作,修复EasyPoi的bug。因此我们需要自定义自己的ContextLoaderListener
package easymall.component;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContextEvent;
import org.springframework.web.context.ContextLoaderListener;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import cn.afterturn.easypoi.cache.ImageCache;
import cn.afterturn.easypoi.cache.manager.POICacheManager;
/**
* @author passerbyYSQ
* @date 2020-12-29 15:15:41
*/
public class MyContextLoaderListener extends ContextLoaderListener {
@Override
public void contextInitialized(ServletContextEvent event) {
super.contextInitialized(event); // 不能去掉
// 容器初始化完成后,替换EasyPoi的ImageCache
// 以解决EasyPoi源码中导出本地图片的路径解析的bug
EasyPoiFixedCacheLoader cacheLoader = new EasyPoiFixedCacheLoader();
LoadingCache<String, byte[]> loadingCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.DAYS)
.maximumSize(2000).build(cacheLoader);
// 替换
ImageCache.setLoadingCache(loadingCache);
// 替换
EasyPoiFixedFileLoaderImpl fileLoader = new EasyPoiFixedFileLoaderImpl();
POICacheManager.setFileLoder(fileLoader);
System.out.println("容器初始化完毕!!!");
}
}
web.xml
<!-- 指定一ContextLoaderListener方式启动Spring容器
原本:org.springframework.web.context.ContextLoaderListener
修改后:easymall.component.MyContextLoaderListener(自定义的ContextLoaderListener的路径)
-->
<listener>
<listener-class>
easymall.component.MyContextLoaderListener
</listener-class>
</listener>