What causes this: java.lang.NoSuchMethodError: org.apache.poi.ss.usermodel.Workbook.sheetIterator()Ljava/ut‌​il/Iterator;

What causes this: java.lang.NoSuchMethodError: org.apache.poi.ss.usermodel.Workbook.sheetIterator()Ljava/ut‌​il/Iterator;

我很难找出这个错误的原因:

org.apache.poi.ss.usermodel.Workbook.sheetIterator()Ljava/util/Iterator;

我有 JAX-WS 服务,使用 POI 解析 Excel 文件。服务在 Weblogic 服务器上运行。这是 Weblogic 响应:

The selected operation convert could not be invoked.
A fault occurred while invoking the webservice operation. The fault is : <ns0:Fault xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.w3.org/2003/05/soap-envelope">
<faultcode>ns0:Server</faultcode>
<faultstring>org.apache.poi.ss.usermodel.Workbook.sheetIterator()Ljava/util/Iterator;</faultstring>
</ns0:Fault>
oracle.sysman.emInternalSDK.webservices.util.SoapTestException: Client received SOAP Fault from server : org.apache.poi.ss.usermodel.Workbook.sheetIterator()Ljava/util/Iterator;

奇怪的是这段代码在我的 PC 上工作,在 Weblogic 11g 上工作,但在 Weblogic 12c 上不工作

@WebService
public class Excel2XMLConverter {
    @WebMethod
    public @WebResult(name = "convertedData") String convert(@WebParam(name = "excelData") byte[] data) throws Exception{

        System.setProperty("org.apache.poi.util.POILogger", "org.apache.poi.util.NullLogger");

        BufferedInputStream bfs = new BufferedInputStream(new ByteArrayInputStream(data));

        Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(data));
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.newDocument();
        doc.createElementNS("http://namespace.org", "wb:workbook");
        Element workbookElement = doc.createElementNS("http://namespace.org", "workbook");
        workbookElement.setPrefix("wb");
        doc.appendChild(workbookElement);


        for(Iterator<Sheet> i = wb.sheetIterator(); i.hasNext();){
            Element sheetElement = doc.createElementNS("http://namespace.org", "sheet");
            sheetElement.setPrefix("wb");
            workbookElement.appendChild(sheetElement);
            Sheet sheet = i.next();        

            for(Iterator<Row> j = sheet.rowIterator(); j.hasNext(); ){

                Row row = j.next();
                Element rowElement = doc.createElementNS("http://namespace.org", "row");
                rowElement.setPrefix("wb");
                sheetElement.appendChild(rowElement);

                for(Iterator<Cell> k = row.cellIterator(); k.hasNext(); ){            
                    Cell cell = k.next();       
                    cell.setCellType(CellType.STRING);
                    Element cellElement = doc.createElementNS("http://namespace.org", "cell");
                    cellElement.setPrefix("wb");
                    cellElement.setAttribute("value", cell.getStringCellValue());
                    rowElement.appendChild(cellElement);
                }        
            }            
        }


        StringWriter sw = new StringWriter();
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.transform(new DOMSource(doc), new StreamResult(sw));


        return  sw.toString();
    }

无论如何,我认为这个错误与代码无关,因为它仅在 Weblogic 12c 上不起作用。另外,我想知道这种类型的错误通常意味着什么。

当 JVM 加载一个 class 并尝试将其 link 加载到第二个 class 时,会发生此异常。第一个 class 的代码是针对与加载的版本不同的第二个 class 版本编译的。具体来说,它期望第二个 class 中的某些方法具有一个签名,但它不存在具有该签名的方法。

简而言之,您要么针对错误版本的 POI 进行了编译,要么在部署中使用/包含了错误的 POI JAR 文件。

我认为您的类路径中有旧版本的 POI,它可能与也在类路径中的新版本冲突,或者新版本根本不在类路径中。在此提交中添加了 Workbook#sheetIterator():https://github.com/apache/poi/commit/9647b62d1a13719b51a23e25fc508788d611732b 并且自版本 3.13

起可用

您必须确保您的 Weblogic 部署中的类路径中没有旧版本的 POI。这包括您的 WAR 文件和任何 Weblogic 系统库文件夹,无论它们是什么。

感谢大家的帮助。那确实是类加载器问题。您可以通过以下方式找到:

Add the java options verbose:class in startup file. For eg. Use the below entry in startWebLogic.sh file (just above the start of weblogic server) to add the verbose:class. It's not necessary to add it in startWebLogic.sh file. Any location is fine. Please make sure it's getting picked up during server startup.

export JAVA_OPTIONS="${JAVA_OPTIONS} -verbose:class"

After server reboot, stdout will contain information about how classes are loaded.

就我而言,有人将旧的 POI 库放入 /oracle//user_projects/domains/%domain_name%/lib。 此目录中的 JAR 会自动添加到 CLASSPATH。

但这还不是全部。由于 Weblogic 使用 Apache Commons Net,因此该库也存储在服务器上:

[Loaded org.apache.commons.net.SocketClient from file:/oracle/<server_name>/wlserver/modules/commons-net.commons-net.jar]
[Loaded org.apache.commons.net.ftp.FTP from file:/oracle/<server_name>/wlserver/modules/commons-net.commons-net.jar]
[Loaded org.apache.commons.net.ftp.FTPClient from file:/oracle/<server_name>/wlserver/modules/commons-net.commons-net.jar]
[Loaded org.apache.commons.net.ftp.FTPHTTPClient from file:/oracle/<server_name>/user_projects/domains/<domain_name>/servers/soa_server1/dc/soa_02656c56-2df4-449a-b79e-ae2f074f34a1/SCA-INF/lib/commons-net-3.5.jar]

因此,虽然 FTPHTTPClient 取自修订版 3.5,但 SocketClient 有点旧,因此 FTPHTTPClient 引用的代码不存在(尚未) SocketClient 方法。在这种情况下降级有点帮助:)