双亲委派模型
1.什么是双亲委派模型
java的类加载器有父子关系,如图:
1.1 启动类加载器
加载java目录lib文件夹下的类;
1.2 扩展类加载器
加载java目录lib\ext目录下的类;
1.3 应用程序类加载器
加载其他的类;
1.4自定义类加载器
用户自定义的类加载器。
所有的类被加载的时候都不会直接被自身所属的类加载器加载,而是一直往上找到能加载该类的类加载器,父类加载不了才用自身加载。
2.为什么要用双亲委派模型
2.1 避免一个类被重复加载
2.2 避免加载不安全的类,比如java.lang.Integer类,如果用户定义了一个不安全的java.lang.Integer类,被自定义类加载器能加载的话,那所有用到这个类的程序都有漏洞,但是有了双亲委派模型,只会交给启动类加载器加载,而启动类加载器只能加载lib包下面的java.lang.Integer类。
tomcat类加载机制
tomcat的类加载机制没有完全遵循双亲委派模型,因为tomcat会部署好几个应用,而每个应用对于jar的引用可能是不同版本的,因此需要加载同一个类的不同版本,那么就用要到不同的类加载机制。
1.common类加载器,加载位于tomcat/lib文件夹下的tomcat的通用类。
2.catalina类加载器,加载server.loader 属性下的jar,默认为空,此时使用父类加载器
common加载器加载。
3.shared类加载器,加载位于share.loader 属性下的jar,默认为空,此时使用父类加载器
common加载器加载。
4.webapp类加载器, 每个web应用都有,加载应用目录下的lib和class文件。
加载顺序:
1.先交给系统类加载器->扩展类加载器->应用程序加载器加载;
2.使用webapp加载器在web-inf/classes目录下寻找类加载;
3.使用webapp加载器在web-inf/lib目录下寻找类加载;
4.使用common类加载器在calalinaHome/lib目录加载。
2、3、4的步骤保证了多个不同版本的类都被加载。
源码分析
初始化类加载器的代码在Bootstrap的initClassLoaders方法中:
//初始化类加载器
private void initClassLoaders() {
try {
// 初始化common类加载器
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();
}
// 父类加载器为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);
}
}
catalinaLoader和sharedLoader类加载器初始化的同事指定父类加载器为commonLoader,由于它们的加载路径为空,因此使用的是父类加载器:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
// catalinaLoader和sharedLoader的加路径为空,所以使用父类加载器
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals(""))) {
return parent;
}
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// 遍历指定目录下的jar包
// Local repository
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));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
读取的properties文件:
# 要加载的类
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
#
# List of comma-separated paths defining the contents of the "server"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank, the "common" loader will be used as Catalina's "server"
# loader.
# Examples:
# "foo": Add this folder as a class repository
# "foo/*.jar": Add all the JARs of the specified folder as class
# repositories
# "foo/bar.jar": Add bar.jar as a class repository
#
# Note: Values may be enclosed in double quotes ("...") in case either the
# ${catalina.base} path or the ${catalina.home} path contains a comma.
# Because double quotes are used for quoting, the double quote character
# may not appear in a path.
# 路径默认为空
server.loader=
#
# List of comma-separated paths defining the contents of the "shared"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
# the "common" loader will be used as Catalina's "shared" loader.
# Examples:
# "foo": Add this folder as a class repository
# "foo/*.jar": Add all the JARs of the specified folder as class
# repositories
# "foo/bar.jar": Add bar.jar as a class repository
# Please note that for single jars, e.g. bar.jar, you need the URL form
# starting with file:.
#
# Note: Values may be enclosed in double quotes ("...") in case either the
# ${catalina.base} path or the ${catalina.home} path contains a comma.
# Because double quotes are used for quoting, the double quote character
# may not appear in a path.
shared.loader=
再看最重要的webAppClassLoader是如何加载的:
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled()) {
log.debug("loadClass(" + name + ", " + resolve + ")");
}
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
// (0) Check our previously loaded local class cache
// 从缓存中查找类
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// (0.1) Check our previously loaded class cache
// 从缓存中查找类
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// (0.2) Try loading the class with the bootstrap class loader, to prevent
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
String resourceName = binaryNameToPath(name, false);
// 使用bootstrap class loader加载
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
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != 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 = sm.getString("webappClassLoader.restrictedPackage", name);
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
// 有些类先交给parent加载
// (1) Delegate to our parent if requested
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
}
}
// (2) Search local repositories
if (log.isDebugEnabled()) {
log.debug(" Searching local repositories");
}
// 应用自带的类交给自身webAppClassloader加载
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
}
// (3) Delegate to parent unconditionally
// 自身加载不了再交给父类加载
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);
}
可以看到:
1.先从缓存中查找是否被加载过;
2.然后用java类加载器加载,即启动类加载器和扩展类加载器;
3.如果该类委托父类加载则用父类加载器;
4.否则用该应用的类加载器加载;
5.应用的类加载器加载不了再用父类加载器加载。
这样就保证了不同项目的jar包可以同时存在不同的版本。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。