通过学习tomcat载入器了解下tomcat是如何载入整个webapp应用项目的

作者: admin 分类: Tomcat 发布时间: 2019-11-20 14:10  阅读: 32 views

servlet容器需要实现一个自定义的载入器,而不能简单地使用系统的类载入器,因为servlet容器不应该完全信任它正在运行的servlet类。如果使用系统类的载入器载入某个servlet类所使用的全部类,那么servlet就能访问所有的类,包括当期运行的java虚拟机中环境变量指明的所有类、库。这是非常危险的。servlet应该只允许载入WEB-INF/classes目录及其子目录下的类,和从部署的库到WEB-INFO/lib目录载入类。

Tomcat中需要实现自定义载入器的另一个原因是,为了提供自动重载的功能,即当web-info/classes目录或者web-info/lib目录下的类发生变化时,web应用程序会重新载入这些类。在tomcat载入器的实现中,类载入器使用一个额外的线程来不断地检查servlet类和其他类的文件时间戳,若要支持自动重载功能,载入器必须实现org.apache.catalina.loader.Reloader接口。

JAVA的类加载器

  • 根加载器(bootstrap class loader),加载jdk_home/lib/rt.jar
  • 扩展加载器(extension class loader),加载jdk_home/jre/lib/*.jar
  • 系统类加载器(system class loader),加载项目中的jar文件
  • 自定义加载器,加载外部类或其他

Tomcat的载入器通常会与一个Context级别的servlet容器相关联,loader接口的getContainer()方法和setContainer()方法用来将载入器与某个servlet容器相关联。如果context容器中的一个或多个类被修改了,载入器也可以支持对类的自动重载。这样,servlet程序猿就可以重新编译servlet类及相关类,并重新载入而不需要重新启动Tomcat。Loader接口使用modified()方法来支持类的自动重载。

在默认情况下,Context接口的标准实现(org.apache.catalina.core.StandardContexgt)是禁用了自动重载功能的。如果想启用自动重载共鞥,需要在server.xml文件中添加context元素,如下:

<Context path="/myApp" docBase="myApp" debug="0” reloadable="true" />

相关接口及类代码:

相关接口

  1. Loader接口(定义载入web应用程序,还有一些方法对仓库的集合进行操作)
package org.apache.catalina;

import java.beans.PropertyChangeListener;

public abstract interface Loader
{
  public abstract ClassLoader getClassLoader();

  public abstract Container getContainer();

  public abstract void setContainer(Container paramContainer);

  public abstract DefaultContext getDefaultContext();

  public abstract void setDefaultContext(DefaultContext paramDefaultContext);

  public abstract boolean getDelegate();

  public abstract void setDelegate(boolean paramBoolean);

  public abstract String getInfo();

  public abstract boolean getReloadable();

  public abstract void setReloadable(boolean paramBoolean);

  public abstract void addPropertyChangeListener(PropertyChangeListener paramPropertyChangeListener);

  public abstract void addRepository(String paramString);

  public abstract String[] findRepositories();

  public abstract boolean modified();

  public abstract void removePropertyChangeListener(PropertyChangeListener paramPropertyChangeListener);
}

  1. Reloader接口(自动重载功能)
public interface Reloader{

    public void addRepository(String respository);

    public String[] findRepositories();

    public boolean modified();

}

  1. WebappLoader类,是Loader接口的实现类。
    执行start方法后,完成几项工作:创建一个类载入器;设置仓库;设置类路径;设置访问权限;启动一个新线程来支持自动重载。

public void start() throws LifecycleException { if (this.started) { throw new LifecycleException(sm.getString("webappLoader.alreadyStarted")); } if (this.debug >= 1) { log(sm.getString("webappLoader.starting")); } this.lifecycle.fireLifecycleEvent("start", null); this.started = true; if (this.container.getResources() == null) { return; } URLStreamHandlerFactory streamHandlerFactory = new DirContextURLStreamHandlerFactory(); try { URL.setURLStreamHandlerFactory(streamHandlerFactory); } catch (Throwable t) {} try { this.classLoader = createClassLoader(); this.classLoader.setResources(this.container.getResources()); this.classLoader.setDebug(this.debug); this.classLoader.setDelegate(this.delegate); if ((this.container instanceof StandardContext)) { this.classLoader.setAntiJARLocking(((StandardContext)this.container).getAntiJARLocking()); } for (int i = 0; i < this.repositories.length; i++) { this.classLoader.addRepository(this.repositories[i]); } setRepositories(); setClassPath(); setPermissions(); if ((this.classLoader instanceof Lifecycle)) { this.classLoader.start(); } DirContextURLStreamHandler.bind(this.classLoader, this.container.getResources()); } catch (Throwable t) { throw new LifecycleException("start: ", t); } validatePackages(); if (this.reloadable) { log(sm.getString("webappLoader.reloading")); try { threadStart(); } catch (IllegalStateException e) { throw new LifecycleException(e); } } } ## 创建加载器 private WebappClassLoader createClassLoader() throws Exception { Class clazz = Class.forName(this.loaderClass); WebappClassLoader classLoader = null; if (this.parentClassLoader == null) { classLoader = (WebappClassLoader)clazz.newInstance(); } else { Class[] argTypes = { ClassLoader.class }; Object[] args = { this.parentClassLoader }; Constructor constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader)constr.newInstance(args); } return classLoader; } ## 设置访问权限(为类载入器设置访问相关目录的权限) private void setPermissions() { if (System.getSecurityManager() == null) { return; } if (!(this.container instanceof Context)) { return; } ServletContext servletContext = ((Context)this.container).getServletContext(); File workDir = (File)servletContext.getAttribute("javax.servlet.context.tempdir"); if (workDir != null) { try { String workDirPath = workDir.getCanonicalPath(); this.classLoader.addPermission(new FilePermission(workDirPath, "read,write")); this.classLoader.addPermission(new FilePermission(workDirPath + File.separator + "-", "read,write,delete")); } catch (IOException e) {} } try { URL rootURL = servletContext.getResource("/"); this.classLoader.addPermission(rootURL); String contextRoot = servletContext.getRealPath("/"); if (contextRoot != null) { try { contextRoot = new File(contextRoot).getCanonicalPath(); this.classLoader.addPermission(contextRoot); } catch (IOException e) {} } URL classesURL = servletContext.getResource("/WEB-INF/classes/"); this.classLoader.addPermission(classesURL); URL libURL = servletContext.getResource("/WEB-INF/lib/"); this.classLoader.addPermission(libURL); if (contextRoot != null) { if (libURL != null) { File rootDir = new File(contextRoot); File libDir = new File(rootDir, "WEB-INF/lib/"); try { String path = libDir.getCanonicalPath(); this.classLoader.addPermission(path); } catch (IOException e) {} } } else if (workDir != null) { if (libURL != null) { File libDir = new File(workDir, "WEB-INF/lib/"); try { String path = libDir.getCanonicalPath(); this.classLoader.addPermission(path); } catch (IOException e) {} } if (classesURL != null) { File classesDir = new File(workDir, "WEB-INF/classes/"); try { String path = classesDir.getCanonicalPath(); this.classLoader.addPermission(path); } catch (IOException e) {} } } } catch (MalformedURLException e) {} } //设置仓库、类载入器就能在WEB-INFO/classes目录中和部署的库到WEB-INFO/lib目录载入相关类 private void setRepositories() { if (!(this.container instanceof Context)) { return; } ServletContext servletContext = ((Context)this.container).getServletContext(); if (servletContext == null) { return; } File workDir = (File)servletContext.getAttribute("javax.servlet.context.tempdir"); if (workDir == null) { return; } log(sm.getString("webappLoader.deploy", workDir.getAbsolutePath())); this.classLoader.setWorkDir(workDir); DirContext resources = this.container.getResources(); String classesPath = "/WEB-INF/classes"; DirContext classes = null; try { Object object = resources.lookup(classesPath); if ((object instanceof DirContext)) { classes = (DirContext)object; } } catch (NamingException e) {} if (classes != null) { File classRepository = null; String absoluteClassesPath = servletContext.getRealPath(classesPath); if (absoluteClassesPath != null) { classRepository = new File(absoluteClassesPath); } else { classRepository = new File(workDir, classesPath); classRepository.mkdirs(); copyDir(classes, classRepository); } log(sm.getString("webappLoader.classDeploy", classesPath, classRepository.getAbsolutePath())); this.classLoader.addRepository(classesPath + "/", classRepository); } String libPath = "/WEB-INF/lib"; this.classLoader.setJarPath(libPath); DirContext libDir = null; try { Object object = resources.lookup(libPath); if ((object instanceof DirContext)) { libDir = (DirContext)object; } } catch (NamingException e) {} if (libDir != null) { boolean copyJars = false; String absoluteLibPath = servletContext.getRealPath(libPath); File destDir = null; if (absoluteLibPath != null) { destDir = new File(absoluteLibPath); } else { copyJars = true; destDir = new File(workDir, libPath); destDir.mkdirs(); } try { NamingEnumeration enumeration = resources.listBindings(libPath); while (enumeration.hasMoreElements()) { Binding binding = (Binding)enumeration.nextElement(); String filename = libPath + "/" + binding.getName(); if (filename.endsWith(".jar")) { File destFile = new File(destDir, binding.getName()); log(sm.getString("webappLoader.jarDeploy", filename, destFile.getAbsolutePath())); Resource jarResource = (Resource)binding.getObject(); if ((!copyJars) || (copy(jarResource.streamContent(), new FileOutputStream(destFile)))) { JarFile jarFile = new JarFile(destFile); this.classLoader.addJar(filename, jarFile, destFile); } } } } catch (NamingException e) {}catch (IOException e) { e.printStackTrace(); } } } ## 设置类路径 private void setClassPath() { if (!(this.container instanceof Context)) { return; } ServletContext servletContext = ((Context)this.container).getServletContext(); if (servletContext == null) { return; } StringBuffer classpath = new StringBuffer(); ClassLoader loader = getClassLoader(); int layers = 0; int n = 0; while ((layers < 3) && (loader != null) && ((loader instanceof URLClassLoader))) { URL[] repositories = ((URLClassLoader)loader).getURLs(); for (int i = 0; i < repositories.length; i++) { String repository = repositories[i].toString(); if (repository.startsWith("file://")) { repository = repository.substring(7); } else if (repository.startsWith("file:")) { repository = repository.substring(5); } else { if (!repository.startsWith("jndi:")) { continue; } repository = servletContext.getRealPath(repository.substring(5)); } if (repository != null) { if (n > 0) { classpath.append(File.pathSeparator); } classpath.append(repository); n++; } } loader = loader.getParent(); layers++; } servletContext.setAttribute("org.apache.catalina.jsp_classpath", classpath.toString()); } ## 如果WEB-INFO/classes目录或WEB-INFO/lib目录下的某些类被重新编译了,这个类会自动载入,无需重启Tomcat public void run() { if (this.debug >= 1) { log("BACKGROUND THREAD Starting"); } while (!this.threadDone) { threadSleep(); if (this.started) { try { if (!this.classLoader.modified()) { continue; } } catch (Exception e) { log(sm.getString("webappLoader.failModifiedCheck"), e); } continue; notifyContext(); } } if (this.debug >= 1) { log("BACKGROUND THREAD Stopping"); } }

Tomcat载入器WebappClassLoader类在载入时需要遵守如下规则:
1. 因为所有已经载入的类都会缓存起来,所以载入类时要先检查本地缓存;
2. 若本地缓存中没有,则检查上一层缓存,即调用java.lang.ClassLoader类的findLoadedClass()方法;
3. 若两个缓存中都没好友,则使用系统的类载入器进行加载,防止web应用程序中的类覆盖J2EE的类;
4. 若启用了SecurityManager,则检查是否允许载入该类。若该类是禁止载入的类,抛出ClassNotFoundException异常;
5. 若打开标志位delegate,或者待载入的类是属于包触发器中的包名,则调用父载入器来载入相关类。如果父类载入器为null,则使用系统的类载入器;
6. 从单签仓库中载入相关类;
7. 若当前仓库中没有需要的类,且标志位delegate关闭,则使用父类载入器。若父类载入器为null,则使用系统的类载入器进行加载。
8. 若未找到需要的类,抛出ClassNotFoundException异常。

这是整理自《How tamcat works》的第八章,学习了这一节,在结合之前的wrapper容器自动加载相关的内容。大致明白了tomcat是如何载入webapp程序的。用了这么久的tomcat服务器,感觉真是一款优秀的作品,给作者点赞。完整代码请看相关资源包。


   原创文章,转载请标明本文链接: 通过学习tomcat载入器了解下tomcat是如何载入整个webapp应用项目的

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

更多阅读