在java本身的类加载器的基础上增加了5个类加载器
下载tomcat源码:https://tomcat.apache.org/download-90.cgi
找到java/org/apache/catalina/startup/Bootstrap.java
这个是启动类,找到这块,我们来看他的逻辑
main() -> start() -> init() -> initClassLoaders(),依次创建我们的三个类加载器
看创建类加载器的方法
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
//得到属性,以common为例,得到common.loader属性,可以在tomcat/conf/catalina.properties中找到
//common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals(""))) {
return parent;
}
//下面就是加载tomcat自身字节码文件的逻辑
value = replace(value);
//把需要加载的都添加到repositories中
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
}
// 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));
}
}
//传入repositories
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
我们接着跳转到ClassLoaderFactory.createClassLoader()方法
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存储不重复的资源
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 (String s : filenames) {
String filename = s.toLowerCase(Locale.ENGLISH);
if (!filename.endsWith(".jar")) {
continue;
}
File file = new File(directory, s);
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
//根据set生成array
final URL[] array = set.toArray(new URL[0]);
if (log.isDebugEnabled()) {
for (int i = 0; i < array.length; i++) {
log.debug(" location " + i + " is " + array[i]);
}
}
return AccessController.doPrivileged(
(PrivilegedAction<URLClassLoader>) () -> {
if (parent == null) {
//传入array,生成URLClassLoader
return new URLClassLoader(array);
} else {
return new URLClassLoader(array, parent);
}
});
}
接着看,我们重点关注ucp这个变量
public URLClassLoader(URL[] urls, ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
//我们传入的array最终生成ucp
ucp = new URLClassPath(urls, acc);
}
最终在findClass中找到ucp的身影
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
//在这里
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
还记得双亲委派吗?
我们分析一下tomcat从运行,到加载commonLoader,catalinaLoader,sharedLoader这三个类的全过程。拿commonLoader举例
首先启动tomcat的bootstrap.java,交给AppClassLoader,委派给ExtClassLoader,再委派给BootStrapLoader,依次返回,最终AppClassLoader加载。然后实例化,执行我们的main方法,最终执行到initClassLoaders()方法,初始化我们的commonLoader,初始化的过程中,会把tomcat自身的资源传入到URLClassLoader类型的commonLoader中。当程序的其他地方使用我们commonLoader的时候会先执行loadClass方法委派给AppClassLoader重复上面步骤,最终执行commonLoader的findClass方法,去加载tomcat的资源。
即,commonLoader,catalinaLoader,sharedLoader没有重写loadClass方法,都没有打破双亲委派规则
我们再看WebAppClassLoader和JasperLoader
public class WebappClassLoader extends WebappClassLoaderBase {
public WebappClassLoader() {
super();
}
public WebappClassLoader(ClassLoader parent) {
super(parent);
}
}
我们看WebappClassLoaderBase中,重写了loadClass
并且JasperLoader中也重写了loadClass
所以二者打破了双亲委派规则
总结:
为什么WebAppClassLoader和JasperLoader要打破双亲委派规则?
1、部署在同一个Tomcat上的两个Web应用所使用的Java类库要相互隔离;
2、部署在同一个Tomcat上的两个Web应用所使用的Java类库要互相共享;
3、保证Tomcat服务器自身的安全不受部署的Web应用程序影响;
4、需要支持JSP页面的热部署和热加载;
加入我们没有打破,那么WebAppClassLoader在加载我们的web应用中的资源的时候,就会先去BootStrapClassLoader中找,再到extClassLoader中去等等,那些是tomcat本身jre中的资源,而不是web的资源,没有起到隔离的效果。加载就乱套了。所以要自定义类加载。