tomcat 如何加载 class 文件? 如何加载 lib 目录下的jar 文件?
在 tomcat 中存在一个 Loader接口:
其中的主要功能包括
container: Loader 是与一个context关联的,负责在context下的所有servlet解析的时候使用
可以设置reloadable参数,repository是指需要加载的资源的路径
其子类 WebappLoader 负责了以上方法的实现,并且实现了其他一些接口(implements Lifecycle, Loader, PropertyChangeListener, MBeanRegistration )
具体的解析任务将会交给 webappclassLoader类
在tomcat的classLoader运作机制中,主要将资源分为3层
顶层是 webapp的classes 资源和lib 目录下的 jar 资源
第二层是 tomcat 自己lib目录下的资源
底层是 java jdk下的lib资源
当容器根据一个url寻找资源时,默认是从顶层开始。
WebappLoader的 主要 任务(start方法):
1. 创建classLoader
private WebappClassLoader createClassLoader() throws Exception { Class clazz = Class.forName(loaderClass); WebappClassLoader classLoader = null; if (parentClassLoader == null) { parentClassLoader = container.getParentClassLoader(); } Class[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); return classLoader; }
这里的classLoader默认使用 WebappClassLoader
2. 配置 classLoader:
classLoader.setResources(container.getResources()); classLoader.setDelegate(this.delegate); classLoader.setSearchExternalFirst(searchExternalFirst); if (container instanceof StandardContext) { classLoader.setAntiJARLocking( ((StandardContext) container).getAntiJARLocking()); classLoader.setClearReferencesStopThreads( ((StandardContext) container).getClearReferencesStopThreads()); classLoader.setClearReferencesStopTimerThreads( ((StandardContext) container).getClearReferencesStopTimerThreads()); classLoader.setClearReferencesThreadLocals( ((StandardContext) container).getClearReferencesThreadLocals()); classLoader.setClearReferencesHttpClientKeepAliveThread( ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread()); }关于container.getResources() 对象 DirContext 将在之后分析
antiJARLocking 属性, 在classLoader. getResource方法中可见
clearReferencesStopThreads 见 classLoader.clearReferences -> clearReferencesThreads方法(关闭web app start的任何线程)
clearReferencesStopTimerThreads 见 classLoader.clearReferences -> clearReferencesThreads方法 用于 判断是否调用 clearReferencesStopTimerThread 方法, 默认 false
clearReferencesThreadLocals 见 clearReferences 方法
clearReferencesHttpClientKeepAliveThread 见 clearReferences 方法
3. 加载资源
for (int i = 0; i < repositories.length; i++) { classLoader.addRepository(repositories[i]); }
4. 加载class文件和lib路径:
setRepositories 方法
4.1 设置默认工作路径:
if (!(container instanceof Context)) return; ServletContext servletContext = ((Context) container).getServletContext(); if (servletContext == null) return; loaderRepositories=new ArrayList(); // Loading the work directory File workDir = (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR); if (workDir == null) { log.info("No work dir for " + servletContext); } if( log.isDebugEnabled()) log.debug(sm.getString("webappLoader.deploy", workDir.getAbsolutePath())); classLoader.setWorkDir(workDir);
4.2 得到classes 文件夹路径 资源
String classesPath = "/WEB-INF/classes"; DirContext classes = null; try { Object object = resources.lookup(classesPath); if (object instanceof DirContext) { classes = (DirContext) object; } } catch(NamingException e) { // Silent catch: it's valid that no /WEB-INF/classes collection // exists } 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(); if (!copyDir(classes, classRepository)) { throw new IOException( sm.getString("webappLoader.copyFailure")); } } if(log.isDebugEnabled()) log.debug(sm.getString("webappLoader.classDeploy", classesPath, classRepository.getAbsolutePath())); // Adding the repository to the class loader classLoader.addRepository(classesPath + "/", classRepository); loaderRepositories.add(classesPath + "/" ); }
并且 把 相关的 classPath classRepository 设置到 classLoader中
4.3 lib 中的jar资源导入
String libPath = "/WEB-INF/lib"; classLoader.setJarPath(libPath); DirContext libDir = null; // Looking up directory /WEB-INF/lib in the context try { Object object = resources.lookup(libPath); if (object instanceof DirContext) libDir = (DirContext) object; } catch(NamingException e) { // Silent catch: it's valid that no /WEB-INF/lib collection // exists } 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(); } // Looking up directory /WEB-INF/lib in the context NamingEnumeration<NameClassPair> enumeration = null; try { enumeration = libDir.list(""); } catch (NamingException e) { IOException ioe = new IOException(sm.getString( "webappLoader.namingFailure", libPath)); ioe.initCause(e); throw ioe; } while (enumeration.hasMoreElements()) { NameClassPair ncPair = enumeration.nextElement(); String filename = libPath + "/" + ncPair.getName(); if (!filename.endsWith(".jar")) continue; // Copy JAR in the work directory, always (the JAR file // would get locked otherwise, which would make it // impossible to update it or remove it at runtime) File destFile = new File(destDir, ncPair.getName()); if( log.isDebugEnabled()) log.debug(sm.getString("webappLoader.jarDeploy", filename, destFile.getAbsolutePath())); // Bug 45403 - Explicitly call lookup() on the name to check // that the resource is readable. We cannot use resources // returned by listBindings(), because that lists all of them, // but does not perform the necessary checks on each. Object obj = null; try { obj = libDir.lookup(ncPair.getName()); } catch (NamingException e) { IOException ioe = new IOException(sm.getString( "webappLoader.namingFailure", filename)); ioe.initCause(e); throw ioe; } if (!(obj instanceof Resource)) continue; Resource jarResource = (Resource) obj; if (copyJars) { if (!copy(jarResource.streamContent(), new FileOutputStream(destFile))) { throw new IOException( sm.getString("webappLoader.copyFailure")); } } try { JarFile jarFile = new JarFile(destFile); classLoader.addJar(filename, jarFile, destFile); } catch (Exception ex) { // Catch the exception if there is an empty jar file // Should ignore and continue loading other jar files // in the dir } loaderRepositories.add( filename );
5. 设置 classpath, 设置webapp的环境变量
// Validate our current state information if (!(container instanceof Context)) return; ServletContext servletContext = ((Context) container).getServletContext(); if (servletContext == null) return; if (container instanceof StandardContext) { String baseClasspath = ((StandardContext) container).getCompilerClasspath(); if (baseClasspath != null) { servletContext.setAttribute(Globals.CLASS_PATH_ATTR, baseClasspath); return; } } StringBuffer classpath = new StringBuffer(); // Assemble the class path information from our class loader chain ClassLoader loader = getClassLoader(); int layers = 0; int n = 0; while (loader != null) { if (!(loader instanceof URLClassLoader)) { String cp=getClasspath( loader ); if( cp==null ) { log.info( "Unknown loader " + loader + " " + loader.getClass()); break; } else { if (n > 0) classpath.append(File.pathSeparator); classpath.append(cp); n++; } break; //continue; } 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:")) repository = servletContext.getRealPath(repository.substring(5)); else continue; if (repository == null) continue; if (n > 0) classpath.append(File.pathSeparator); classpath.append(repository); n++; } loader = loader.getParent(); layers++; } this.classpath=classpath.toString(); // Store the assembled class path as a servlet context attribute servletContext.setAttribute(Globals.CLASS_PATH_ATTR, classpath.toString());
通过循环将各个loader中的资源路径加载到classpath 变量中
6. setPermissions 设置资源 权限
判断是否 存在 SecurityManager
if (!Globals.IS_SECURITY_ENABLED) return; if (!(container instanceof Context)) return;
只允许根目录的读写权限,和classes 、lib目录的读写删权限,其他根目录下的资源不能碰
URL rootURL = servletContext.getResource("/"); classLoader.addPermission(rootURL); String contextRoot = servletContext.getRealPath("/"); if (contextRoot != null) { try { contextRoot = (new File(contextRoot)).getCanonicalPath(); classLoader.addPermission(contextRoot); } catch (IOException e) { // Ignore } }
URL classesURL = servletContext.getResource("/WEB-INF/classes/"); classLoader.addPermission(classesURL); URL libURL = servletContext.getResource("/WEB-INF/lib/"); classLoader.addPermission(libURL);
7. 启动 classloader, 绑定 JMX
if (classLoader instanceof Lifecycle) ((Lifecycle) classLoader).start(); // Binding the Webapp class loader to the directory context DirContextURLStreamHandler.bind ((ClassLoader) classLoader, this.container.getResources()); StandardContext ctx=(StandardContext)container; Engine eng=(Engine)ctx.getParent().getParent(); String path = ctx.getPath(); if (path.equals("")) { path = "/"; } ObjectName cloname = new ObjectName (ctx.getEngineName() + ":type=WebappClassLoader,path=" + path + ",host=" + ctx.getParent().getName()); Registry.getRegistry(null, null) .registerComponent(classLoader, cloname, null);