Tomcat 的classload 是tomcat能同时部署多个应用,而每个应用之家不冲突的核心技术,所以要分析tomcat的classload机制,必须要知道他的classload是怎么创建的,在哪里使用的,具体的加载规则是怎么实现的,打算分上下两篇来完成,一篇太长,效果不好。
Tomcat 怎么创建classload
tomcat 在启动的时候会为为每个webapp 创建一个StandardContext,
StandardContext在初始化的时候一个WebappClassLoad,代码如下:
if (getLoader() == null) {
WebappLoader webappLoader = new
WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
//下面会用到,用来创建真正的WebappClassLoaderBase
setLoader(webappLoader);
}
WebappLoader 只是一个代理,创建完成了,通过start方法来初始化类加载器,代码如下:
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
WebappLoader的start方法,里面是调用了WebappLoader的startInternal方法,代码如下:
try {
//这里是tomcat创建他的WebappClassLoaderBase的地方
classLoader = createClassLoader();
classLoader.setResources(context.getResources());
classLoader.setDelegate(this.delegate);
// Configure our repositories
setClassPath();
setPermissions();
classLoader.start();
String contextName = context.getName();
if (!contextName.startsWith("/")) {
contextName = "/" + contextName;
}
ObjectName cloname = new ObjectName(context.getDomain() + ":type=" +
classLoader.getClass().getSimpleName() + ",host=" +
context.getParent().getName() + ",context=" + contextName);
Registry.getRegistry(null, null)
.registerComponent(classLoader, cloname, null);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
throw new LifecycleException(sm.getString("webappLoader.startError"), t);
}
createClassLoader的代码如下:
private WebappClassLoaderBase createClassLoader()
throws Exception {
//通过应用类classload加载loaderClass,这个loaderClass为
// ParallelWebappClassLoader继承WebappClassLoaderBase
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoaderBase classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = context.getParentClassLoader();
}
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args);
return classLoader;
}
tomcat写死了loaderClass为ParallelWebappClassLoader,通过他的构造函数,创建一个牛叉的classload,WebappClassLoaderBase,并
指定了parent的classload为AppClassload,javaseClassLoader的class为ExtClassload,这两个在tomcat加载类的时候都会用到。
这里创建的WebappClassLoaderBase是tomcat的StandardContext级别的,tomcat会为每个webapp都创建一个StandardContext,即每个app也就有一个独立的classload,就这样可以各自依赖自己的jar包了。
tomcat的WebappClassLoader 创建好了,但是我们还要知道在哪里用到,怎么用的才行,下面就一步一步分析
JVM 类加载的全面性。
有个前提,就是tomcat加载我们自己定义的第一个class,肯定是我们定义的servlet,从servlet开始,就都是我们自己的写的class和依赖第三方的jar,只要保证servlet的加载是用WebappClassLoader加载,那servlet依赖的其他类都会用这个WebappClassLoader加载,从而都按
tomcat的加载规则来执行,这个是前提。
Servlet 的加载
tomcat servlet 如果订阅了loadstartup>=0,则在启动的时候初始化,否则在第一次请求的时候初始化 ,这个servlet的classload就是用的上面创建的classload来加载的,代码在StandardWrapper的loadServlet方法,核心代码如下:
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
//这里是会用前面创建的webappclassload来加载我们订阅的servlet
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// https://bz.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
InstanceManager
Tomcat通过InstanceManager来管理代理classload的。
InstanceManager 又是在哪里创建的呢,这个是在初始化StandardContext时,在创建完classload时,会初始化InstanceManager,代码如下:
if (ok ) {
if (getInstanceManager() == null) {
setInstanceManager(createInstanceManager());
}
...
}
createInstanceManager 代码如下:
public InstanceManager createInstanceManager() {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map<String, Map<String, String>> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
//this.getClass().getClassLoader()是tomcat启动的AppClassload
return new DefaultInstanceManager(context, injectionMap,
this, this.getClass().getClassLoader());
}
DefaultInstanceManager 的构造函数如下:
public DefaultInstanceManager(Context context,
Map<String, Map<String, String>> injectionMap,
org.apache.catalina.Context catalinaContext,
ClassLoader containerClassLoader) {
//这里指定了我们前面创建的WebappClassLoaderBase
classLoader = catalinaContext.getLoader().getClassLoader();
privileged = catalinaContext.getPrivileged();
//containerClassLoader 是传进来的即AppClassload
this.containerClassLoader = containerClassLoader;
ignoreAnnotations = catalinaContext.getIgnoreAnnotations();
Log log = catalinaContext.getLogger();
Set<String> classNames = new HashSet<>();
loadProperties(classNames,
"org/apache/catalina/core/RestrictedServlets.properties",
"defaultInstanceManager.restrictedServletsResource", log);
loadProperties(classNames,
"org/apache/catalina/core/RestrictedListeners.properties",
"defaultInstanceManager.restrictedListenersResource", log);
loadProperties(classNames,
"org/apache/catalina/core/RestrictedFilters.properties",
"defaultInstanceManager.restrictedFiltersResource", log);
restrictedClasses = Collections.unmodifiableSet(classNames);
this.context = context;
this.injectionMap = injectionMap;
this.postConstructMethods = catalinaContext.findPostConstructMethods();
this.preDestroyMethods = catalinaContext.findPreDestroyMethods();
}
DefaultInstanceManager 我们目前只要关心classLoader和containerClassLoader,tomcat在加载class时,会判断如果是tomcat自己的class,就用containerClassLoader加载,否则就classLoader也就是WebappClassLoaderBase加载。
现在我们知道InstanceManager是怎么来的,下面我们看这个InstanceManager是怎么加载我们定义的Servlet的,上面InstanceManager的newInstance方法最终会调用到DefaultInstanceManager的loadClass方法,代码如下:
protected Class<?> loadClass(String className, ClassLoader classLoader)
throws ClassNotFoundException {
//如果是tomcat自己实现的类,则用appClassLoad加载
if (className.startsWith("org.apache.catalina")) {
return containerClassLoader.loadClass(className);
}
try {
//如果是tomcat自己实现的类,则用appClassLoad加载
Class<?> clazz = containerClassLoader.loadClass(className);
if (ContainerServlet.class.isAssignableFrom(clazz)) {
return clazz;
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
//这里就是WebappClassLoaderBase开始加载我们自己定义的Servlet的地方,这里面就涉及到加载顺序的问题
return classLoader.loadClass(className);
}
到这里我们才分析完,tomcat的WebappClassLoaderBase的创建,和使用的地方,对于WebappClassLoaderBase具体怎么加载的,他为啥打破了jdk的双亲委派模型,以及对我们自己的class和第三方的依赖又是那个优先的,我们在下篇分析。
网友评论