From: markt Date: Wed, 20 Apr 2011 11:28:53 +0000 (+0000) Subject: Switch JAR scanning to use JarInputStream rather JarFile for significant startup... X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=3b1c69f439994b71c3ec1cbd8a295c9947ce99b1;p=tomcat7.0 Switch JAR scanning to use JarInputStream rather JarFile for significant startup performance improvements git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1095367 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/java/org/apache/catalina/startup/ContextConfig.java b/java/org/apache/catalina/startup/ContextConfig.java index da468b92e..ef2d1d066 100644 --- a/java/org/apache/catalina/startup/ContextConfig.java +++ b/java/org/apache/catalina/startup/ContextConfig.java @@ -44,8 +44,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.zip.ZipEntry; +import java.util.jar.JarInputStream; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; @@ -1376,17 +1375,28 @@ public class ContextConfig for (WebXml fragment : fragments) { URL url = fragment.getURL(); - JarFile jarFile = null; + JarInputStream jarInputStream = null; InputStream is = null; ServletContainerInitializer sci = null; try { if ("jar".equals(url.getProtocol())) { - JarURLConnection conn = + JarURLConnection jarConn = (JarURLConnection) url.openConnection(); - jarFile = conn.getJarFile(); - ZipEntry entry = jarFile.getEntry(SCI_LOCATION); + URL resourceURL = jarConn.getJarFileURL(); + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + + jarInputStream = + new JarInputStream(resourceConn.getInputStream()); + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { + if (SCI_LOCATION.equals(entry.getName())) { + break; + } + entry = jarInputStream.getNextJarEntry(); + } if (entry != null) { - is = jarFile.getInputStream(entry); + is = jarInputStream; } } else if ("file".equals(url.getProtocol())) { String path = url.getPath(); @@ -1412,9 +1422,9 @@ public class ContextConfig // Ignore } } - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.close(); } catch (IOException e) { // Ignore } @@ -1504,14 +1514,24 @@ public class ContextConfig protected void processResourceJARs(Set fragments) { for (WebXml fragment : fragments) { URL url = fragment.getURL(); - JarFile jarFile = null; + JarInputStream jarInputStream = null; try { // Note: Ignore file URLs for now since only jar URLs will be accepted if ("jar".equals(url.getProtocol())) { - JarURLConnection conn = + JarURLConnection jarConn = (JarURLConnection) url.openConnection(); - jarFile = conn.getJarFile(); - ZipEntry entry = jarFile.getEntry("META-INF/resources/"); + URL resourceURL = jarConn.getJarFileURL(); + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + jarInputStream = + new JarInputStream(resourceConn.getInputStream()); + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { + if ("META-INF/resources/".equals(entry.getName())) { + break; + } + entry = jarInputStream.getNextJarEntry(); + } if (entry != null) { context.addResourceJarUrl(url); } @@ -1520,9 +1540,9 @@ public class ContextConfig log.error(sm.getString("contextConfig.resourceJarFail", url, context.getName())); } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.close(); } catch (IOException e) { // Ignore } @@ -1780,50 +1800,42 @@ public class ContextConfig protected void processAnnotationsJar(URL url, WebXml fragment) { - JarFile jarFile = null; + JarInputStream jarInputStream = null; try { URLConnection urlConn = url.openConnection(); - JarURLConnection jarUrlConn; + JarURLConnection jarConn; if (!(urlConn instanceof JarURLConnection)) { // This should never happen sm.getString("contextConfig.jarUrl", url); return; } - jarUrlConn = (JarURLConnection) urlConn; - jarUrlConn.setUseCaches(false); - jarFile = jarUrlConn.getJarFile(); - - Enumeration jarEntries = jarFile.entries(); - while (jarEntries.hasMoreElements()) { - JarEntry jarEntry = jarEntries.nextElement(); - String entryName = jarEntry.getName(); + jarConn = (JarURLConnection) urlConn; + jarConn.setUseCaches(false); + URL resourceURL = jarConn.getJarFileURL(); + URLConnection resourceConn = resourceURL.openConnection(); + + jarInputStream = new JarInputStream(resourceConn.getInputStream()); + + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { + String entryName = entry.getName(); if (entryName.endsWith(".class")) { - InputStream is = null; try { - is = jarFile.getInputStream(jarEntry); - processAnnotationsStream(is, fragment); + processAnnotationsStream(jarInputStream, fragment); } catch (IOException e) { log.error(sm.getString("contextConfig.inputStreamJar", entryName, url),e); - } finally { - if (is != null) { - try { - is.close(); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - } - } } } } } catch (IOException e) { log.error(sm.getString("contextConfig.jarFile", url), e); } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.close(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } @@ -2302,45 +2314,48 @@ public class ContextConfig private Map fragments = new HashMap(); @Override - public void scan(JarURLConnection urlConn) throws IOException { + public void scan(JarURLConnection jarConn) throws IOException { - JarFile jarFile = null; - InputStream stream = null; + // JarURLConnection#getJarFile() creates temporary copies of the JAR + // if the underlying resource is not a file URL. That can be slow so + // the InputStream for the resource is used + URL resourceURL = jarConn.getJarFileURL(); + + JarInputStream jarInputStream = null; WebXml fragment = new WebXml(); try { - urlConn.setUseCaches(false); - jarFile = urlConn.getJarFile(); - JarEntry fragmentEntry = - jarFile.getJarEntry(FRAGMENT_LOCATION); - if (fragmentEntry == null) { + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + jarInputStream = + new JarInputStream(resourceConn.getInputStream()); + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { + if (FRAGMENT_LOCATION.equals(entry.getName())) { + break; + } + entry = jarInputStream.getNextJarEntry(); + } + + if (entry == null) { // If there is no web.xml, normal JAR no impact on // distributable fragment.setDistributable(true); } else { - stream = jarFile.getInputStream(fragmentEntry); InputSource source = new InputSource( - urlConn.getJarFileURL().toString() + - "!/" + FRAGMENT_LOCATION); - source.setByteStream(stream); + resourceURL.toString() + "!/" + FRAGMENT_LOCATION); + source.setByteStream(jarInputStream); parseWebXml(source, fragment, true); } } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - } - } - if (stream != null) { - try { - stream.close(); + jarInputStream.close(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } } - fragment.setURL(urlConn.getURL()); + fragment.setURL(jarConn.getURL()); if (fragment.getName() == null) { fragment.setName(fragment.getURL().toString()); } diff --git a/java/org/apache/catalina/startup/TldConfig.java b/java/org/apache/catalina/startup/TldConfig.java index 4f2f9e337..1c5fdfaa7 100644 --- a/java/org/apache/catalina/startup/TldConfig.java +++ b/java/org/apache/catalina/startup/TldConfig.java @@ -21,15 +21,15 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.JarEntry; -import java.util.jar.JarFile; import javax.servlet.ServletContext; import javax.servlet.descriptor.TaglibDescriptor; @@ -44,6 +44,7 @@ import org.apache.tomcat.JarScannerCallback; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.digester.Digester; import org.apache.tomcat.util.res.StringManager; +import org.apache.tomcat.util.scan.NonClosingJarInputStream; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -377,9 +378,10 @@ public final class TldConfig implements LifecycleListener { log.trace(sm.getString("tldConfig.webxmlAdd", resourcePath, descriptor.getTaglibURI())); } + InputStream stream = null; try { - InputStream stream = context.getServletContext( - ).getResourceAsStream(resourcePath); + stream = context.getServletContext().getResourceAsStream( + resourcePath); XmlErrorHandler handler = tldScanStream(stream); handler.logFindings(log, resourcePath); taglibUris.add(descriptor.getTaglibURI()); @@ -387,6 +389,14 @@ public final class TldConfig implements LifecycleListener { } catch (IOException ioe) { log.warn(sm.getString("tldConfig.webxmlFail", resourcePath, descriptor.getTaglibURI()), ioe); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Throwable t) { + ExceptionUtils.handleThrowable(t); + } + } } } } @@ -494,34 +504,41 @@ public final class TldConfig implements LifecycleListener { * Scans the given JarURLConnection for TLD files located in META-INF * (or a sub-directory of it). * - * @param conn The JarURLConnection to the JAR file to scan + * @param jarConn The JarURLConnection to the JAR file to scan * * Keep in sync with o.a.j.comiler.TldLocationsCache */ - private void tldScanJar(JarURLConnection conn) { + private void tldScanJar(JarURLConnection jarConn) { - JarFile jarFile = null; + // JarURLConnection#getJarFile() creates temporary copies of the JAR if + // the underlying resource is not a file URL. That can be slow so the + // InputStream for the resource is used + URL resourceURL = jarConn.getJarFileURL(); + NonClosingJarInputStream jarInputStream = null; String name = null; + try { - conn.setUseCaches(false); - jarFile = conn.getJarFile(); - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + jarInputStream = + new NonClosingJarInputStream(resourceConn.getInputStream()); + + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { name = entry.getName(); - if (!name.startsWith("META-INF/")) continue; - if (!name.endsWith(".tld")) continue; - InputStream stream = jarFile.getInputStream(entry); - XmlErrorHandler handler = tldScanStream(stream); - handler.logFindings(log, conn.getURL() + name); + if (name.startsWith("META-INF/") && name.endsWith(".tld")) { + XmlErrorHandler handler = tldScanStream(jarInputStream); + handler.logFindings(log, jarConn.getURL() + name); + } + entry = jarInputStream.getNextJarEntry(); } } catch (IOException ioe) { - log.warn(sm.getString("tldConfig.jarFail", conn.getURL() + name), + log.warn(sm.getString("tldConfig.jarFail", jarConn.getURL() + name), ioe); } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.reallyClose(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } @@ -556,13 +573,6 @@ public final class TldConfig implements LifecycleListener { throw new IOException(s); } finally { tldDigester.reset(); - if (resourceStream != null) { - try { - resourceStream.close(); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - } - } } return result; } diff --git a/java/org/apache/jasper/compiler/TldLocationsCache.java b/java/org/apache/jasper/compiler/TldLocationsCache.java index 3d66af623..ff74c6082 100644 --- a/java/org/apache/jasper/compiler/TldLocationsCache.java +++ b/java/org/apache/jasper/compiler/TldLocationsCache.java @@ -21,14 +21,14 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; -import java.util.Enumeration; +import java.net.URL; +import java.net.URLConnection; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.JarEntry; -import java.util.jar.JarFile; import javax.servlet.ServletContext; @@ -40,6 +40,7 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.JarScanner; import org.apache.tomcat.JarScannerCallback; +import org.apache.tomcat.util.scan.NonClosingJarInputStream; /** @@ -392,32 +393,39 @@ public class TldLocationsCache { * (or a subdirectory of it), adding an implicit map entry to the taglib * map for any TLD that has a element. * - * @param conn The JarURLConnection to the JAR file to scan + * @param jarConn The JarURLConnection to the JAR file to scan * * Keep in sync with o.a.c.startup.TldConfig */ - private void tldScanJar(JarURLConnection conn) throws IOException { - - JarFile jarFile = null; - String resourcePath = conn.getJarFileURL().toString(); + private void tldScanJar(JarURLConnection jarConn) throws IOException { + + // JarURLConnection#getJarFile() creates temporary copies of the JAR if + // the underlying resource is not a file URL. That can be slow so the + // InputStream for the resource is used + URL resourceURL = jarConn.getJarFileURL(); + String resourcePath = resourceURL.toString(); + + NonClosingJarInputStream jarInputStream = null; + boolean foundTld = false; try { - conn.setUseCaches(false); - jarFile = conn.getJarFile(); - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); + URLConnection resourceConn = resourceURL.openConnection(); + resourceConn.setUseCaches(false); + jarInputStream = + new NonClosingJarInputStream(resourceConn.getInputStream()); + JarEntry entry = jarInputStream.getNextJarEntry(); + while (entry != null) { String name = entry.getName(); - if (!name.startsWith("META-INF/")) continue; - if (!name.endsWith(".tld")) continue; - foundTld = true; - InputStream stream = jarFile.getInputStream(entry); - tldScanStream(resourcePath, name, stream); + if (name.startsWith("META-INF/") && name.endsWith(".tld")) { + foundTld = true; + tldScanStream(resourcePath, name, jarInputStream); + } + entry = jarInputStream.getNextJarEntry(); } } finally { - if (jarFile != null) { + if (jarInputStream != null) { try { - jarFile.close(); + jarInputStream.reallyClose(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } @@ -468,14 +476,6 @@ public class TldLocationsCache { } catch (JasperException e) { // Hack - makes exception handling simpler throw new IOException(e); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (Throwable t) { - ExceptionUtils.handleThrowable(t); - } - } } } diff --git a/java/org/apache/tomcat/util/scan/NonClosingJarInputStream.java b/java/org/apache/tomcat/util/scan/NonClosingJarInputStream.java new file mode 100644 index 000000000..4c8bb2700 --- /dev/null +++ b/java/org/apache/tomcat/util/scan/NonClosingJarInputStream.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.util.scan; + +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.JarInputStream; + +/** + * When using a {@link JarInputStream} with an XML parser, the stream will be + * closed by the parser. This causes problems if multiple entries from the JAR + * need to be parsed. This implementation makes {{@link #close()} a NO-OP and + * adds {@link #reallyClose()} that will close the stream. + */ +public class NonClosingJarInputStream extends JarInputStream { + + public NonClosingJarInputStream(InputStream in, boolean verify) + throws IOException { + super(in, verify); + } + + public NonClosingJarInputStream(InputStream in) throws IOException { + super(in); + } + + @Override + public void close() throws IOException { + // Make this a NO-OP so that further entries can be read from the stream + } + + public void reallyClose() throws IOException { + super.close(); + } +} diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index c5e34dcc4..95f499651 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -79,6 +79,13 @@ fragments to the list of JARs to skip when scanning for TLDs and web fragments. (markt) + + While scanning JARs for TLDs and fragments, avoid using JarFile and use + JarInputStream as in most circumstances where JARs are scanned, JarFile + will create a temporary copy of the JAR rather than using the resource + directly. This change significantly improves startup performance for + applications with lots of JARs to be scanned. (markt) +