网站建设需要交文化建设税吗抖音营销软件
接下来,我们再来看下 tomcat 是如何创建 common 类加载器的。关键代码如下所示,在创建类加载器时,会读取相关的路径配置,并把路径封装成 Repository
对象,然后交给 ClassLoaderFactory
创建类加载器。
Bootstrap.java
private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {// 从catalina.propeties中读取配置,并替换 catalina.home、或者catalina.base,或者环境变量String value = CatalinaProperties.getProperty(name + ".loader");value = replace(value);// 遍历目录,并对路径进行处理List<Repository> repositories = new ArrayList<>();String[] repositoryPaths = getPaths(value);for (String repository : repositoryPaths) {//TODO 将路径封装成 Repository 对象}return ClassLoaderFactory.createClassLoader(repositories, parent);
我们再进一步对 ClassLoaderFactory
进行分析,都是细节上的处理,比如利用文件路径构造带有明显协议的 URL 对象,例如本地文件的标准 URL 是 file:/D:/app.jar
。另外,在创建 URLClassLoader
的时候还需要考虑 jdk 对权限控制的影响,因此 tomcat 利用 AccessController
创建 URLClassLoader
,由此可见 tomcat 编码的严谨性。而我们在实际的开发过程中,有时候需要自定义类加载器,但往往不会考虑权限控制这块,所以在对类加载器进行编码时需要注意一下
ClassLoaderFactory.java
public static ClassLoader createClassLoader(List<Repository> repositories, final ClassLoader parent)throws Exception {Set<URL> set = new LinkedHashSet<>();if (repositories != null) {for (Repository repository : repositories) {// 对不同类型的 Repository 对象进行处理,将路径转换为URL类型// 因为 URL 类型带有明显的协议,比如jar:xxx、file:xxx}}// 将对应的路径组装成 URLfinal URL[] array = set.toArray(new URL[set.size()]);// 在创建 URLClassLoader 需要考虑到 AccessController 的影响return AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {public URLClassLoader run() {if (parent == null) return new URLClassLoader(array);else return new URLClassLoader(array, parent);}});
}private static URL buildClassLoaderUrl(File file) throws MalformedURLException {String fileUrlString = file.toURI().toString();fileUrlString = fileUrlString.replaceAll("!/", "%21/"); // 转换成URL编码return new URL(fileUrlString);
OK,前面介绍了 tomcat 创建类加载器的过程,接下来我们看下 tomcat 类加载器的具体应用场景
WebappClassLoader
在前面,我们介绍了 tomcat 类加载器的设计,每个 webapp 使用单独的类加载器完成我们开发的 webapp 应用程序的类加载,而每一个 webapp 对应一个 WebappClassLoader
。tomcat7 默认使用 WebappClassLoader
类加载器,而 tomcat8 默认使用 ParallelWebappClassLoader
,支持并行加载类的特性,这也算是 tomcat8 做的一些优化吧,而实际上也是利用 jdk 的功能,需要同时满足以下两点才支持并行加载类,并且一旦注册了并行加载的能力,就不能回退了
1、 没有创建调用者的实例
2、 调用者的所有超类(除了类对象)都是并行注册的
基于上面两点,因此,ParallelWebappClassLoader
在 static 代码块中注册并行加载机制,而它的父类 URLClassLoader
父类也是具有并行能力的,关键代码如下所示:
public class ParallelWebappClassLoader extends WebappClassLoaderBase {static {boolean result = ClassLoader.registerAsParallelCapable();if (!result) {log.warn(sm.getString("webappClassLoaderParallel.registrationFailed"));}}// 省略无关代码...
WebappClassLoader
的类图如下所示,其中 WebappClassLoaderBase
实现了主要的逻辑,并且继承了 Lifecycle
,在 tomcat 组件启动、关闭时会完成资源的加载、卸载操作,例如在 start
过程会读取我们熟悉的 /WEB-INF/classes
、/WEB-INF/lib
资源,并且记录每个 jar 包的时间戳方便重载 jar 包;而在组件 stop
的时候,会清理已经加载的资源;destory
时会显式地触发 URLClassLoader.close()
。这个 Lifecycle
真是无处不在啊
单独的类加载器是无法获取 webapp 的资源信息的,因此 tomcat 引入了 WebappLoader
,便于访问 Context
组件的信息,同时为 Context
提供类加载的能力支持,下面我们分析下 WebappLoader
的底层实现
WebappLoader
我们先来看看 WebappLoader
几个重要的属性,内部持有 Context
组件,并且有个我们熟悉的 reloadable
参数,如果设为 true,则会开启类的热加载机制
public class WebappLoader extends LifecycleMBeanBaseimplements Loader, PropertyChangeListener {private WebappClassLoaderBase classLoader = null; // 默认使用ParallelWebappClassLoaderprivate Context context = null;private String loaderClass = ParallelWebappClassLoader.class.getName();private ClassLoader parentClassLoader = null; // 父加载器,默认为 catalina 类加载器private boolean reloadable = false; // 是否支持热加载类private String classpath = null;
在 tomcat 中,每个 webapp 对应一个 StandardContext
,在 start
过程便会实例化 WebappLoader
,并且调用其 start
方法完成初始化,包括创建 ParallelWebappClassLoader
实例,然后,还会启动 Context
的子容器。注意,这两个过程,都会将线程上下文类加载器指定为 ParallelWebappClassLoader
类加载器,在完成 webapp 相关的类加载之后,又将线程上下文类加载器设置为 catalina 类加载器。Context
容器的启动过程,这里便不再重复了,感兴趣的童鞋请查看前面的博文 webapp源码分析
StandardContext.javaprotected synchronized void startInternal() throws LifecycleException {// 实例化 Loader 实例,它是 tomcat 对于 ClassLoader 的封装,用于支持在运行期间热加载 class if (getLoader() == null) {WebappLoader webappLoader = new WebappLoader(getParentClassLoader());webappLoader.setDelegate(getDelegate());setLoader(webappLoader); // 使用了读写锁控制并发问题}// 将 Loader 中的 ParallelWebappClassLoader 绑定到当前线程中,并返回 catalian 类加载器ClassLoader oldCCL = bindThread();try {if (ok) {// 如果 Loader 是 Lifecycle 实现类,则启动该 LoaderLoader loader = getLoader();if (loader instanceof Lifecycle) {((Lifecycle) loader).start();}// 设置 ClassLoader 的各种属性setClassLoaderProperty("clearReferencesRmiTargets", getClearReferencesRmiTargets());// 省略……// 解除线程上下文类加载器绑定unbindThread(oldCCL);oldCCL = bindThread();// 发出 CONFIGURE_START_EVENT 事件,ContextConfig 会处理该事件,主要目的是加载 Context 的子容器fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);// 启动子容器for (Container child : findChildren()) {if (!child.getState().isAvailable()) {child.start();}}}} finally {// Unbinding threadunbindThread(oldCCL);}