001/* 002 * Copyright 2012-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.loader; 018 019import java.io.IOException; 020import java.net.JarURLConnection; 021import java.net.URL; 022import java.net.URLClassLoader; 023import java.net.URLConnection; 024import java.security.AccessController; 025import java.security.PrivilegedExceptionAction; 026import java.util.Enumeration; 027import java.util.jar.JarFile; 028 029import org.springframework.boot.loader.jar.Handler; 030import org.springframework.lang.UsesJava7; 031 032/** 033 * {@link ClassLoader} used by the {@link Launcher}. 034 * 035 * @author Phillip Webb 036 * @author Dave Syer 037 * @author Andy Wilkinson 038 */ 039public class LaunchedURLClassLoader extends URLClassLoader { 040 041 static { 042 performParallelCapableRegistration(); 043 } 044 045 /** 046 * Create a new {@link LaunchedURLClassLoader} instance. 047 * @param urls the URLs from which to load classes and resources 048 * @param parent the parent class loader for delegation 049 */ 050 public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) { 051 super(urls, parent); 052 } 053 054 @Override 055 public URL findResource(String name) { 056 Handler.setUseFastConnectionExceptions(true); 057 try { 058 return super.findResource(name); 059 } 060 finally { 061 Handler.setUseFastConnectionExceptions(false); 062 } 063 } 064 065 @Override 066 public Enumeration<URL> findResources(String name) throws IOException { 067 Handler.setUseFastConnectionExceptions(true); 068 try { 069 return super.findResources(name); 070 } 071 finally { 072 Handler.setUseFastConnectionExceptions(false); 073 } 074 } 075 076 @Override 077 protected Class<?> loadClass(String name, boolean resolve) 078 throws ClassNotFoundException { 079 Handler.setUseFastConnectionExceptions(true); 080 try { 081 try { 082 definePackageIfNecessary(name); 083 } 084 catch (IllegalArgumentException ex) { 085 // Tolerate race condition due to being parallel capable 086 if (getPackage(name) == null) { 087 // This should never happen as the IllegalArgumentException indicates 088 // that the package has already been defined and, therefore, 089 // getPackage(name) should not return null. 090 throw new AssertionError("Package " + name + " has already been " 091 + "defined but it could not be found"); 092 } 093 } 094 return super.loadClass(name, resolve); 095 } 096 finally { 097 Handler.setUseFastConnectionExceptions(false); 098 } 099 } 100 101 /** 102 * Define a package before a {@code findClass} call is made. This is necessary to 103 * ensure that the appropriate manifest for nested JARs is associated with the 104 * package. 105 * @param className the class name being found 106 */ 107 private void definePackageIfNecessary(String className) { 108 int lastDot = className.lastIndexOf('.'); 109 if (lastDot >= 0) { 110 String packageName = className.substring(0, lastDot); 111 if (getPackage(packageName) == null) { 112 try { 113 definePackage(className, packageName); 114 } 115 catch (IllegalArgumentException ex) { 116 // Tolerate race condition due to being parallel capable 117 if (getPackage(packageName) == null) { 118 // This should never happen as the IllegalArgumentException 119 // indicates that the package has already been defined and, 120 // therefore, getPackage(name) should not have returned null. 121 throw new AssertionError( 122 "Package " + packageName + " has already been defined " 123 + "but it could not be found"); 124 } 125 } 126 } 127 } 128 } 129 130 private void definePackage(final String className, final String packageName) { 131 try { 132 AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { 133 @Override 134 public Object run() throws ClassNotFoundException { 135 String packageEntryName = packageName.replace('.', '/') + "/"; 136 String classEntryName = className.replace('.', '/') + ".class"; 137 for (URL url : getURLs()) { 138 try { 139 URLConnection connection = url.openConnection(); 140 if (connection instanceof JarURLConnection) { 141 JarFile jarFile = ((JarURLConnection) connection) 142 .getJarFile(); 143 if (jarFile.getEntry(classEntryName) != null 144 && jarFile.getEntry(packageEntryName) != null 145 && jarFile.getManifest() != null) { 146 definePackage(packageName, jarFile.getManifest(), 147 url); 148 return null; 149 } 150 } 151 } 152 catch (IOException ex) { 153 // Ignore 154 } 155 } 156 return null; 157 } 158 }, AccessController.getContext()); 159 } 160 catch (java.security.PrivilegedActionException ex) { 161 // Ignore 162 } 163 } 164 165 /** 166 * Clear URL caches. 167 */ 168 public void clearCache() { 169 for (URL url : getURLs()) { 170 try { 171 URLConnection connection = url.openConnection(); 172 if (connection instanceof JarURLConnection) { 173 clearCache(connection); 174 } 175 } 176 catch (IOException ex) { 177 // Ignore 178 } 179 } 180 181 } 182 183 private void clearCache(URLConnection connection) throws IOException { 184 Object jarFile = ((JarURLConnection) connection).getJarFile(); 185 if (jarFile instanceof org.springframework.boot.loader.jar.JarFile) { 186 ((org.springframework.boot.loader.jar.JarFile) jarFile).clearCache(); 187 } 188 } 189 190 @UsesJava7 191 private static void performParallelCapableRegistration() { 192 try { 193 ClassLoader.registerAsParallelCapable(); 194 } 195 catch (NoSuchMethodError ex) { 196 // Running on Java 6. Continue. 197 } 198 } 199 200}