Tomcat源码学习--WebAppClassLoader类加载机制
2017-11-01 20:10
573 查看
上一篇博客JVM-类加载机制 中我们已经对JVM的类加载机制有所了解, 这篇博客我们了解一下Tomcat的类加载机制。
Tomcat的类加载器可以分为两部分,第一个是Tomcat自身所使用的类加载器,会加载jre的lib包及tomcat的lib包的类,遵循类加载的双亲委派机制;第二个是每个Web应用程序用的,每个web应用程序都有自己专用的WebappClassLoader,优先加载/web-inf/lib下的jar中的class文件,这样就隔离了每个web应用程序的影响,但是webappClassLoader没有遵循类加载的双亲委派机制,处理的方法就是在使用webappClassLoader的load加载类会进行过滤,如果有些类被过滤掉还是通过双亲委派机制优先从父加载器中加载类。
接下来我们看看这3个类加载器都使用了在什么地方,commonLoader除了在initClassLoader处使用外并没有在其他地方使用,它是作为catalinaLoader和sharedLoader的父类加载器;catalinaLoader在init方法中被设置为当前线程的类加载器。
在WebappClassLoaderBase中重写了ClassLoader的loadClass方法,在这个实现方法中我们可以一窥tomcat真正的类加载机制,简单来说web应用首先还是去尝试加载jre下面的类这个流程是不可变的,接下来web应用就可以根据设置首先是加载自己应用下的class文件还是tomcat的lib目录下的class文件了,实现逻辑看loadClass的实现机制还是比较简单的,所有通过设置web应用可以遵循类加载的双亲委派机制或者不遵循双亲委派机制了。
Tomcat的类加载器可以分为两部分,第一个是Tomcat自身所使用的类加载器,会加载jre的lib包及tomcat的lib包的类,遵循类加载的双亲委派机制;第二个是每个Web应用程序用的,每个web应用程序都有自己专用的WebappClassLoader,优先加载/web-inf/lib下的jar中的class文件,这样就隔离了每个web应用程序的影响,但是webappClassLoader没有遵循类加载的双亲委派机制,处理的方法就是在使用webappClassLoader的load加载类会进行过滤,如果有些类被过滤掉还是通过双亲委派机制优先从父加载器中加载类。
一、Tomcat类加载器初始化
在tomcat调用Bootstrap进行启动时会调用initClassLoaders创建3个ClassLoader,它们分别是commonLoader,catalinaLoader,sharedLoader,遵循双亲委派机制。commonLoader会根据tomcat的conf/catalina.properties中的配置加载tomcat自身的jar,然后将这个类加载器作为整个tomcat容器的父类加载器。common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"ClassLoader commonLoader = null; ClassLoader catalinaLoader = null; ClassLoader sharedLoader = null; private void initClassLoaders() { try { commonLoader = createClassLoader("common", null); if( commonLoader == null ) { // no config file, default to this loader - we might be in a 'single' env. commonLoader=this.getClass().getClassLoader(); } catalinaLoader = createClassLoader("server", commonLoader); sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); } }在createClassLoader中会加载tomcat的lib/*.jar下的所有jar中的class文件。
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; value = replace(value); List<Repository> repositories = new ArrayList<>(); String[] repositoryPaths = getPaths(value); for (String repository : repositoryPaths) { // Check for a JAR URL repository try { @SuppressWarnings("unused") URL url = new URL(repository); repositories.add( new Repository(repository, RepositoryType.URL)); continue; } catch (MalformedURLException e) { // Ignore } // Local repository if (repository.endsWith("*.jar")) { repository = repository.substring (0, repository.length() - "*.jar".length()); repositories.add( new Repository(repository, RepositoryType.GLOB)); } else if (repository.endsWith(".jar")) { repositories.add( new Repository(repository, RepositoryType.JAR)); } else { repositories.add( new Repository(repository, RepositoryType.DIR)); } } return ClassLoaderFactory.createClassLoader(repositories, parent); }
接下来我们看看这3个类加载器都使用了在什么地方,commonLoader除了在initClassLoader处使用外并没有在其他地方使用,它是作为catalinaLoader和sharedLoader的父类加载器;catalinaLoader在init方法中被设置为当前线程的类加载器。
public void init() throws Exception { //.....省略部分代码 //将catalinaLoader设置为当前线程的类加载器 Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); //.....省略部分代码 }sharedLoader类加载器作为参数调用了Catalina的setParentClassLoader方法,成为了整个Catalina容器的父类加载器,当然也是WebAppClassLoader的父类加载器。
//Catalina类的setParentClassLoader方法 String methodName = "setParentClassLoader"; Class<?> paramTypes[] = new Class[1]; paramTypes[0] = Class.forName("java.lang.ClassLoader"); Object paramValues[] = new Object[1]; //sharedLoader作为参数调用setParentClassLoader方法 paramValues[0] = sharedLoader; Method method = startupInstance.getClass().getMethod(methodName, paramTypes); //调用Catalina类的setParentClassLoader方法参数值为sharedLoader method.invoke(startupInstance, paramValues);
二、WebAppClassLoader应用类加载器
在tomcat中对每个应用都有一个WebAppClassLoader用来隔绝不同应用之前的class文件,因此WebAppClassLoader的创建工作也是在Context中进行的,在StandardContext的startInternal方法开启一个web应用时会创建类加载器。if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader());//添加父类加载器 webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); }同时会启动类加载器
Loader loader = getLoader(); if (loader instanceof Lifecycle) { ((Lifecycle) loader).start(); }在WebappClassLoader的父类WebappClassLoaderBase中实现了start方法
@Override public void start() throws LifecycleException { state = LifecycleState.STARTING_PREP; //加载web应用的所有class文件 WebResource classes = resources.getResource("/WEB-INF/classes"); if (classes.isDirectory() && classes.canRead()) { localRepositories.add(classes.getURL()); } //加载web应用lib目录下在jar文件 WebResource[] jars = resources.listResources("/WEB-INF/lib"); for (WebResource jar : jars) { if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) { localRepositories.add(jar.getURL()); jarModificationTimes.put( jar.getName(), Long.valueOf(jar.getLastModified())); } } state = LifecycleState.STARTED; }
在WebappClassLoaderBase中重写了ClassLoader的loadClass方法,在这个实现方法中我们可以一窥tomcat真正的类加载机制,简单来说web应用首先还是去尝试加载jre下面的类这个流程是不可变的,接下来web应用就可以根据设置首先是加载自己应用下的class文件还是tomcat的lib目录下的class文件了,实现逻辑看loadClass的实现机制还是比较简单的,所有通过设置web应用可以遵循类加载的双亲委派机制或者不遵循双亲委派机制了。
@Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { if (log.isDebugEnabled()) log.debug("loadClass(" + name + ", " + resolve + ")"); Class<?> clazz = null; // Log access to stopped class loader checkStateForClassLoading(name); // (0) Check our previously loaded local class cache //从当前ClassLoader的本地缓存中加载类,如果找到则返回 clazz = findLoadedClass0(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.1) Check our previously loaded class cache // 本地缓存没有的情况下,调用ClassLoader的findLoadedClass方法查看jvm是否已经加载过此类,如果已经加载则直接返回。 clazz = findLoadedClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.2) Try loading the class with the system class loader, to prevent // the webapp from overriding Java SE classes. This implements // SRV.10.7.2 //通过系统的来加载器加载此类,这里防止应用写的类覆盖了J2SE的类,这句代码非常关键,如果不写的话, //就会造成你自己写的类有可能会把J2SE的类给替换调,另外假如你写了一个javax.servlet.Servlet类,放在当前应用的WEB-INF/class中, //如果没有此句代码的保证,那么你自己写的类就会替换到Tomcat容器Lib中包含的类。 String resourceName = binaryNameToPath(name, false); ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; try { // Use getResource as it won't trigger an expensive // ClassNotFoundException if the resource is not available from // the Java SE class loader. However (see // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for // details) when running under a security manager in rare cases // this call may trigger a ClassCircularityError. tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null); } catch (ClassCircularityError cce) { // The getResource() trick won't work for this class. We have to // try loading it directly and accept that we might get a // ClassNotFoundException. tryLoadingFromJavaseLoader = true; } if (tryLoadingFromJavaseLoader) { try { clazz = javaseLoader.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } } if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) { try { securityManager.checkPackageAccess(name.substring(0,i)); } catch (SecurityException se) { String error = "Security Violation, attempt to use " + "Restricted Class: " + name; log.info(error, se); throw new ClassNotFoundException(error, se); } } } //判断是否需要委托给父类加载器进行加载,delegate属性默认为false,那么delegatedLoad的值就取决于filter的返回值了,filter中是优先加载tomcat的lib下的class文件 //filter方法中根据包名来判断是否需要进行委托加载,默认情况下会返回false.因此delegatedLoad为false boolean delegateLoad = delegate || filter(name, true); // (1) Delegate to our parent if requested //因为delegatedLoad为false,那么此时不会委托父加载器去加载,这里其实是没有遵循parent-first的加载机制。 if (delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader1 " + parent); try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } } // (2) Search local repositories //调用findClass方法在webapp级别进行加载 if (log.isDebugEnabled()) log.debug(" Searching local repositories"); try { clazz = findClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from local repository"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } // (3) Delegate to parent unconditionally //如果还是没有加载到类,并且不采用委托机制的话,则通过父类加载器去加载。 if (!delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader at end: " + parent); try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } } } throw new ClassNotFoundException(name); }
相关文章推荐
- android应用程序窗口框架学习(2)-view绘制流程源代码解析-setContentView与LayoutInflater加载解析机制源码分析
- Tomcat类加载器机制(Tomcat源码解析六)
- Tomcat源码解读系列(四)——Tomcat类加载机制概述
- Tomcat WebappClassLoader 类加载机制源码分析
- 深入java虚拟机h5房卡斗牛棋牌源码搭建学习 -- 类的加载机制
- Tomcat源码解读系列四Tomcat类加载机制概述
- 通过DefaultListableBeanFactory加载.xml配置文件学习Spring-IoC容器注册/加载bean的机制(源码走读)
- tomcat6源码研究:tomcat类加载机制
- Tomcat源码解读系列(四)——Tomcat类加载机制概述
- Tomcat源码解读系列(四)——Tomcat类加载机制概述
- Tomcat源码学习(五)-- Tomcat_7.0.70 类加载体系分析
- (九)Tomcat源码解析 - WebApp类加载机制原理分析
- Tomcat、Websphere和Jboss类加载机制
- Tomcat源码学习记录--web服务器初步认识
- 源码学习:一个express().get方法的加载与调用
- tomcat 源码学习
- Android源码梳理(一):setContentView(...)与LayoutInflater的加载机制分析
- Tomcat 源码学习之JIoEndpoint
- 75篇关于Tomcat源码和机制的文章
- (精)tomcat 源码学习