java的类加载器我们这里就不做详细讲解了,百度一堆(启动类加载器BootstrapClassLoader,扩展类加载器ExtensionClassLoader,系统类加载器App ClassLoader,线程上下文类加载器),我们这里主要讲一下tomcat自定义类加载ParallelWebappClassLoader如何做到webapps(Host)中不同的文件夹(context)的类隔离
1.创建TestTomcatClassLoader 类,路径C:/Users/paul/Desktop/ROOT为tomcat默认的ROOT站点
public class TestTomcatClassLoader {
public static void main(String[] args) throws Exception {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.setBaseDir("/tmp/tomcat");
tomcat.addWebapp("", "C:\\Users\\paul\\Desktop\\ROOT");
tomcat.start();
tomcat.getServer().await();
}
}
2.在TestTomcatClassLoader 工程的classpath中创建两个类Hello和HelloServlet,这2个类编译后System.out.println 为CLASSPATH放到与EmbedTomcatHttp同项目中,System.out.println 为ROOT放到C:/Users/paul/Desktop/ROOT/WEB-INF/classes/test目录下
package test;
public class Hello {
public void print() {
System.out.println("=======类加载器 CLASSPATH");
//System.out.println("=======类加载器 ROOT");
System.out.println(this.getClass().getClassLoader());
//System.out.println(Thread.currentThread().getContextClassLoader());
}
}
package test;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class HelloServlet extends GenericServlet{
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
response.setStatus(200);
response.getWriter().append("Hello World...CLASSPATH");
//response.getWriter().append("Hello World...ROOT");
new Hello().print();
}
}
3.配置ROOT/WEB-INF/web.xml 如下:
HelloServlet
test.HelloServlet
HelloServlet
/hello/*
4.访问http://127.0.0.1:8080/hello
返回:Hello World...ROOT ,控制台打印:
=======类加载器 ROOT
ParallelWebappClassLoader
context: ROOT
delegate: false
----------> Parent Classloader:sun.misc.Launcher$AppClassLoader@14dad5dc
CLASSPATH中的test.Hello 为A1 ,test.HelloServlet 为B1,
C:/Users/paul/Desktop/ROOT/WEB-INF/classes/ 中的test.Hello 为A2 ,test.HelloServlet 为B2
A1 | A2 | B1 | B2 | 浏览器响应 | 控制台 |
---|---|---|---|---|---|
Y | Y | Y | Y | ? | ? |
Y | N | Y | N | ? | ? |
N | Y | N | Y | ? | ? |
Y | N | N | Y | ? | ? |
N | Y | Y | N | ? | ? |
(读者可以组合查看结果,理解类加载器)
下面详细给大家讲解ParallelWebappClassLoader extends WebappClassLoaderBase
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 system class loader, to prevent
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();//ExtensionClassLoader
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);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
// (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");
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);
}
new Hello().print();加载流程:
1.由于classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入,HelloServlet是由WebappClassLoaderBase加载的,那么Hello也由WebappClassLoaderBase,可以自行打断点验证
2.findLoadedClass0("test.Hello")查看当前类加载器resourceEntries是否缓存
protected Class> findLoadedClass0(String name) {
String path = binaryNameToPath(name, true);
ResourceEntry entry = resourceEntries.get(path);
if (entry != null) {
return entry.loadedClass;
}
return null;
}
3.findLoadedClass("test.Hello") 从native方法中查看类缓存
protected final Class> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class> findLoadedClass0(String name);
4.ClassLoader javaseLoader = getJavaseClassLoader() 得到的sun.misc.LauncherAppClassLoader)clazz = Class.forName(name, false, parent);
比如以javax开头的类
6.clazz = findClass(name); 由当前类加载WebappClassLoaderBase加载,从/WEB-INF/classes/test/Hello.class进行查找文件将文件放入byte[],transformer.transform()进行插桩改造byte[],最终defineClass生成class
7.WebappClassLoaderBase加载不到的类由父类加载器AppClassLoader 记载clazz = Class.forName(name, false, parent);
8.throw new ClassNotFoundException(name); 第7步中加载不到就抛异常啦