依赖包poi-ooxml-lite-5.2.3.jar和ooxml-schemas-1.1.jar有全限定名相同的interface:org.openxmlformats.schemas.wordprocessingml.x2006.main.DocumentDocument,导致出现如下错误:
java.lang.NoSuchFieldError: Factory
at org.apache.poi.xwpf.usermodel.XWPFDocument.onDocumentRead(XWPFDocument.java:197)
at org.apache.poi.ooxml.POIXMLDocument.load(POIXMLDocument.java:169)
at org.apache.poi.xwpf.usermodel.XWPFDocument.<init>(XWPFDocument.java:146)
at com.duxiu.readfile.operate.AbstractReadFile.readDocx(AbstractReadFile.java:375)
at com.duxiu.readfile.operate.AbstractReadFile.readFileByTypeV1(AbstractReadFile.java:111)
注:报错的地方在poi-ooxml-5.2.3.jar内部的XWPFDocument.java中。
目前进度:
1.确定是类加载器只加载了ooxml-schemas-1.1.jar中的DocumentDocument.class,而没有加载poi-ooxml-lite-5.2.3.jar中的DocumentDocument.class,导致DocumentDocument.class缺少方法(ooxml-schemas-1.1.jar中的DocumentDocument.class没有Factory,poi-ooxml-lite-5.2.3.jar中的DocumentDocument.class有Factory);
2.项目中poi-ooxml-lite-5.2.3.jar是直接引的依赖包,而ooxml-schemas-1.1.jar是引用的其他依赖包间接引入的依赖包。已经查询过优先级:先引用的大于后引用的、直接引用的大于间接引用的。所以尝试过:在配置Project Settings -> Moudles -> depency中移动依赖的位置,让poi-ooxml-5.2.3.jar以及相关jar包移动到最前方,依赖包含ooxml-schemas-1.1.jar的依赖以及ooxml-schemas-1.1.jar自己放在后方、pom.xml中也是同样处理,结果还是没用,依旧报NoSuchFieldError。
3.自定义类加载器加载正确的类:(由于接触这块儿不多,借鉴了网上的写法)
/**
* 自定义的类加载器
*/
public class CustomizeClassLoader extends ClassLoader{
private String classPath;
public CustomizeClassLoader(String classPath) {
this.classPath = classPath;
}
/**
* 重写findClass方法
*
* 如果不会写, 可以参考URLClassLoader中是如何加载AppClassLoader和ExtClassLoader的
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadBytes(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private byte[] loadBytes(String name) throws Exception {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
name = name.replace(".", "\\");
// 如果path是jar包的路径,那么上述方法不能加载成功,抛出异常后执行此处的方法。
JarFile jar = new JarFile(this.classPath);
is = jar.getInputStream(jar.getEntry(name.replace("\\", "/") + ".class"));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception ex) {
throw ex;
} finally {
try {
if(is != null)
is.close();
if(baos != null)
baos.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
return data;
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
/**
* 直接执行findClass()...什么意思呢? 首先会使用自定义类加载器加载类, 不在向上委托, 直接由
* 自己执行
*
* jvm自带的类还是需要由引导类加载器自动加载
*/
if (!name.equals("org.openxmlformats.schemas.wordprocessingml.x2006.main.DocumentDocument")) {
c = this.getParent().loadClass(name);
} else {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
public static void main(String[] args) throws Exception {
// 获取所有TestUse得到路径
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
// Enumeration<URL> urls = classloader.getResources("org/openxmlformats/schemas/wordprocessingml/x2006/main/DocumentDocument.class");
// Enumeration<URL> urls = classloader.getResources("com/duxiu/readfile/utils/ReadFileUtils.class");
Enumeration<URL> urls = classloader.getResources("org/apache/poi/xwpf/usermodel/XWPFDocument.class");
// 循环加载两个jar包下的
while (urls.hasMoreElements()) {
URL url = (URL) urls.nextElement();
String fullPath = url.getPath();
System.out.println(fullPath);
String[] strs = fullPath.split("!");
// 获取文件的路径以及文件名
String path = strs[0].replace("file:/","");
String fileName = strs[1].substring(1).replace(".class", "").replace("/", ".");
CustomizeClassLoader loader = new CustomizeClassLoader(path);
// 加载类
Class<?> clazz = loader.loadClass(fileName);
// 反射创建实体
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod("方法名", null);
// 执行
method.invoke(obj, null);
}
}
}
结果:能够使用自定义类加载器成功加载XWPFDocument.class,未成功加载DocumentDocument.class,也许是因为DocumentDocument.class是接口?
4.搜索到:如果当前类用的类加载器是AClassLoader,那么它内部的类也会使用AClassLoader。所以,在SpringMVC项目调用涉及到依赖的方法中,使用3中的自定义类加载器,那么到 DocumentDocument.Factory.parse(stream, DEFAULT_XML_OPTIONS);这一行也会使用自定义类加载器去加载DocumentDocument.class,如下:
public static String readFileNotFilter(String filePath){
String articleContent = "";
try{
ClassLoader classloader = Thread.currentThread().getContextClassLoader(); // ParallelWebappClassLoader加载器
Enumeration<URL> urls = classloader.getResources("com/duxiu/readfile/utils/ReadFileUtils.class");
if (urls.hasMoreElements()) {
URL url = (URL) urls.nextElement();
String fullPath = url.getPath();
System.out.println(fullPath);
String[] strs = fullPath.split("!");
// 获取文件的路径以及文件名
String path = strs[0].replace("file:/", "");
String fileName = strs[1].substring(1).replace(".class", "").replace("/", ".");
CustomizeClassLoader loader = new CustomizeClassLoader(path);
// 加载类
Class<?> clazz = loader.loadClass(fileName);
// 反射创建实体
Object obj = clazz.getDeclaredConstructor().newInstance();
articleContent = ((ReadFileUtils)obj).readFileByTypeV1(filePath, null);
}
}catch (Exception e){
e.printStackTrace();
}
// 原引用了依赖的地方
// articleContent = ReadFileUtils.readFileByTypeV1(filePath,null);
articleContent = OperateContent.removeContent(articleContent, filePath, null);
return articleContent;
}
5.如果4中能够取出对应的类的话,预计会遍历两个相同的DocumentDocument.class,取出正确的那一个。项目中自定义类加载器需要加载的类:public class ReadFileUtils extends AbstractReadFile {.}。目前自定义类加载器的main中加载ReadFileUtils.class失败,项目代码中加载ReadFileUtils.class也同样失败。
触发错误的条件:
依赖包poi-ooxml-lite-5.2.3.jar和ooxml-schemas-1.1.jar;
public static String readDocx(String path, ArticleProp p) {
OPCPackage opcPackage = null;
try {
opcPackage = POIXMLDocument.openPackage(path);
XWPFDocument document = new XWPFDocument(opcPackage); // 触发错误的行
List<XWPFHeader> headers = document.getHeaderList();
} finally {
if (opcPackage != null) {
try {
opcPackage.close();
} catch (Exception var29) {
LOGGER.error("not affect the use--->AbstractReadFile--->readDocx()-->opcPackage close(): path={}, error msg={}, ", path, var29.getMessage());
opcPackage = null;
}
}
}
}
手上太忙了,很多地方来不及仔细思考,所以打算先用maven-shade-plugin给重名的部分再重新取一个不重复的名字。
但是,还是对能否使用类加载器解决保持疑问,如果各位有能够正确执行的类加载器解法务必告知~,越关键越详细越好,必有重金酬谢。