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; 030 031/** 032 * {@link ClassLoader} used by the {@link Launcher}. 033 * 034 * @author Phillip Webb 035 * @author Dave Syer 036 * @author Andy Wilkinson 037 */ 038public class LaunchedURLClassLoader extends URLClassLoader { 039 040 static { 041 ClassLoader.registerAsParallelCapable(); 042 } 043 044 /** 045 * Create a new {@link LaunchedURLClassLoader} instance. 046 * @param urls the URLs from which to load classes and resources 047 * @param parent the parent class loader for delegation 048 */ 049 public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) { 050 super(urls, parent); 051 } 052 053 @Override 054 public URL findResource(String name) { 055 Handler.setUseFastConnectionExceptions(true); 056 try { 057 return super.findResource(name); 058 } 059 finally { 060 Handler.setUseFastConnectionExceptions(false); 061 } 062 } 063 064 @Override 065 public Enumeration<URL> findResources(String name) throws IOException { 066 Handler.setUseFastConnectionExceptions(true); 067 try { 068 return new UseFastConnectionExceptionsEnumeration(super.findResources(name)); 069 } 070 finally { 071 Handler.setUseFastConnectionExceptions(false); 072 } 073 } 074 075 @Override 076 protected Class<?> loadClass(String name, boolean resolve) 077 throws ClassNotFoundException { 078 Handler.setUseFastConnectionExceptions(true); 079 try { 080 try { 081 definePackageIfNecessary(name); 082 } 083 catch (IllegalArgumentException ex) { 084 // Tolerate race condition due to being parallel capable 085 if (getPackage(name) == null) { 086 // This should never happen as the IllegalArgumentException indicates 087 // that the package has already been defined and, therefore, 088 // getPackage(name) should not return null. 089 throw new AssertionError("Package " + name + " has already been " 090 + "defined but it could not be found"); 091 } 092 } 093 return super.loadClass(name, resolve); 094 } 095 finally { 096 Handler.setUseFastConnectionExceptions(false); 097 } 098 } 099 100 /** 101 * Define a package before a {@code findClass} call is made. This is necessary to 102 * ensure that the appropriate manifest for nested JARs is associated with the 103 * package. 104 * @param className the class name being found 105 */ 106 private void definePackageIfNecessary(String className) { 107 int lastDot = className.lastIndexOf('.'); 108 if (lastDot >= 0) { 109 String packageName = className.substring(0, lastDot); 110 if (getPackage(packageName) == null) { 111 try { 112 definePackage(className, packageName); 113 } 114 catch (IllegalArgumentException ex) { 115 // Tolerate race condition due to being parallel capable 116 if (getPackage(packageName) == null) { 117 // This should never happen as the IllegalArgumentException 118 // indicates that the package has already been defined and, 119 // therefore, getPackage(name) should not have returned null. 120 throw new AssertionError( 121 "Package " + packageName + " has already been defined " 122 + "but it could not be found"); 123 } 124 } 125 } 126 } 127 } 128 129 private void definePackage(String className, String packageName) { 130 try { 131 AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> { 132 String packageEntryName = packageName.replace('.', '/') + "/"; 133 String classEntryName = className.replace('.', '/') + ".class"; 134 for (URL url : getURLs()) { 135 try { 136 URLConnection connection = url.openConnection(); 137 if (connection instanceof JarURLConnection) { 138 JarFile jarFile = ((JarURLConnection) connection) 139 .getJarFile(); 140 if (jarFile.getEntry(classEntryName) != null 141 && jarFile.getEntry(packageEntryName) != null 142 && jarFile.getManifest() != null) { 143 definePackage(packageName, jarFile.getManifest(), url); 144 return null; 145 } 146 } 147 } 148 catch (IOException ex) { 149 // Ignore 150 } 151 } 152 return null; 153 }, AccessController.getContext()); 154 } 155 catch (java.security.PrivilegedActionException ex) { 156 // Ignore 157 } 158 } 159 160 /** 161 * Clear URL caches. 162 */ 163 public void clearCache() { 164 for (URL url : getURLs()) { 165 try { 166 URLConnection connection = url.openConnection(); 167 if (connection instanceof JarURLConnection) { 168 clearCache(connection); 169 } 170 } 171 catch (IOException ex) { 172 // Ignore 173 } 174 } 175 176 } 177 178 private void clearCache(URLConnection connection) throws IOException { 179 Object jarFile = ((JarURLConnection) connection).getJarFile(); 180 if (jarFile instanceof org.springframework.boot.loader.jar.JarFile) { 181 ((org.springframework.boot.loader.jar.JarFile) jarFile).clearCache(); 182 } 183 } 184 185 private static class UseFastConnectionExceptionsEnumeration 186 implements Enumeration<URL> { 187 188 private final Enumeration<URL> delegate; 189 190 UseFastConnectionExceptionsEnumeration(Enumeration<URL> delegate) { 191 this.delegate = delegate; 192 } 193 194 @Override 195 public boolean hasMoreElements() { 196 Handler.setUseFastConnectionExceptions(true); 197 try { 198 return this.delegate.hasMoreElements(); 199 } 200 finally { 201 Handler.setUseFastConnectionExceptions(false); 202 } 203 204 } 205 206 @Override 207 public URL nextElement() { 208 Handler.setUseFastConnectionExceptions(true); 209 try { 210 return this.delegate.nextElement(); 211 } 212 finally { 213 Handler.setUseFastConnectionExceptions(false); 214 } 215 } 216 217 } 218 219}