相关源码如下:
common,catalina,shared三个classloader没有违背双亲加载原则
public void init() throws Exception {
初始化classloader common,catalina,shared
initClassLoaders();
把catalinaLoader设置到线程上下文
Thread.currentThread().setContextClassLoader(catalinaLoader);
给SecurityClass使用的
SecurityClassLoad.securityClassLoad(catalinaLoader);
获取Catalina类实例
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
设置Catalina的parentclassloader为sharedLoader。这边并不是指定catalinaclassloader的ParentClassLoader
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
initClassloaders
private void initClassLoaders() {
try {
设置commonLoader ,如果获取不到就使用appclassloader作为commonLoader
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
这边设置如果catalinaLoader 或着sharedLoader 不存在就是使用commonLoader
如果存在 则把commonLoader设置为其父类
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
如果不存在我们配置了属性 则直接使用父类
common的value 分别是对应的catalina.home或catalina.base的/lib 或者/lib/*.jar
其他的server和shared默认都是空字符串
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
将上面的catalina.home和catalina.base替换成真正的地址
value = replace(value);
List<Repository> repositories = new ArrayList<>();
将value分成一个个对应的repositoryPaths
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
首先检测是否是url
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
如果不是url在检测是否是*.jar,.jar或者其他的
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(new Repository(repository, RepositoryType.DIR));
}
}
这边就是如果存在父类 就设置父类为当前classloader的父类为parent,否则设置
为appclassloader,这边我们创建一个commonclassloader然后让他去加载repositories,
即把repositories转化为url调用urlclassloader,commonclassloader调用了urlclassloader的无parent的参数
默认会将系统加载classloader设置为父类
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
public static ClassLoader createClassLoader(List<Repository> repositories,
final ClassLoader parent)
throws Exception {
if (log.isDebugEnabled())
log.debug("Creating new class loader");
// Construct the "class path" for this class loader
Set<URL> set = new LinkedHashSet<>();
if (repositories != null) {
for (Repository repository : repositories) {
if (repository.getType() == RepositoryType.URL) {
URL url = buildClassLoaderUrl(repository.getLocation());
if (log.isDebugEnabled())
log.debug(" Including URL " + url);
set.add(url);
} else if (repository.getType() == RepositoryType.DIR) {
File directory = new File(repository.getLocation());
directory = directory.getCanonicalFile();
if (!validateFile(directory, RepositoryType.DIR)) {
continue;
}
URL url = buildClassLoaderUrl(directory);
if (log.isDebugEnabled())
log.debug(" Including directory " + url);
set.add(url);
} else if (repository.getType() == RepositoryType.JAR) {
File file=new File(repository.getLocation());
file = file.getCanonicalFile();
if (!validateFile(file, RepositoryType.JAR)) {
continue;
}
URL url = buildClassLoaderUrl(file);
if (log.isDebugEnabled())
log.debug(" Including jar file " + url);
set.add(url);
} else if (repository.getType() == RepositoryType.GLOB) {
File directory=new File(repository.getLocation());
directory = directory.getCanonicalFile();
if (!validateFile(directory, RepositoryType.GLOB)) {
continue;
}
if (log.isDebugEnabled())
log.debug(" Including directory glob "
+ directory.getAbsolutePath());
String filenames[] = directory.list();
if (filenames == null) {
continue;
}
for (int j = 0; j < filenames.length; j++) {
String filename = filenames[j].toLowerCase(Locale.ENGLISH);
if (!filename.endsWith(".jar"))
continue;
File file = new File(directory, filenames[j]);
file = file.getCanonicalFile();
if (!validateFile(file, RepositoryType.JAR)) {
continue;
}
if (log.isDebugEnabled())
log.debug(" Including glob jar file "
+ file.getAbsolutePath());
URL url = buildClassLoaderUrl(file);
set.add(url);
}
}
}
}
// Construct the class loader itself
final URL[] array = set.toArray(new URL[set.size()]);
if (log.isDebugEnabled())
for (int i = 0; i < array.length; i++) {
log.debug(" location " + i + " is " + array[i]);
}
核心就是在这 根据是否存在父类进行设置
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
设置系统类为父类
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}
WebappClassLoader违背了双亲委派机制 因为重写了loadclass
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null;
checkStateForClassLoading(name);
检测WebappClassLoader是否已经加载过该class
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
是否需要link 即进行准备验证链接
if (resolve)
resolveClass(clazz);
return clazz;
}
检测jvm是否已经加载过该class(避免了我们自己的应用程序写的类覆盖jdk核心类)
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
尝试采用appclassloader加载我们的应用类,避免了我们自己的应用程序写的类覆盖jdk核心类
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
} catch (Throwable t) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
// The getResource() trick won't work for this class. We have to
// try loading it directly and accept that we might get a
// ClassNotFoundException.
tryLoadingFromJavaseLoader = true;
}
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
检测是否有权限加载该类
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = "Security Violation, attempt to use " +
"Restricted Class: " + name;
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
filter就是确定我们加载的这个类是交给extclassloader去实现还是交给
webappclassloader,其根据类名类判断
boolean delegateLoad = delegate || filter(name, true);
delegateLoad为true就是把类交给扩展类(extclassloader)加载器去加载
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
webappclassloader自己去加载
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
如果到这一步还没加载成功 说明webappclassloader
根本无法加载,我们尝试交给appclassloader去加载
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
还未加载成功则抛出异常
throw new ClassNotFoundException(name);
}
总结:webappclassloader的加载过程
首先检测webappclassloader是否已经加载过了该类
如果没有在检测jvm是否已经加载过该类
如果没有我们尝试用扩展类去加载这样就避免了我们jdk的核心类被我们webappclassloader
给覆盖。
如果加载失败,或者不需要扩展类去加载我们根据类名判断
是否需要交给我们的父类进行加载(也就是所谓sharedclassloader)
如果不需要 则我们自己加载
如果我们自己加载失败 则我们尝试让sharedclassloader加载
如果还是失败则抛出异常
在context启动的时候其会启动一个webappLoader
而webappLoader会创建一个WebappClassLoader,设置一个source(对应咱们webapps下面的一个项目)
设置delegate=false 代表不需要遵循双亲委派机制,然后启动WebappClassLoader,
然后这个context的加载的会使用自己的context里面含有的WebappClassLoader去加载bean
当webapps下面有多个项目就建立多个standardContext
每个standardContext 设有一个对应的webappLoader和webappClassLoader