背景 工作需要将多个 Playframework 的应用 在一个 java 进程中跑起来 Java 出身的我自然的想到 tomcat 可以运行多个 war,也就等同于多个 Playframework 在一个 Java 进程中跑起来
Tomcat 如何运行多个 War 首先通过 Tomcat 的 startup.sh 跳到 catalina.sh,
1 2 3 4 5 6 7 8 eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ -D$ ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS \"" \ -classpath "\"$CLASSPATH \"" \ -Dcatalina.base="\"$CATALINA_BASE \"" \ -Dcatalina.home="\"$CATALINA_HOME \"" -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start \ > > "$CATALINA_OUT " 2>&1 "&"
进入 org.apache.catalina.startup.Bootstrap 这个 tomcat 的启动入口
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 public void init () throws Exception { initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina" ); Object startupInstance = startupClass.getConstructor().newInstance(); 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; } 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 ); } }
Bootstrap 中的 调用 Init 初始化了 catalinaLoader 和 sharedLoader 是 commonClassLoder 的子类
commonClassloadercatalinaLoader sharedLoader 然后看 Bootstrap.start, 实际上是 org.apache.catalina.startup.Catalina.start() start() 会先 load Server, 将 tomcat 一系列的复杂组件进行初始化操作
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 public void load () { initDirs(); initNaming(); File file = configFile(); Digester digester = createStartDigester(); getServer().setCatalina(this ); getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); initStreams(); try { getServer().init(); } catch (LifecycleException e) { if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE" )) { throw new java.lang.Error(e); } else { log.error(sm.getString("catalina.initError" ), e); } } } protected Digester createStartDigester () { Digester digester = new Digester(); digester.setValidating(false ); digester.setRulesValidation(true ); Map<Class<?>, List<String>> fakeAttributes = new HashMap<>(); List<String> objectAttrs = new ArrayList<>(); objectAttrs.add("className" ); fakeAttributes.put(Object.class, objectAttrs); List<String> contextAttrs = new ArrayList<>(); contextAttrs.add("source" ); fakeAttributes.put(StandardContext.class, contextAttrs); digester.setFakeAttributes(fakeAttributes); ... return digester; } public void start () { if (getServer() == null ) { load(); } ... getServer().start(); ... if (await) { await(); stop(); } } public void load () { getServer().init(); }
getServer().init() 的逻辑 在 Server 的子类 StandardServer 中实现
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 public abstract class LifecycleBase implements Lifecycle { @Override public final synchronized void init () throws LifecycleException { if (!state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try { setStateInternal(LifecycleState.INITIALIZING, null , false ); initInternal(); setStateInternal(LifecycleState.INITIALIZED, null , false ); } catch (Throwable t) { handleSubClassException(t, "lifecycleBase.initFail" , toString()); } } } public final class StandardServer extends LifecycleMBeanBase implements Server { @Override protected void initInternal () throws LifecycleException { if (getCatalina() != null ) { ClassLoader cl = getCatalina().getParentClassLoader(); while (cl != null && cl != ClassLoader.getSystemClassLoader()) { if (cl instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) cl).getURLs(); for (URL url : urls) { if (url.getProtocol().equals("file" )) { try { File f = new File (url.toURI()); if (f.isFile() && f.getName().endsWith(".jar" )) { ExtensionValidator.addSystemResource(f); } } catch (URISyntaxException e) { } catch (IOException e) { } } } } cl = cl.getParent(); } } } }
StandardServer.init 逻辑实际是 继承自 LifecycleBase 并调用 自己的 initInternal StandardServer.getCatalina().getParentClassLoader(); 实际上获取的就是 在 Catalina.load 过程中 设置的 Catalina 实例的属性 parentClassLoader 注释上写的很好,就是用 Catalina 里的 commonClassLoader 和 sharedClassLoader 去加载资源
接着看 StandardContext
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 85 86 87 88 89 90 public class StandardContext extends ContainerBase implements Context , NotificationEmitter { protected synchronized void startInternal () throws LifecycleException { if (getLoader() == null ) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); } ClassLoader oldCCL = bindThread(); try { if (ok) { Loader loader = getLoader(); if (loader instanceof Lifecycle) { ((Lifecycle) loader).start(); } unbindThread(oldCCL); oldCCL = bindThread(); } } finally { unbindThread(oldCCL); } } protected ClassLoader bindThread () { ClassLoader oldContextClassLoader = bind(false , null ); if (isUseNaming()) { try { ContextBindings.bindThread(this , getNamingToken()); } catch (NamingException e) { } } return oldContextClassLoader; } public ClassLoader bind (boolean usePrivilegedAction, ClassLoader originalClassLoader) { Loader loader = getLoader(); ClassLoader webApplicationClassLoader = null ; if (loader != null ) { webApplicationClassLoader = loader.getClassLoader(); } if (originalClassLoader == null ) { if (usePrivilegedAction) { PrivilegedAction<ClassLoader> pa = new PrivilegedGetTccl(); originalClassLoader = AccessController.doPrivileged(pa); } else { originalClassLoader = Thread.currentThread().getContextClassLoader(); } } if (webApplicationClassLoader == null || webApplicationClassLoader == originalClassLoader) { return null ; } ThreadBindingListener threadBindingListener = getThreadBindingListener(); if (usePrivilegedAction) { PrivilegedAction<Void> pa = new PrivilegedSetTccl(webApplicationClassLoader); AccessController.doPrivileged(pa); } else { Thread.currentThread().setContextClassLoader(webApplicationClassLoader); } if (threadBindingListener != null ) { try { threadBindingListener.bind(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString( "standardContext.threadBindingListenerError" , getName()), t); } } return originalClassLoader; }
核心逻辑就是 利用 Thread.currentThread().setContextClassLoader(webApplicationClassLoader) 来改变当前线程上下文中的 classloader 实例 通过 WebappClassLoader.start() 去加载 每个 war 里面的资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public void start () throws LifecycleException { state = LifecycleState.STARTING_PREP; WebResource[] classesResources = resources.getResources("/WEB-INF/classes" ); for (WebResource classes : classesResources) { if (classes.isDirectory() && classes.canRead()) { localRepositories.add(classes.getURL()); } } 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; }
总结 Classloader 可有效的将 class 资源进行隔离 Class.getClass.getClassLoader 获取的是加载 这个 class 的 classLoader Thread.currentThread().getClassLoader() 获取的是当前线程上下文的 classloader Thread.currentThread().setClassLoader(自定义classLoader) 可加载指定的 class 资源