在上一篇文章中,主要介绍了jvm中类加载器的工作原理和小demo,在这篇文章中,介绍下Tomcat封装后的加载器。
Tomcat封装后的加载器,只能够加载指定库中的文件,这里,还拿第二章的程序中的加载器做个例子。
URLClassLoader loader = null;
try
{
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
String repository = (new URL("file", null,
classPath.getAbsolutePath() + File.separator)).toString();
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
} catch (MalformedURLException e)
{
e.printStackTrace();
}
介绍下上面用到的URL的构造器, URL(URL context, String spec, URLStreamHandler handler),第一个参数,context,上下文,官方的注释:the context in which to parse the specification,从构造器中的代码的条件(((context != null) && ((newProtocol == null) || newProtocol.equalsIgnoreCase(context.protocol))))可以知道,当传入的上下文中的协议与传入参数中spec中的url协议一致的时候,构造器会使用我们传入的上下文(context)。下面介绍第二个参数,spec,官方的注释:the String to parse as a URL,它就表示一个URL,通过上面的代码片段,这个参数使用URL的另一个构造器URL(String protocol, String host, String file),这个构造器我之后再说,这个参数所表示的url就是我们的类加载器所加载的类的路径。最后一个参数streamHandler,官方解释:the stream handler for the URL。如果这个参数不为空的话,Check for permission to specify a handler,在构造器中就会检查处理程序的权限。这个处理中的代表类的实际路径的spec参数是非空的,其他两个参数是可以为空的,这个构造器被Tomcat看中的点,就在于那两个参数,可以添加上下文和权限控制。
下面介绍先刚刚提到的构造器URL(String protocol, String host, String file),第一个参数protocol,官方解释:the name of the protocol to use.第二个参数:the name of the host.,第三个参数:the file on the host.当我们使用这个构造器的时候,他会调取该对象内的另一构造器:代码片段如下
public URL(String protocol, String host, String file)
throws MalformedURLException {
this(protocol, host, -1, file);
}
它又搞出来一个新的构造器URL(String protocol, String host, int port, String file),这个构造器比刚才的构造器多了一个端口的参数,当调用上面的构造器的方法的时候,他会使用传入的主机host在默认端口-1,中查找文件file。但是当我看这个构造器的源码的时候,这里并没有结束,它描述路径的方法:String file = (new File(Constants.WEB_ROOT)).getAbsolutePath() + File.separator ; 描述项目下的web_root文件夹的路径
public URL(String protocol, String host, int port, String file)
throws MalformedURLException
{
this(protocol, host, port, file, null);
}
在Tomcat中,加载器都需要实现Loader接口,回顾一下tomcat加载器的UML结构图:
从结构图中,可以看出,在Tomcat的加载器中生活在最底层的是WebappClassLoader,下面,介绍下WebappClassLoader对象与WebappLoader对象,WebappLoader可以控制WebAppClassLoader的生死。从上面的结构图中可以知道,它还继承了URLClassLoader类,从WebappLoader的start()方法的开始,Start this component, initializing our associated class loader.在开始的时候,日志组件和Lifecycle接口中做的事情就不累述了,我在后面的会把前面讲到的几个组件联合起来讲一遍,这里只说loader的事情了,看下面代码块:
1、为JNDI协议注册一个流管理工厂
if (container.getResources() == null)
return;
// Register a stream handler factory for the JNDI protocol
URLStreamHandlerFactory streamHandlerFactory = new DirContextURLStreamHandlerFactory();
try {
URL.setURLStreamHandlerFactory(streamHandlerFactory);
} catch (Throwable t) {
// Ignore the error here.
}
2、创建classloader加载器,运用java中的反射创建classloader加载器,其中在下面代码块中的变量 loaderClass,是定义好了的常量,Tomcat默认加载器的类名:“org.apache.catalina.loader.WebappClassLoader”
Class clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;
if (parentClassLoader == null) {
// Will cause a ClassCast is the class does not extend WCL, but
// this is on purpose (the exception will be caught and rethrown)
classLoader = (WebappClassLoader) clazz.newInstance();
} else {
Class[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);
}
3、装载有特色的classLoader,下面的各种设置中最主要的就是设置类库列表 addRepository,为classloader指定库,不仅如此,在设置完库之后,他又设置权限相关内容,使classLoader加载各种url中,按照与url匹配的协议加载,这样设置之后,对于首先的classLoader,用起来是安全的。
classLoader.setResources(container.getResources());
classLoader.setDebug(this.debug);
classLoader.setDelegate(this.delegate);
for (int i = 0; i < repositories.length; i++) {
classLoader.addRepository(repositories[i]);
}
// Configure our repositories
setRepositories();
setClassPath();
setPermissions();
4、组件一旦启动起来,下面这个方法可能会经常调用,重载。WebappLoader 支持自动重载,如果 WEB-INF/classes 或者 WEB-INF/lib 目录被重新编译过,在不重启 Tomcat 的情况下必须自动重新载入这些类。为了实现这个目的,WebappLoader 有一个单独的线程每个 x 秒会检查源的时间戳。x 的值由checkInterval 变量定义,它的默认值是 15,也就是每隔 15 秒会进行一次检查是否需要自动重载。
if (reloadable)
{
log(sm.getString("webappLoader.reloading"));
try {
threadStart();
} catch (IllegalStateException e) {
throw new LifecycleException(e);
}
}
5、重载,下面就是重载线程所做的事情:检查classloader是否发生改变,如果发生改变了,这里会通知上下文,上下文会唤醒一个线程 WebappContextNotifier,这个线程运行,就是让容器重载: ((Context) container).reload();
try {
// Perform our modification check
if (!classLoader.modified())
continue;
} catch (Exception e) {
log(sm.getString("webappLoader.failModifiedCheck"), e);
continue;
}
// Handle a need for reloading
notifyContext();
6、缓存。
每一个可以被加载的类(放在 WEB-INF/classes 目录下的类文件或者 JAR 文件)都被当做一个源。一个源被 org.apache.catalina.loader.ResourceEntry 类表示。一个 ResourceEntry 实例保存一个 byte 类型的数组表示该类、最后修改的数据或者副本等等。所有缓存的源被存放在一个叫做 resourceEntries 的 HashMap 中(在WabappClassLoader中的元素),键值为源名,所有找不到的源都被放在一个名为 notFoundResources 的 HashMap 中。要注意,Tomcat没加载一个类,都会把它放到缓存中,加载器加载类的时候,也是首先在缓存中查找,找不到才会到库中去查找。