springboot实现定义了一个符合双亲委派的类TomcatEmbeddedWebappClassLoader extends WebappClassLoader,因为springboot只针对一个应用运行的场景,所以无需打破双亲委派。
使用IDEA调试Tomcat代码时,在Project Settings - Libraries里把tomcat/lib加进来就可以了。
一、前置问题
tomcat的类加载破坏了双亲委派吗?
为什么要破坏双亲委派?
tomcat的类加载器的继承关系?
tomcat的类加载层级和机制?
二、Tomcat的类加载器类图
说明: 1)WebappClassLoaderBase及其子类是Tomcat自己实现的类加载器; 2)其默认打破了双亲委派机制,用来实现Tomcat多应用间的隔离;
三、Tomcat类加载层级
说明: 1)Bootstrap类加载器:JDK的启动类加载器和扩展类加载器 2)System类加载器:JDK的应用类加载器 3)Common,Catalina,Shared类加载器:是三个URLClassLoader实例,分别指定了不同的path; 4)Webapp类加载器:Tomcat定义的类加载器WebappClassLoaderBase及其自类;
四、Tomcat类加载器创建 1. 执行tomcat启动脚本catalina.sh,运行主类org.apache.catalina.startup.Bootstrap#main ; 2. Bootstrap#main会执行以下操作 2.1 实例化Bootstrap并调用其init方法(此处暂不考虑以service运行); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static void main (String args[]) { if (daemon == null ) { Bootstrap bootstrap = new Bootstrap (); try { bootstrap.init(); } catch (Throwable t) { handleThrowable(t); t.printStackTrace(); return ; } daemon = bootstrap; } else { Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); } ... }
Bootstrap#init方法会执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public void init () throws Exception { initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); if (log.isDebugEnabled()) log.debug("Loading startup class" ); Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina" ); Object startupInstance = startupClass.newInstance(); if (log.isDebugEnabled()) log.debug("Setting startup class properties" ); String methodName = "setParentClassLoader" ; Class<?> paramTypes[] = new Class [1 ]; paramTypes[0 ] = Class.forName("java.lang.ClassLoader" ); Object paramValues[] = new Object [1 ]; paramValues[0 ] = sharedLoader; Method method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); catalinaDaemon = startupInstance; }
2.1.1 初始化类加载器:common,server,shared。 1)类型为URLClassLoader,未打破双亲委派; 2)其path从catalina.properties中读取common.loader,server.loader,shared.loader的键值; 3)指定parent关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void initClassLoaders () { try { commonLoader = createClassLoader("common" , null ); if ( commonLoader == null ) { 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 ); }
Note: tomcat-8.5.56里把common, server, shared合成了一个。
1 2 3 4 5 6 7 8 private ClassLoader createClassLoader (String name, ClassLoader parent) throws Exception { String value = CatalinaProperties.getProperty(name + ".loader" ); if (value != null && !value.equals("" )) { } else { return parent; } }
2.1.2 设置server类加载器为当前线程的ContextClassLoader 2.1.3 实例化org.apache.catalina.startup.Catalina,并把shared类加载器设为其父类加载器 2.2 Bootstrap#main接收到startd命令时会调用bootstrap#load,bootstrap#start 1 2 3 4 5 if (command.equals("startd" )) { args[args.length - 1 ] = "start" ; daemon.load(args); daemon.start(); }
2.2.1 load方法 1)通过Digester读取server.xml来初始化Catalina#server;
1 2 3 4 digester.addRuleSet(new HostRuleSet ("Server/Service/Engine/" )); digester.addRuleSet(new ContextRuleSet ("Server/Service/Engine/Host/" )); digester.addRule("Server/Service/Engine" ,new SetParentClassLoaderRule (parentClassLoader));
2)HostRuleSet里设置
1 2 3 4 5 6 7 digester.addObjectCreate(prefix + "Host" , "org.apache.catalina.core.StandardHost" , "className" ); digester.addSetNext(prefix + "Host" , "addChild" , "org.apache.catalina.Container" );
3)ContextRuleSet里设置设置Host的Context为StarndardContext,然后设置StandardContext的loader为WebappLoader
1 2 3 4 5 6 digester.addObjectCreate(prefix + "Context" , "org.apache.catalina.core.StandardContext" , "className" ); digester.addObjectCreate(prefix + "Context/Loader" , "org.apache.catalina.loader.WebappLoader" , "className" );
4)StandardContext把WebappLoader的context设为自身
1 loader.setContext(this );
5)WebappLoader里把StandardContext的parentClassLoader设为应用类加载器的parent。而StandardContext的parentClassLoader为空,继续找其parent(StandardHOST)的paretClassLoader,即shared类加载器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private String loaderClass = ParallelWebappClassLoader.class.getName();private WebappClassLoaderBase createClassLoader () throws Exception { Class<?> clazz = Class.forName(loaderClass); WebappClassLoaderBase classLoader = null ; if (parentClassLoader == null ) { parentClassLoader = context.getParentClassLoader(); } Class<?>[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor<?> constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoaderBase) constr.newInstance(args); return classLoader; }
五、应用类加载器加载机制
不开启委托(默认情况,delegate=false),破坏了双亲委派 1)检查类是否被加载;有则返回,没有则继续执行; 2)使用JavaSE类加载器加载类;若成功则返回,否则继续执行; 3)应用类加载器尝试加载类;若成功则返回,否则继续执行; 4)应用类加载器委托给父加载器(shared类加载器)加载;若成功则返回,否则抛出ClassNotFoundException;
开启委托(delegate=true), 符合双亲委派 1)检查类是否被加载;有则返回,没有则继续执行; 2)使用JavaSE类加载器加载类;若成功则返回,否则继续执行; 3)应用类加载器委托给父加载器(shared类加载器)加载;若成功则返回,否则继续执行; 4)应用类加载器尝试加载类;若成功则返回,否则抛出ClassNotFoundException;
Note: JavaSE类加载器默认为启动类加载器。(应用类加载器的构造器里指定了其指向String.class.getClassLoader();)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> clazz = null ; clazz = findLoadedClass0(name); if (clazz != null ) { if (resolve) resolveClass(clazz); return (clazz); } clazz = findLoadedClass(name); if (clazz != null ) { if (resolve) resolveClass(clazz); return (clazz); } String resourceName = binaryNameToPath(name, false ); ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; try { tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null ); } catch (Throwable t) { tryLoadingFromJavaseLoader = true ; } if (tryLoadingFromJavaseLoader) { try { clazz = javaseLoader.loadClass(name); if (clazz != null ) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { } } boolean delegateLoad = delegate || filter(name, true ); if (delegateLoad) { try { clazz = Class.forName(name, false , parent); if (clazz != null ) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { } } try { clazz = findClass(name); if (clazz != null ) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { } if (!delegateLoad) { try { clazz = Class.forName(name, false , parent); if (clazz != null ) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { } } throw new ClassNotFoundException (name); }
参考
https://tomcat.apache.org/tomcat-8.5-doc/class-loader-howto.html
https://pt.slideshare.net/wptree/class-loader-incloud/5