简单读!tomcat源码(一)启动与监听
tomcat 作为知名的web容器,很棒! 本文简单了从其应用命令开始拆解,让我们对他有清晰的了解,揭开神秘的面纱!(冗长的代码流水线,给你一目了然)
话分两头:
1. tomcat是如何启动的?
2. tomcat是如何接收请求的?
x. 应用程序是怎样接入tomcat的?
1. 从何处开始?
找到入口还是比较重要的,这个问题一般可以通过官网或者网上搜索资料直接获取。也可以自行摸索,如下,从shell开始找起。
/etc/init.d/tomcat8 start # 简单的 tomcat8 脚本封装
/usr/java/jdk1.8.0_101/bin/java org.apache.catalina.startup.Bootstrap "$@" start # 参考eval命令 eval "\"$_RUNJAVA\"" $JAVA_OPTS $CATALINA_OPTS \ -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ -Dcatalina.home="\"$CATALINA_HOME\"" \ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start "2>&1" | /usr/local/sbin/cronolog -S "$CATALINA_BASE"/logs/catalina_ln.out "$CATALINA_BASE"/logs/catalina.%Y-%m-%d-%H.out >> /dev/null & # 运行后得到结果 /usr/java/jdk1.8.0_101/bin/java -server -Xmx6144M -Xms1024M -Dfile.encoding=UTF-8 -Xloggc:/opt/tomcat7/logs/tomcat_app_gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tomcat7/logs/ -classpath /opt/tomcat8/bin/bootstrap.jar:/opt/tomcat8/bin/tomcat-juli.jar -Dcatalina.base="/opt/tomcat8" -Dcatalina.home="/opt/tomcat8" org.apache.catalina.startup.Bootstrap "$@" start "2>&1"
可以看到,tomcat最终是执行 org.apache.catalina.startup.Bootstrap 的 main() 方法,参数为 start ...
那么,我们就以此为起点,进行简单查看下 tomcat 的一些运行原理吧!
最后,要说明一点,也是非常重要的一点:如何打开tomcat源码?
tomcat官网下载源码后,使用idea直接打开tomcat源码即可。但是,此时我们看到java文件全是不认识的标识,此时我们还要做两件事:
1. 设置jdk版本,这是所有java代码都必须的过程;
2. 设置java/test目录为source目录,在project structure中设置。(因为tomcat并没有使用maven等工具管理代码,因此需要自行设置源码目录)
3. 也许你还需要设置下编译输出目录,在project中随便选择一个目录即可,但最好容易辨认,比如设置在当前项目目录的output目录就是不错的选择。
4. 然后你还是可能遇到很多的jar包的依赖问题,也许你需要使用ant先编译下。这太烦了!简便些,直接将项目转换为maven项目,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.apache.tomcat</groupId> <artifactId>Tomcat8.0</artifactId> <name>Tomcat8.0</name> <version>8.0</version> <build> <finalName>Tomcat8.0</finalName> <sourceDirectory>java</sourceDirectory> <!-- 不编译测试包,因有些包存在依赖问题 --> <!--<testSourceDirectory>test</testSourceDirectory>--> <resources> <resource> <directory>java</directory> </resource> </resources> <!--<testResources>--> <!--<testResource>--> <!--<directory>test</directory>--> <!--</testResource>--> <!--</testResources>--> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <encoding>UTF-8</encoding> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>3.4</version> </dependency> <dependency> <groupId>ant</groupId> <artifactId>ant</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>javax.xml</groupId> <artifactId>jaxrpc</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.eclipse.jdt.core.compiler</groupId> <artifactId>ecj</artifactId> <version>4.5.1</version> </dependency> </dependencies> </project>
然后maven刷新一下就可以了。
如此,你就可以打开 java 代码,就可以run起来了。
2. Bootstrap.main 一路向前
在进入main() 运行前,将会进行些home目录的检测,此方法在 Bootstrap 的静态方法块中:
static { // Will always be non-null String userDir = System.getProperty("user.dir"); // Home first String home = System.getProperty(Globals.CATALINA_HOME_PROP); File homeFile = null; if (home != null) { File f = new File(home); try { homeFile = f.getCanonicalFile(); } catch (IOException ioe) { homeFile = f.getAbsoluteFile(); } } if (homeFile == null) { // First fall-back. See if current directory is a bin directory // in a normal Tomcat install File bootstrapJar = new File(userDir, "bootstrap.jar"); if (bootstrapJar.exists()) { File f = new File(userDir, ".."); try { homeFile = f.getCanonicalFile(); } catch (IOException ioe) { homeFile = f.getAbsoluteFile(); } } } if (homeFile == null) { // Second fall-back. Use current directory File f = new File(userDir); try { homeFile = f.getCanonicalFile(); } catch (IOException ioe) { homeFile = f.getAbsoluteFile(); } } catalinaHomeFile = homeFile; System.setProperty( Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath()); // Then base String base = System.getProperty(Globals.CATALINA_BASE_PROP); if (base == null) { catalinaBaseFile = catalinaHomeFile; } else { File baseFile = new File(base); try { baseFile = baseFile.getCanonicalFile(); } catch (IOException ioe) { baseFile = baseFile.getAbsoluteFile(); } catalinaBaseFile = baseFile; } System.setProperty( Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath()); }
// org.apache.catalina.startup.Bootstrap main()入口,启动失败时,直接调用 System.exit(1); 退出jvm.
public static void main(String args[]) { // 开始执行,先进行各种初始化操作,然后再解析命令,进行相应方法调用 if (daemon == null) { // Don't set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { bootstrap.init(); } catch (Throwable t) { handleThrowable(t); t.printStackTrace(); return; } daemon = bootstrap; } else { // When running as a service the call to stop will be on a new // thread so make sure the correct class loader is used to prevent // a range of class not found exceptions. Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); } try { // 解析命令行,以最后一个参数作为启动或停止指令,默认为启动操作,但是外部的tomcat脚本可以防止不传入该参数 String command = "start"; if (args.length > 0) { command = args[args.length - 1]; } if (command.equals("startd")) { args[args.length - 1] = "start"; daemon.load(args); daemon.start(); } else if (command.equals("stopd")) { args[args.length - 1] = "stop"; daemon.stop(); } else if (command.equals("start")) { // 重点以start为例 daemon.setAwait(true); daemon.load(args); daemon.start(); if (null == daemon.getServer()) { System.exit(1); } } else if (command.equals("stop")) { daemon.stopServer(args); } else if (command.equals("configtest")) { daemon.load(args); if (null == daemon.getServer()) { System.exit(1); } System.exit(0); } else { log.warn("Bootstrap: command \"" + command + "\" does not exist."); } } catch (Throwable t) { // Unwrap the Exception for clearer error reporting if (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } // 先处理特殊异常 handleThrowable(t); t.printStackTrace(); System.exit(1); } }
// 接下来我们先看看初始化过程: bootstrap.init(); 也就是自身的 init() 方法。
/** * Initialize daemon. * @throws Exception Fatal initialization error */ public void init() throws Exception { // 设置 classLoader, 因该classLoader是tomcat定义的,可由外部传入,所以不能直接加载类,如: WebappClassLoader, SharedClassLoader initClassLoaders(); // 设置classLoader到当前线程,以备后续调用 Thread.currentThread().setContextClassLoader(catalinaLoader); // 加载关键的各种系统类,使用自定义的classLoader SecurityClassLoad.securityClassLoad(catalinaLoader); // 由于加载默认应用时报错,找不到 JSP Compiler, 因此加上此句主动加载, 临时解决问题 catalinaLoader.loadClass("org.apache.jasper.servlet.JasperInitializer"); new JasperInitializer(); // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.getConstructor().newInstance(); // Set the shared extensions class loader, 将 catalina 实例中放入 sharedClassLoader 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; } // step1. 加载 classLoader private void initClassLoaders() { try { // 创建 common 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); } } // private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { // "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; // 将 ${xx} 替换为具体的路径,转换为绝对路径 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); }
// org.apache.catalina.startup.ClassLoaderFactory 工厂类
/** * Create and return a new class loader, based on the configuration * defaults and the specified directory paths: * * @param repositories List of class directories, jar files, jar directories * or URLS that should be added to the repositories of * the class loader. * @param parent Parent class loader for the new class loader, or * <code>null</code> for the system class loader. * @return the new class loader * * @exception Exception if an error occurs constructing the class loader */ public static ClassLoader createClassLoader(List<Repository> repositories, final ClassLoader parent) throws Exception { if (log.isDebugEnabled()) log.debug("Creating new class loader"); // Construct the "class path" for this class loader Set<URL> set = new LinkedHashSet<>(); if (repositories != null) { for (Repository repository : repositories) { if (repository.getType() == RepositoryType.URL) { URL url = buildClassLoaderUrl(repository.getLocation()); if (log.isDebugEnabled()) log.debug(" Including URL " + url); set.add(url); } else if (repository.getType() == RepositoryType.DIR) { File directory = new File(repository.getLocation()); directory = directory.getCanonicalFile(); if (!validateFile(directory, RepositoryType.DIR)) { continue; } URL url = buildClassLoaderUrl(directory); if (log.isDebugEnabled()) log.debug(" Including directory " + url); set.add(url); } else if (repository.getType() == RepositoryType.JAR) { File file=new File(repository.getLocation()); file = file.getCanonicalFile(); if (!validateFile(file, RepositoryType.JAR)) { continue; } URL url = buildClassLoaderUrl(file); if (log.isDebugEnabled()) log.debug(" Including jar file " + url); set.add(url); } else if (repository.getType() == RepositoryType.GLOB) { File directory=new File(repository.getLocation()); directory = directory.getCanonicalFile(); if (!validateFile(directory, RepositoryType.GLOB)) { continue; } if (log.isDebugEnabled()) log.debug(" Including directory glob " + directory.getAbsolutePath()); String filenames[] = directory.list(); if (filenames == null) { continue; } for (int j = 0; j < filenames.length; j++) { String filename = filenames[j].toLowerCase(Locale.ENGLISH); if (!filename.endsWith(".jar")) continue; File file = new File(directory, filenames[j]); file = file.getCanonicalFile(); if (!validateFile(file, RepositoryType.JAR)) { continue; } if (log.isDebugEnabled()) log.debug(" Including glob jar file " + file.getAbsolutePath()); URL url = buildClassLoaderUrl(file); set.add(url); } } } } // Construct the class loader itself final URL[] array = set.toArray(new URL[set.size()]); if (log.isDebugEnabled()) for (int i = 0; i < array.length; i++) { log.debug(" location " + i + " is " + array[i]); } return AccessController.doPrivileged( new PrivilegedAction<URLClassLoader>() { @Override public URLClassLoader run() { if (parent == null) return new URLClassLoader(array); else return new URLClassLoader(array, parent); } }); }
// org.apache.catalina.security.SecurityClassLoad 加载系统关键类
public final class SecurityClassLoad { public static void securityClassLoad(ClassLoader loader) throws Exception { securityClassLoad(loader, true); } static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager) throws Exception { if (requireSecurityManager && System.getSecurityManager() == null) { return; } // 清晰地加载各种系统类库 loadCorePackage(loader); loadCoyotePackage(loader); loadLoaderPackage(loader); loadRealmPackage(loader); loadServletsPackage(loader); loadSessionPackage(loader); loadUtilPackage(loader); loadValvesPackage(loader); loadJavaxPackage(loader); loadConnectorPackage(loader); loadTomcatPackage(loader); } private static final void loadCorePackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.core."; loader.loadClass(basePackage + "AccessLogAdapter"); loader.loadClass(basePackage + "ApplicationContextFacade$PrivilegedExecuteMethod"); loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedForward"); loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedInclude"); loader.loadClass(basePackage + "ApplicationPushBuilder"); loader.loadClass(basePackage + "AsyncContextImpl"); loader.loadClass(basePackage + "AsyncContextImpl$AsyncRunnable"); loader.loadClass(basePackage + "AsyncContextImpl$DebugException"); loader.loadClass(basePackage + "AsyncListenerWrapper"); loader.loadClass(basePackage + "ContainerBase$PrivilegedAddChild"); loadAnonymousInnerClasses(loader, basePackage + "DefaultInstanceManager"); loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntry"); loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntryType"); loader.loadClass(basePackage + "ApplicationHttpRequest$AttributeNamesEnumerator"); } private static final void loadLoaderPackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.loader."; loader.loadClass(basePackage + "WebappClassLoaderBase$PrivilegedFindClassByName"); loader.loadClass(basePackage + "WebappClassLoaderBase$PrivilegedHasLoggingConfig"); } private static final void loadRealmPackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.realm."; loader.loadClass(basePackage + "LockOutRealm$LockRecord"); } private static final void loadServletsPackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.servlets."; // Avoid a possible memory leak in the DefaultServlet when running with // a security manager. The DefaultServlet needs to load an XML parser // when running under a security manager. We want this to be loaded by // the container rather than a web application to prevent a memory leak // via web application class loader. loader.loadClass(basePackage + "DefaultServlet"); } private static final void loadSessionPackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.session."; loader.loadClass(basePackage + "StandardSession"); loadAnonymousInnerClasses(loader, basePackage + "StandardSession"); loader.loadClass(basePackage + "StandardManager$PrivilegedDoUnload"); } private static final void loadUtilPackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.util."; loader.loadClass(basePackage + "ParameterMap"); loader.loadClass(basePackage + "RequestUtil"); loader.loadClass(basePackage + "TLSUtil"); } private static final void loadValvesPackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.valves."; loadAnonymousInnerClasses(loader, basePackage + "AbstractAccessLogValve"); } private static final void loadCoyotePackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.coyote."; loader.loadClass(basePackage + "http11.Constants"); // Make sure system property is read at this point Class<?> clazz = loader.loadClass(basePackage + "Constants"); clazz.getConstructor().newInstance(); loader.loadClass(basePackage + "http2.Stream$PrivilegedPush"); } private static final void loadJavaxPackage(ClassLoader loader) throws Exception { loader.loadClass("javax.servlet.http.Cookie"); } private static final void loadConnectorPackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.connector."; loader.loadClass(basePackage + "RequestFacade$GetAttributePrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetParameterMapPrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetRequestDispatcherPrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetParameterPrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetParameterNamesPrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetParameterValuePrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetCharacterEncodingPrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetHeadersPrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetHeaderNamesPrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetCookiesPrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetLocalePrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetLocalesPrivilegedAction"); loader.loadClass(basePackage + "ResponseFacade$SetContentTypePrivilegedAction"); loader.loadClass(basePackage + "ResponseFacade$DateHeaderPrivilegedAction"); loader.loadClass(basePackage + "RequestFacade$GetSessionPrivilegedAction"); loadAnonymousInnerClasses(loader, basePackage + "ResponseFacade"); loadAnonymousInnerClasses(loader, basePackage + "OutputBuffer"); loadAnonymousInnerClasses(loader, basePackage + "CoyoteInputStream"); loadAnonymousInnerClasses(loader, basePackage + "InputBuffer"); loadAnonymousInnerClasses(loader, basePackage + "Response"); } private static final void loadTomcatPackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.tomcat."; // buf loader.loadClass(basePackage + "util.buf.B2CConverter"); loader.loadClass(basePackage + "util.buf.ByteBufferUtils"); loader.loadClass(basePackage + "util.buf.C2BConverter"); loader.loadClass(basePackage + "util.buf.HexUtils"); loader.loadClass(basePackage + "util.buf.StringCache"); loader.loadClass(basePackage + "util.buf.StringCache$ByteEntry"); loader.loadClass(basePackage + "util.buf.StringCache$CharEntry"); loader.loadClass(basePackage + "util.buf.UriUtil"); // collections Class<?> clazz = loader.loadClass(basePackage + "util.collections.CaseInsensitiveKeyMap"); // Ensure StringManager is configured clazz.getConstructor().newInstance(); loader.loadClass(basePackage + "util.collections.CaseInsensitiveKeyMap$EntryImpl"); loader.loadClass(basePackage + "util.collections.CaseInsensitiveKeyMap$EntryIterator"); loader.loadClass(basePackage + "util.collections.CaseInsensitiveKeyMap$EntrySet"); loader.loadClass(basePackage + "util.collections.CaseInsensitiveKeyMap$Key"); // http loader.loadClass(basePackage + "util.http.CookieProcessor"); loader.loadClass(basePackage + "util.http.NamesEnumerator"); // Make sure system property is read at this point clazz = loader.loadClass(basePackage + "util.http.FastHttpDateFormat"); clazz.getConstructor().newInstance(); loader.loadClass(basePackage + "util.http.parser.HttpParser"); loader.loadClass(basePackage + "util.http.parser.MediaType"); loader.loadClass(basePackage + "util.http.parser.MediaTypeCache"); loader.loadClass(basePackage + "util.http.parser.SkipResult"); // net loader.loadClass(basePackage + "util.net.Constants"); loader.loadClass(basePackage + "util.net.DispatchType"); loader.loadClass(basePackage + "util.net.NioBlockingSelector$BlockPoller$RunnableAdd"); loader.loadClass(basePackage + "util.net.NioBlockingSelector$BlockPoller$RunnableCancel"); loader.loadClass(basePackage + "util.net.NioBlockingSelector$BlockPoller$RunnableRemove"); // security loader.loadClass(basePackage + "util.security.PrivilegedGetTccl"); loader.loadClass(basePackage + "util.security.PrivilegedSetTccl"); } private static final void loadAnonymousInnerClasses(ClassLoader loader, String enclosingClass) { try { for (int i = 1;; i++) { loader.loadClass(enclosingClass + '$' + i); } } catch (ClassNotFoundException ignored) { // } } }
3. tomcat server启动
前面的动作基本还是在做准备,解析server.xml, 命令行参数等等。初始化完成后,准备启动tomcat了!
// 启动时序 if (command.equals("start")) { // 设置标志位 daemon.setAwait(true); // 加载参数 daemon.load(args); // 启动监听 daemon.start(); if (null == daemon.getServer()) { System.exit(1); } }
/** * Set flag. * @param await <code>true</code> if the daemon should block * @throws Exception Reflection error */ public void setAwait(boolean await) throws Exception { Class<?> paramTypes[] = new Class[1]; paramTypes[0] = Boolean.TYPE; Object paramValues[] = new Object[1]; paramValues[0] = Boolean.valueOf(await); Method method = catalinaDaemon.getClass().getMethod("setAwait", paramTypes); method.invoke(catalinaDaemon, paramValues); // 一个简单的方法,尽也用反射操作,奇怪不? } // 加载参数配置,设置 /** * Load daemon. */ private void load(String[] arguments) throws Exception { // Call the load() method String methodName = "load"; Object param[]; Class<?> paramTypes[]; if (arguments==null || arguments.length==0) { paramTypes = null; param = null; } else { paramTypes = new Class[1]; paramTypes[0] = arguments.getClass(); param = new Object[1]; param[0] = arguments; } Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes); if (log.isDebugEnabled()) log.debug("Calling startup class " + method); method.invoke(catalinaDaemon, param); // 反射调用 Catalina.load() 方法 }
// 调用 org.apache.catalina.startup.Catalina.load()
/** * Start a new server instance. */ public void load() { // 只加载一次 if (loaded) { return; } loaded = true; long t1 = System.nanoTime(); // 检查临时目录设置 initDirs(); // Before digester - it may be needed initNaming(); // Create and execute our Digester // 初始化 server.xml 的配置项 Digester digester = createStartDigester(); InputSource inputSource = null; InputStream inputStream = null; File file = null; try { try { // 加载配置 server.xml file = configFile(); inputStream = new FileInputStream(file); inputSource = new InputSource(file.toURI().toURL().toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", file), e); } } if (inputStream == null) { try { inputStream = getClass().getClassLoader() .getResourceAsStream(getConfigFile()); inputSource = new InputSource (getClass().getClassLoader() .getResource(getConfigFile()).toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", getConfigFile()), e); } } } // This should be included in catalina.jar // Alternative: don't bother with xml, just create it manually. if (inputStream == null) { try { inputStream = getClass().getClassLoader() .getResourceAsStream("server-embed.xml"); inputSource = new InputSource (getClass().getClassLoader() .getResource("server-embed.xml").toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", "server-embed.xml"), e); } } } if (inputStream == null || inputSource == null) { if (file == null) { log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml]")); } else { log.warn(sm.getString("catalina.configFail", file.getAbsolutePath())); if (file.exists() && !file.canRead()) { log.warn("Permissions incorrect, read permission is not allowed on the file."); } } return; } try { inputSource.setByteStream(inputStream); // Catalina 加入栈,解析server.xml, server 在此时创建 digester.push(this); digester.parse(inputSource); } catch (SAXParseException spe) { log.warn("Catalina.start using " + getConfigFile() + ": " + spe.getMessage()); return; } catch (Exception e) { log.warn("Catalina.start using " + getConfigFile() + ": " , e); return; } } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { // Ignore } } } getServer().setCatalina(this); getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); // Stream redirection initStreams(); // Start the new server try { getServer().init(); // 关键: 初始化server, 会进入到 StandardServer 的 lifeCycle 中 } catch (LifecycleException e) { if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) { throw new java.lang.Error(e); } else { log.error("Catalina.start", e); } } long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); } } /** * Create and configure the Digester we will be using for startup. * @return the main digester to parse server.xml */ protected Digester createStartDigester() { long t1=System.currentTimeMillis(); // Initialize the digester Digester digester = new Digester(); digester.setValidating(false); digester.setRulesValidation(true); HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<>(); ArrayList<String> attrs = new ArrayList<>(); attrs.add("className"); fakeAttributes.put(Object.class, attrs); digester.setFakeAttributes(fakeAttributes); digester.setUseContextClassLoader(true); // Configure the actions we will be using digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); digester.addObjectCreate("Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResourcesImpl"); digester.addSetProperties("Server/GlobalNamingResources"); digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResourcesImpl"); digester.addObjectCreate("Server/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Listener"); digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); digester.addObjectCreate("Server/Service/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Listener"); digester.addSetNext("Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); //Executor digester.addObjectCreate("Server/Service/Executor", "org.apache.catalina.core.StandardThreadExecutor", "className"); digester.addSetProperties("Server/Service/Executor"); digester.addSetNext("Server/Service/Executor", "addExecutor", "org.apache.catalina.Executor"); digester.addRule("Server/Service/Connector", new ConnectorCreateRule()); digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"})); digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector"); digester.addObjectCreate("Server/Service/Connector/SSLHostConfig", "org.apache.tomcat.util.net.SSLHostConfig"); digester.addSetProperties("Server/Service/Connector/SSLHostConfig"); digester.addSetNext("Server/Service/Connector/SSLHostConfig", "addSslHostConfig", "org.apache.tomcat.util.net.SSLHostConfig"); digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate", new CertificateCreateRule()); digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate", new SetAllPropertiesRule(new String[]{"type"})); digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate", "addCertificate", "org.apache.tomcat.util.net.SSLHostConfigCertificate"); digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf", "org.apache.tomcat.util.net.openssl.OpenSSLConf"); digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf"); digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf", "setOpenSslConf", "org.apache.tomcat.util.net.openssl.OpenSSLConf"); digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd", "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd"); digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd"); digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd", "addCmd", "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd"); digester.addObjectCreate("Server/Service/Connector/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Connector/Listener"); digester.addSetNext("Server/Service/Connector/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Connector/UpgradeProtocol"); digester.addSetNext("Server/Service/Connector/UpgradeProtocol", "addUpgradeProtocol", "org.apache.coyote.UpgradeProtocol"); // Add RuleSets for nested elements digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); digester.addRuleSet(new EngineRuleSet("Server/Service/")); digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/"); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader. digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(parentClassLoader)); addClusterRuleSet(digester, "Server/Service/Engine/Cluster/"); long t2=System.currentTimeMillis(); if (log.isDebugEnabled()) { log.debug("Digester for server.xml created " + ( t2-t1 )); } return (digester); } protected File configFile() { File file = new File(configFile); if (!file.isAbsolute()) { file = new File(Bootstrap.getCatalinaBase(), configFile); } return (file); }
ObjectCreate 如下:
/** * Add an "object create" rule for the specified parameters. * * @param pattern Element matching pattern * @param className Default Java class name to be created * @param attributeName Attribute name that optionally overrides * the default Java class name to be created * @see ObjectCreateRule */ public void addObjectCreate(String pattern, String className, String attributeName) { addRule(pattern, new ObjectCreateRule(className, attributeName)); }
// org.apache.tomcat.util.digester.Digester.push() 入栈, parse() 解析配置文件
/** * Push a new object onto the top of the object stack. * * @param object The new object */ public void push(Object object) { if (stack.size() == 0) { root = object; } stack.push(object); } /** * Parse the content of the specified input source using this Digester. * Returns the root element from the object stack (if any). * * @param input Input source containing the XML data to be parsed * @return the root object * @exception IOException if an input/output error occurs * @exception SAXException if a parsing exception occurs */ public Object parse(InputSource input) throws IOException, SAXException { configure(); getXMLReader().parse(input); return (root); }
插播,我们顺便看看获取 MBean的过程,使用读写锁进行操作,提升效率! org.apache.tomcat.util.modeler.ManagedBean.getMBeanInfo()
/** * Create and return a <code>ModelMBeanInfo</code> object that * describes this entire managed bean. * @return the MBean info */ MBeanInfo getMBeanInfo() { // Return our cached information (if any) mBeanInfoLock.readLock().lock(); try { if (info != null) { return info; } } finally { mBeanInfoLock.readLock().unlock(); } mBeanInfoLock.writeLock().lock(); try { if (info == null) { // Create subordinate information descriptors as required AttributeInfo attrs[] = getAttributes(); MBeanAttributeInfo attributes[] = new MBeanAttributeInfo[attrs.length]; for (int i = 0; i < attrs.length; i++) attributes[i] = attrs[i].createAttributeInfo(); OperationInfo opers[] = getOperations(); MBeanOperationInfo operations[] = new MBeanOperationInfo[opers.length]; for (int i = 0; i < opers.length; i++) operations[i] = opers[i].createOperationInfo(); NotificationInfo notifs[] = getNotifications(); MBeanNotificationInfo notifications[] = new MBeanNotificationInfo[notifs.length]; for (int i = 0; i < notifs.length; i++) notifications[i] = notifs[i].createNotificationInfo(); // Construct and return a new ModelMBeanInfo object info = new MBeanInfo(getClassName(), getDescription(), attributes, new MBeanConstructorInfo[] {}, operations, notifications); } return info; } finally { mBeanInfoLock.writeLock().unlock(); } } // 获取 MBeanServer()时,直接调用 util/modeler/Registry , 然后 从 MBeanServerFactory 获取 /** * Factory method to create (if necessary) and return our * <code>MBeanServer</code> instance. * @return the MBean server */ public synchronized MBeanServer getMBeanServer() { if (server == null) { long t1 = System.currentTimeMillis(); // 先查找是否已配置了 MBeanServer if (MBeanServerFactory.findMBeanServer(null).size() > 0) { server = MBeanServerFactory.findMBeanServer(null).get(0); if (log.isDebugEnabled()) { log.debug("Using existing MBeanServer " + (System.currentTimeMillis() - t1)); } } else { // 如果没有配置, 则直接取 jdk 的 MBeanServer, JmxMBeanServer server = ManagementFactory.getPlatformMBeanServer(); if (log.isDebugEnabled()) { log.debug("Creating MBeanServer" + (System.currentTimeMillis() - t1)); } } } return server; } /** * <p>Return a list of registered MBeanServer objects. A * registered MBeanServer object is one that was created by one of * the <code>createMBeanServer</code> methods and not subsequently * released with <code>releaseMBeanServer</code>.</p> * * @param agentId The agent identifier of the MBeanServer to * retrieve. If this parameter is null, all registered * MBeanServers in this JVM are returned. Otherwise, only * MBeanServers whose id is equal to <code>agentId</code> are * returned. The id of an MBeanServer is the * <code>MBeanServerId</code> attribute of its delegate MBean. * * @return A list of MBeanServer objects. * * @exception SecurityException if there is a SecurityManager and the * caller's permissions do not include or imply <code>{@link * MBeanServerPermission}("findMBeanServer")</code>. */ public synchronized static ArrayList<MBeanServer> findMBeanServer(String agentId) { checkPermission("findMBeanServer"); if (agentId == null) return new ArrayList<MBeanServer>(mBeanServerList); ArrayList<MBeanServer> result = new ArrayList<MBeanServer>(); for (MBeanServer mbs : mBeanServerList) { String name = mBeanServerId(mbs); if (agentId.equals(name)) result.add(mbs); } return result; } // org.apache.catalina.mbeans.MBeanUtils 在初始化的时候就会创建几个单例应用 /** * The configuration information registry for our managed beans. */ private static Registry registry = createRegistry(); /** * The <code>MBeanServer</code> for this application. */ private static MBeanServer mserver = createServer(); /** * Create and configure (if necessary) and return the registry of * managed object descriptions. * @return the singleton registry */ public static synchronized Registry createRegistry() { if (registry == null) { registry = Registry.getRegistry(null, null); ClassLoader cl = MBeanUtils.class.getClassLoader(); registry.loadDescriptors("org.apache.catalina.mbeans", cl); registry.loadDescriptors("org.apache.catalina.authenticator", cl); registry.loadDescriptors("org.apache.catalina.core", cl); registry.loadDescriptors("org.apache.catalina", cl); registry.loadDescriptors("org.apache.catalina.deploy", cl); registry.loadDescriptors("org.apache.catalina.loader", cl); registry.loadDescriptors("org.apache.catalina.realm", cl); registry.loadDescriptors("org.apache.catalina.session", cl); registry.loadDescriptors("org.apache.catalina.startup", cl); registry.loadDescriptors("org.apache.catalina.users", cl); registry.loadDescriptors("org.apache.catalina.ha", cl); registry.loadDescriptors("org.apache.catalina.connector", cl); registry.loadDescriptors("org.apache.catalina.valves", cl); registry.loadDescriptors("org.apache.catalina.storeconfig", cl); registry.loadDescriptors("org.apache.tomcat.util.descriptor.web", cl); } return (registry); }
4. LifeCycle 生命周期管理
tomcat的代码量非常大,但其基本都遵从生命周期管理模式,所以每个组件都可以作为一个独立的个体来看待。org.apache.catalina.util.LifeCycleBase.init(), 很多实现都继承该该,进行生命周期的管理!
@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) { ExceptionUtils.handleThrowable(t); setStateInternal(LifecycleState.FAILED, null, false); throw new LifecycleException( sm.getString("lifecycleBase.initFail",toString()), t); } } // setStateInternal, 设置当前运行状态 private synchronized void setStateInternal(LifecycleState state, Object data, boolean check) throws LifecycleException { if (log.isDebugEnabled()) { log.debug(sm.getString("lifecycleBase.setState", this, state)); } if (check) { // Must have been triggered by one of the abstract methods (assume // code in this class is correct) // null is never a valid state if (state == null) { invalidTransition("null"); // Unreachable code - here to stop eclipse complaining about // a possible NPE further down the method return; } // Any method can transition to failed // startInternal() permits STARTING_PREP to STARTING // stopInternal() permits STOPPING_PREP to STOPPING and FAILED to // STOPPING if (!(state == LifecycleState.FAILED || (this.state == LifecycleState.STARTING_PREP && state == LifecycleState.STARTING) || (this.state == LifecycleState.STOPPING_PREP && state == LifecycleState.STOPPING) || (this.state == LifecycleState.FAILED && state == LifecycleState.STOPPING))) { // No other transition permitted invalidTransition(state.name()); } } this.state = state; String lifecycleEvent = state.getLifecycleEvent(); if (lifecycleEvent != null) { fireLifecycleEvent(lifecycleEvent, data); } }
org.apache.catalina.deploy.NamingResourcesImpl: 负责对一些必须资源的管理!
// 进行事件监听通知 /** * Allow sub classes to fire {@link Lifecycle} events. * * @param type Event type * @param data Data associated with event. */ protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(this, type, data); // NamingContextListener // VersionLoggerListener // AprLifecycleListener // JreMemoryLeakPreventionListener // GlobalResourcesLifecycleListener // ThreadLocalLeakPreventionListener for (LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); } } // org.apache.catalina.core.StandardServer.initInternal() /** * Invoke a pre-startup initialization. This is used to allow connectors * to bind to restricted ports under Unix operating environments. */ @Override protected void initInternal() throws LifecycleException { super.initInternal(); // Register global String cache // Note although the cache is global, if there are multiple Servers // present in the JVM (may happen when embedding) then the same cache // will be registered under multiple names onameStringCache = register(new StringCache(), "type=StringCache"); // Register the MBeanFactory MBeanFactory factory = new MBeanFactory(); factory.setContainer(this); onameMBeanFactory = register(factory, "type=MBeanFactory"); // Register the naming resources globalNamingResources.init(); // Populate the extension validator with JARs from common and shared // class loaders if (getCatalina() != null) { ClassLoader cl = getCatalina().getParentClassLoader(); // Walk the class loader hierarchy. Stop at the system class loader. // This will add the shared (if present) and common class loaders 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) { // Ignore } catch (IOException e) { // Ignore } } } } cl = cl.getParent(); } } // Initialize our defined Services for (int i = 0; i < services.length; i++) { services[i].init(); } } // org.apache.catalina.util.LifecycleMBeanBase.initInternal() /** * Sub-classes wishing to perform additional initialization should override * this method, ensuring that super.initInternal() is the first call in the * overriding method. */ @Override protected void initInternal() throws LifecycleException { // If oname is not null then registration has already happened via // preRegister(). if (oname == null) { mserver = Registry.getRegistry(null, null).getMBeanServer(); oname = register(this, getObjectNameKeyProperties()); } } /** * Utility method to enable sub-classes to easily register additional * components that don't implement {@link JmxEnabled} with an MBean server. * <br> * Note: This method should only be used once {@link #initInternal()} has * been called and before {@link #destroyInternal()} has been called. * * @param obj The object the register * @param objectNameKeyProperties The key properties component of the * object name to use to register the * object * * @return The name used to register the object */ protected final ObjectName register(Object obj, String objectNameKeyProperties) { // Construct an object name with the right domain StringBuilder name = new StringBuilder(getDomain()); name.append(':'); name.append(objectNameKeyProperties); ObjectName on = null; try { on = new ObjectName(name.toString()); Registry.getRegistry(null, null).registerComponent(obj, on, null); } catch (MalformedObjectNameException e) { log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name), e); } catch (Exception e) { log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name), e); } return on; }
StandardServer.initInternal(), 负责server的逻辑处理:
/** * Invoke a pre-startup initialization. This is used to allow connectors * to bind to restricted ports under Unix operating environments. */ @Override protected void initInternal() throws LifecycleException { super.initInternal(); // Register global String cache // Note although the cache is global, if there are multiple Servers // present in the JVM (may happen when embedding) then the same cache // will be registered under multiple names onameStringCache = register(new StringCache(), "type=StringCache"); // Register the MBeanFactory MBeanFactory factory = new MBeanFactory(); factory.setContainer(this); onameMBeanFactory = register(factory, "type=MBeanFactory"); // Register the naming resources globalNamingResources.init(); // Populate the extension validator with JARs from common and shared // class loaders if (getCatalina() != null) { ClassLoader cl = getCatalina().getParentClassLoader(); // Walk the class loader hierarchy. Stop at the system class loader. // This will add the shared (if present) and common class loaders 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) { // Ignore } catch (IOException e) { // Ignore } } } } cl = cl.getParent(); } } // Initialize our defined Services, 最后,调用 StandardService.init() 监听方法 for (int i = 0; i < services.length; i++) { services[i].init(); } }
而 StandardService.init() 又会带动 initInternal 处理具体逻辑!
// StandardService.initInternal() 初始化服务,Engin(),线程池。。。
/** * Invoke a pre-startup initialization. This is used to allow connectors * to bind to restricted ports under Unix operating environments. */ @Override protected void initInternal() throws LifecycleException { super.initInternal(); // engin init, StandardEngine.init() if (engine != null) { engine.init(); } // Initialize any Executors for (Executor executor : findExecutors()) { if (executor instanceof JmxEnabled) { ((JmxEnabled) executor).setDomain(getDomain()); } executor.init(); } // Initialize mapper listener mapperListener.init(); // Initialize our defined Connectors // 初始化连接器,如: Connector[HTTP/1.1-8080], Connector[AJP/1.3-8011] synchronized (connectorsLock) { for (Connector connector : connectors) { try { connector.init(); } catch (Exception e) { String message = sm.getString( "standardService.connector.initFailed", connector); log.error(message, e); if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) throw new LifecycleException(message); } } } }
// org.apache.catalina.core.StandardEngine.initInternal(): 主要是检查 Realm 的设置,以及生成启动线程: Catalina-startStop-xx, 默认是1个线程启动;
@Override protected void initInternal() throws LifecycleException { // Ensure that a Realm is present before any attempt is made to start // one. This will create the default NullRealm if necessary. getRealm(); super.initInternal(); } // org.apache.catalina.core.ContainerBase.initInternal() // Catalina-startStop-xx 线程池创建 @Override protected void initInternal() throws LifecycleException { BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>(); startStopExecutor = new ThreadPoolExecutor( getStartStopThreadsInternal(), getStartStopThreadsInternal(), 10, TimeUnit.SECONDS, startStopQueue, new StartStopThreadFactory(getName() + "-startStop-")); startStopExecutor.allowCoreThreadTimeOut(true); super.initInternal(); } // org.apache.catalina.connector.Connector @Override protected void initInternal() throws LifecycleException { super.initInternal(); // Initialize adapter adapter = new CoyoteAdapter(this); protocolHandler.setAdapter(adapter); // Make sure parseBodyMethodsSet has a default if (null == parseBodyMethodsSet) { setParseBodyMethods(getParseBodyMethods()); } if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) { throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr", getProtocolHandlerClassName())); } if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() && protocolHandler instanceof AbstractHttp11JsseProtocol) { AbstractHttp11JsseProtocol<?> jsseProtocolHandler = (AbstractHttp11JsseProtocol<?>) protocolHandler; if (jsseProtocolHandler.isSSLEnabled() && jsseProtocolHandler.getSslImplementationName() == null) { // OpenSSL is compatible with the JSSE configuration, so use it if APR is available jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName()); } } try { protocolHandler.init(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e); } } // org.apache.coyote.AbstractProtocol.init() @Override public void init() throws Exception { if (getLog().isInfoEnabled()) { getLog().info(sm.getString("abstractProtocolHandler.init", getName())); } if (oname == null) { // Component not pre-registered so register it oname = createObjectName(); if (oname != null) { Registry.getRegistry(null, null).registerComponent(this, oname, null); } } if (this.domain != null) { rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName()); Registry.getRegistry(null, null).registerComponent( getHandler().getGlobal(), rgOname, null); } String endpointName = getName(); endpoint.setName(endpointName.substring(1, endpointName.length()-1)); endpoint.setDomain(domain); endpoint.init(); } // org.apache.tomcat.util.net.AbstractEndpoint.init() public void init() throws Exception { if (bindOnInit) { bind(); bindState = BindState.BOUND_ON_INIT; } if (this.domain != null) { // Register endpoint (as ThreadPool - historical name) oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\""); Registry.getRegistry(null, null).registerComponent(this, oname, null); for (SSLHostConfig sslHostConfig : findSslHostConfigs()) { registerJmx(sslHostConfig); } } }
接下来是对监听器的初始化工作: mapperListener.init(); 但是它没有什么特别的需要初始化的,所以直接交给父类操作了,而其自身的作用,则是启动过程发挥的!
再往下,就是对 Connector 的初始化了,这也就是我们的应用服务了!其中最重要的,是开启了socket 监听!
// org.apache.catalina.connector.Connector, 初始化 Connector // 其主要实现依赖于 CoyoteAdapter @Override protected void initInternal() throws LifecycleException { super.initInternal(); // Initialize adapter, httpProtocolHandler 是 Http11NioProtocol adapter = new CoyoteAdapter(this); protocolHandler.setAdapter(adapter); // Make sure parseBodyMethodsSet has a default // 设置默认的body的解析方法, 默认为 POST if (null == parseBodyMethodsSet) { setParseBodyMethods(getParseBodyMethods()); } if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) { throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr", getProtocolHandlerClassName())); } if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() && protocolHandler instanceof AbstractHttp11JsseProtocol) { AbstractHttp11JsseProtocol<?> jsseProtocolHandler = (AbstractHttp11JsseProtocol<?>) protocolHandler; if (jsseProtocolHandler.isSSLEnabled() && jsseProtocolHandler.getSslImplementationName() == null) { // OpenSSL is compatible with the JSSE configuration, so use it if APR is available jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName()); } } try { // 根据协议调用相应初始化 protocolHandler.init(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e); } } // org.apache.coyote.http11.AbstractHttp11Protocol // org.apache.coyote.http11.AbstractHttp11Protocol @Override public void init() throws Exception { for (UpgradeProtocol upgradeProtocol : upgradeProtocols) { configureUpgradeProtocol(upgradeProtocol); } super.init(); } @Override public void init() throws Exception { if (getLog().isInfoEnabled()) { getLog().info(sm.getString("abstractProtocolHandler.init", getName())); } if (oname == null) { // Component not pre-registered so register it oname = createObjectName(); if (oname != null) { Registry.getRegistry(null, null).registerComponent(this, oname, null); } } if (this.domain != null) { rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName()); Registry.getRegistry(null, null).registerComponent( getHandler().getGlobal(), rgOname, null); } // 如 http-nio-8080, 由子类返回名称+端口号 String endpointName = getName(); endpoint.setName(endpointName.substring(1, endpointName.length()-1)); endpoint.setDomain(domain); // Endpoint 为 NioEndpoint endpoint.init(); } /** * The name will be prefix-address-port if address is non-null and * prefix-port if the address is null. * * @return A name for this protocol instance that is appropriately quoted * for use in an ObjectName. */ public String getName() { return ObjectName.quote(getNameInternal()); } private String getNameInternal() { StringBuilder name = new StringBuilder(getNamePrefix()); name.append('-'); if (getAddress() != null) { name.append(getAddress().getHostAddress()); name.append('-'); } int port = getPort(); if (port == 0) { // Auto binding is in use. Check if port is known name.append("auto-"); name.append(getNameIndex()); port = getLocalPort(); if (port != -1) { name.append('-'); name.append(port); } } else { name.append(port); } return name.toString(); } // coyote.http11.Http11NioProtocol.getNamePrefix() @Override protected String getNamePrefix() { if (isSSLEnabled()) { return ("https-" + getSslImplementationShortName()+ "-nio"); } else { return ("http-nio"); } } // endpoint.init() // org.apache.tomcat.util.net.AbstractJsseEndpoint.init(), 添加证书验证逻辑 @Override public void init() throws Exception { testServerCipherSuitesOrderSupport(); // 调用父类初始化方法, 绑定服务端口 super.init(); } // org.apache.tomcat.util.net.AbstractEndpoint.init() public void init() throws Exception { if (bindOnInit) { // 直接调用子类 NioEndpoint.bind(), 打开服务端口 bind(); bindState = BindState.BOUND_ON_INIT; } if (this.domain != null) { // Register endpoint (as ThreadPool - historical name) oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\""); Registry.getRegistry(null, null).registerComponent(this, oname, null); for (SSLHostConfig sslHostConfig : findSslHostConfigs()) { registerJmx(sslHostConfig); } } }
Connector 初始过程,会 调用如下 NioEndpoint.bind(), 打开socket连接
/** * Initialize the endpoint. */ @Override public void bind() throws Exception { if (!getUseInheritedChannel()) { serverSock = ServerSocketChannel.open(); // 来自jdk的nio socket 开启方式 socketProperties.setProperties(serverSock.socket()); // 设置各项配置属性 // 然后再绑定host 和 端口号 InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort())); serverSock.socket().bind(addr,getAcceptCount()); // 默认的连接数是 100, 可以自行配置 } else { // Retrieve the channel provided by the OS Channel ic = System.inheritedChannel(); if (ic instanceof ServerSocketChannel) { serverSock = (ServerSocketChannel) ic; } if (serverSock == null) { throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited")); } } serverSock.configureBlocking(true); //mimic APR behavior // Initialize thread count defaults for acceptor, poller, 默认的 acceptor 线程数为1, 即只会有一个线程监听前端请求 if (acceptorThreadCount == 0) { // FIXME: Doesn't seem to work that well with multiple accept threads acceptorThreadCount = 1; } if (pollerThreadCount <= 0) { //minimum one poller thread, 默认 poller 线程数是 2, Math.min(2,Runtime.getRuntime().availableProcessors()); pollerThreadCount = 1; } // 闭锁控制 setStopLatch(new CountDownLatch(pollerThreadCount)); // Initialize SSL if needed initialiseSsl(); // 调用selector池,打开 select 连接 selectorPool.open(); } // org.apache.tomcat.util.net.NioSelectorPool.open() public void open() throws IOException { enabled = true; getSharedSelector(); // NioSelector, 调用 Selector.open() 开启平台相关的 jdk Selector if (SHARED) { // 接下来还开启多个selector的阻塞等待 blockingSelector = new NioBlockingSelector(); blockingSelector.open(getSharedSelector()); } }
NioBlockingSelector.open();
// NioBlockingSelector.open() public void open(Selector selector) { sharedSelector = selector; // 使用 BlockPoller 来处理实际业务, BlockPoller 是个内部类,可以共享该类的各种配置 // 使用 SynchronizedQueue 来保存处理队列, 保证线程安全 // BlockPoller 的主要作用是: 查询是否有需要进行处理的事件,如果有,则启动相应处理,否则阻塞等待 poller = new BlockPoller(); poller.selector = sharedSelector; poller.setDaemon(true); poller.setName("NioBlockingSelector.BlockPoller-"+(++threadCounter)); // 初始化完成后,直接启动线程 poller.start(); } // BlockPoller 的 任务逻辑如下: @Override public void run() { while (run) { try { // 如果有需要处理的事件,则直接 调用 run 事件的 event.run() 运行任务; // 所以,此处的任务其实是同步执行的 events(); int keyCount = 0; try { int i = wakeupCounter.get(); if (i>0) keyCount = selector.selectNow(); else { wakeupCounter.set(-1); // 阻塞等待超时 keyCount = selector.select(1000); } wakeupCounter.set(0); if (!run) break; }catch ( NullPointerException x ) { //sun bug 5076772 on windows JDK 1.5 if (selector==null) throw x; if ( log.isDebugEnabled() ) log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5",x); continue; } catch ( CancelledKeyException x ) { //sun bug 5076772 on windows JDK 1.5 if ( log.isDebugEnabled() ) log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5",x); continue; } catch (Throwable x) { ExceptionUtils.handleThrowable(x); log.error("",x); continue; } Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null; // Walk through the collection of ready keys and dispatch // any active event. while (run && iterator != null && iterator.hasNext()) { SelectionKey sk = iterator.next(); NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment(); try { iterator.remove(); sk.interestOps(sk.interestOps() & (~sk.readyOps())); if ( sk.isReadable() ) { countDown(attachment.getReadLatch()); } if (sk.isWritable()) { countDown(attachment.getWriteLatch()); } }catch (CancelledKeyException ckx) { sk.cancel(); countDown(attachment.getReadLatch()); countDown(attachment.getWriteLatch()); } }//while }catch ( Throwable t ) { log.error("",t); } } // 异常处理 events.clear(); // If using a shared selector, the NioSelectorPool will also try and // close the selector. Try and avoid the ClosedSelectorException // although because multiple threads are involved there is always // the possibility of an Exception here. if (selector.isOpen()) { try { // Cancels all remaining keys selector.selectNow(); }catch( Exception ignore ) { if (log.isDebugEnabled())log.debug("",ignore); } } try { selector.close(); }catch( Exception ignore ) { if (log.isDebugEnabled())log.debug("",ignore); } }
// load() 完成后,即初始化完成,接下来是调用 start() 的过程!让我们回到 Bootstrap 实例:
// 其实就是对 daemon.start(); 走线问题处理!
/** * Start the Catalina daemon. * @throws Exception Fatal start error */ public void start() throws Exception { if( catalinaDaemon==null ) init(); // 为确保万无一失,再次检查初始化 Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null); method.invoke(catalinaDaemon, (Object [])null); // 同样,使用反射调用 Catalina.start(); } // org.apache.catalina.startup.Catalina.start() /** * Start a new server instance. */ public void start() { if (getServer() == null) { load(); } // 如果经过前面的server初始化,还是获取不到 StandardServer if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; } long t1 = System.nanoTime(); // Start the new server try { // 调用 server.start(), 即是 StandardServer.start(); 同样走一个生命周期的管理过程! getServer().start(); } catch (LifecycleException e) { log.fatal(sm.getString("catalina.serverStartFail"), e); try { getServer().destroy(); } catch (LifecycleException e1) { log.debug("destroy() failed for failed Server ", e1); } return; } long t2 = System.nanoTime(); if(log.isInfoEnabled()) { log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); } // Register shutdown hook 注册关闭钩子,进行资源清理,server stop if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( false); } } if (await) { await(); stop(); } }
start() 的生命周期管理相比 init() 来说,复杂了一些些!LifeCycleBase.start()
@Override public final synchronized void start() throws LifecycleException { // 没有准备好时,失败返回 if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) || LifecycleState.STARTED.equals(state)) { if (log.isDebugEnabled()) { Exception e = new LifecycleException(); log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e); } else if (log.isInfoEnabled()) { log.info(sm.getString("lifecycleBase.alreadyStarted", toString())); } return; } // 没有初始化过的,再次初始化, 这也体现了代码的健壮性,而如果失败,则停止退出 if (state.equals(LifecycleState.NEW)) { init(); } else if (state.equals(LifecycleState.FAILED)) { stop(); } else if (!state.equals(LifecycleState.INITIALIZED) && !state.equals(LifecycleState.STOPPED)) { invalidTransition(Lifecycle.BEFORE_START_EVENT); } try { setStateInternal(LifecycleState.STARTING_PREP, null, false); // 检测通过后,正式进入 startInternal() startInternal(); if (state.equals(LifecycleState.FAILED)) { // This is a 'controlled' failure. The component put itself into the // FAILED state so call stop() to complete the clean-up. stop(); } else if (!state.equals(LifecycleState.STARTING)) { // Shouldn't be necessary but acts as a check that sub-classes are // doing what they are supposed to. invalidTransition(Lifecycle.AFTER_START_EVENT); } else { setStateInternal(LifecycleState.STARTED, null, false); } } catch (Throwable t) { // This is an 'uncontrolled' failure so put the component into the // FAILED state and throw an exception. ExceptionUtils.handleThrowable(t); setStateInternal(LifecycleState.FAILED, null, false); throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t); } }
而 StandardServer.startInternal(), 则是这样的:
// /** * Start nested components ({@link Service}s) and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected void startInternal() throws LifecycleException { fireLifecycleEvent(CONFIGURE_START_EVENT, null); // 发布事件,使监听器们工作起来,标准的观察者模块应用 setState(LifecycleState.STARTING); globalNamingResources.start(); // 同样,先调用 NamingResource 的生命周期 start(),事情不多,主要是设置一个可监听点, 再调用 service.start() // Start our defined Services, standardService.start() synchronized (servicesLock) { for (int i = 0; i < services.length; i++) { services[i].start(); } } }
tomcat 中 默认的监听器有以下几个,望文生义,可以自行注册监听
- org.apache.catalina.core.NamingContextListener
- org.apache.catalina.startup.VersionLoggerListener
- org.apache.catalina.core.AprLifecycleListener
- org.apache.catalina.core.JreMemoryLeakPreventionListener
- org.apache.catalina.mbeans.GlobalResourcesLifecycleListener
- org.apache.catalina.core.ThreadLocalLeakPreventionListener
StandardService.startInternal(), 处理整个service的整体流程,包括 StandardEngine的启动和Connector的启动(关键)!
/** * Start nested components ({@link Executor}s, {@link Connector}s and * {@link Container}s) and implement the requirements of * {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected void startInternal() throws LifecycleException { if(log.isInfoEnabled()) log.info(sm.getString("standardService.start.name", this.name)); setState(LifecycleState.STARTING); // Start our defined Container first, 先把 StandardEngine.start() 启起来 if (engine != null) { synchronized (engine) { engine.start(); } } // 再将Executors启起来,以备请求使用 synchronized (executors) { for (Executor executor: executors) { executor.start(); } } // 启动mapperListner, 在 init() 时无用,直接在这个时候进行解析 mapperListener.start(); // Start our defined Connectors second // 最后,启动 connectors synchronized (connectorsLock) { for (Connector connector: connectors) { try { // If it has already failed, don't try and start it if (connector.getState() != LifecycleState.FAILED) { connector.start(); } } catch (Exception e) { log.error(sm.getString( "standardService.connector.startFailed", connector), e); } } } } // StandardEngine.startInternal() @Override protected synchronized void startInternal() throws LifecycleException { // Start our subordinate components, if any logger = null; getLogger(); // 集群? 看起来很高大上呢! 一般为空 Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { ((Lifecycle) cluster).start(); } // realm 验证,这里会触发初始化 Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).start(); } // Start our child containers, if any // 将 child 交给启动线程异步执行去 Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } MultiThrowable multiThrowable = new MultiThrowable(); for (Future<Void> result : results) { try { // 阻塞等待子容器启动完成 result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); multiThrowable.add(e); } } if (multiThrowable.size() > 0) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable()); } // Start the Valves in our pipeline (including the basic), if any // 管道处理,后续再说 if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).start(); } setState(LifecycleState.STARTING); // Start our thread threadStart(); } protected void threadStart() { if (thread != null) return; if (backgroundProcessorDelay <= 0) return; threadDone = false; String threadName = "ContainerBackgroundProcessor[" + toString() + "]"; thread = new Thread(new ContainerBackgroundProcessor(), threadName); thread.setDaemon(true); thread.start(); }
5. 监听器的启动
作为应用服务器,监听功能是最基础的。它会在前面的工作都完成,使监听器跑起来。
// org.apache.catalina.mapper.MapperListener @Override public void startInternal() throws LifecycleException { setState(LifecycleState.STARTING); Engine engine = service.getContainer(); if (engine == null) { return; } findDefaultHost(); addListeners(engine); Container[] conHosts = engine.findChildren(); // 注册 host for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { // Registering the host will register the context and wrappers registerHost(host); } } } // 注册监听到 container 中, 以便收到事件通知, 使用 CopyOnWriteArrayList 数据结构保持监听者 private void addListeners(Container container) { container.addContainerListener(this); container.addLifecycleListener(this); for (Container child : container.findChildren()) { addListeners(child); } }
最后,是 connector 的启动过程,主要是依赖协议的实现,主要任务有:启动 Acceptor, Poller 接收请求:
// Connector /** * Begin processing requests via this Connector. * * @exception LifecycleException if a fatal startup error occurs */ @Override protected void startInternal() throws LifecycleException { // Validate settings before starting if (getPort() < 0) { throw new LifecycleException(sm.getString( "coyoteConnector.invalidPort", Integer.valueOf(getPort()))); } setState(LifecycleState.STARTING); try { // 可以查看 Http11NioProtocol.start() protocolHandler.start(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerStartFailed"), e); } } // Http11NioProtocol.start() @Override public void start() throws Exception { if (getLog().isInfoEnabled()) { getLog().info(sm.getString("abstractProtocolHandler.start", getName())); } // start() 事件通知到 NioEndpoint endpoint.start(); // Start async timeout thread asyncTimeout = new AsyncTimeout(); Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout"); int priority = endpoint.getThreadPriority(); if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) { priority = Thread.NORM_PRIORITY; } timeoutThread.setPriority(priority); timeoutThread.setDaemon(true); timeoutThread.start(); } // AbstractEndpoint.start() public final void start() throws Exception { // 在初始化时可能已经完成绑定,此处可能无需执行 if (bindState == BindState.UNBOUND) { bind(); bindState = BindState.BOUND_ON_START; } // 更重要的是要创建 Acceptor 和 Pollers startInternal(); } // NioEndpoint.startInternal() /** * Start the NIO endpoint, creating acceptor, poller threads. */ @Override public void startInternal() throws Exception { // 只会执行一次 if (!running) { running = true; paused = false; // 默认128, tomcat8 设置 500 processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getProcessorCache()); eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getEventCache()); nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getBufferPool()); // Create worker collection // 初次运行,创建 executors, 后续直接调用 if ( getExecutor() == null ) { createExecutor(); } // 初始化最大连接数锁,tomcat8中默认为 10000 initializeConnectionLatch(); // Start poller threads // 最后,开启 n 个 Poller 处理; 默认为 2 pollers = new Poller[getPollerThreadCount()]; for (int i=0; i<pollers.length; i++) { pollers[i] = new Poller(); Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i); pollerThread.setPriority(threadPriority); pollerThread.setDaemon(true); pollerThread.start(); } // 最后,开启 Acceptor 监听 startAcceptorThreads(); } } // AbstractNioEndpoint public void createExecutor() { internalExecutor = true; // 队列基于 LinkedBlockingQueue, 即无限队列,但是又不是完全的 无限队列,如下实现 TaskQueue taskqueue = new TaskQueue(); TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority()); // 线程池: 最小默认 10, 最大默认: 200, executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf); taskqueue.setParent( (ThreadPoolExecutor) executor); } // taskqueue 的 队列插入与出队 @Override public boolean offer(Runnable o) { //we can't do any checks if (parent==null) return super.offer(o); //we are maxed out on threads, simply queue the object if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o); //we have idle threads, just add it to the queue // 如果当前有空闲线程,则直接入队,使用空闲线程处理即可,而如果按照原生 ThreadPoolExecutor 的逻辑,它会创建到 min 的线程时才会使用队列 if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o); //if we have less threads than maximum force creation of a new thread if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false; //if we reached here, we need to add it to the queue return super.offer(o); } @Override public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException { Runnable runnable = super.poll(timeout, unit); if (runnable == null && parent != null) { // the poll timed out, it gives an opportunity to stop the current // thread if needed to avoid memory leaks. parent.stopCurrentThreadIfNeeded(); } return runnable; }
6. http请求的处理
作为应用服务器,实际有相当大的应用场景,都是用于处理http请求,因此,http模块是其中举足轻重的模块。http是工作在socket之后的一个模块,当socket监听到连接后,会将请求转发给http模块,然后由http模块转发给业务代码实现业务处理。接收 http 请求的 poller 处理逻辑如下:
// NioEndpoint$Poller, 与前面的 BlockPoller 有点区别 /** * The background thread that adds sockets to the Poller, checks the * poller for triggered events and hands the associated socket off to an * appropriate processor as events occur. */ @Override public void run() { // Loop until destroy() is called while (true) { boolean hasEvents = false; try { if (!close) { hasEvents = events(); if (wakeupCounter.getAndSet(-1) > 0) { //if we are here, means we have other stuff to do //do a non blocking select keyCount = selector.selectNow(); } else { keyCount = selector.select(selectorTimeout); } wakeupCounter.set(0); } if (close) { events(); timeout(0, false); try { selector.close(); } catch (IOException ioe) { log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe); } break; } } catch (Throwable x) { ExceptionUtils.handleThrowable(x); log.error("",x); continue; } //either we timed out or we woke up, process events first if ( keyCount == 0 ) hasEvents = (hasEvents | events()); Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null; // Walk through the collection of ready keys and dispatch // any active event. while (iterator != null && iterator.hasNext()) { SelectionKey sk = iterator.next(); NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment(); // Attachment may be null if another thread has called // cancelledKey() if (attachment == null) { iterator.remove(); } else { iterator.remove(); processKey(sk, attachment); } }//while //process timeouts timeout(keyCount,hasEvents); }//while getStopLatch().countDown(); }
// NioEndpoint$Poller, 与前面的 BlockPoller 有点区别 /** * The background thread that adds sockets to the Poller, checks the * poller for triggered events and hands the associated socket off to an * appropriate processor as events occur. */ @Override public void run() { // Loop until destroy() is called while (true) { boolean hasEvents = false; try { if (!close) { hasEvents = events(); if (wakeupCounter.getAndSet(-1) > 0) { //if we are here, means we have other stuff to do //do a non blocking select keyCount = selector.selectNow(); } else { keyCount = selector.select(selectorTimeout); } wakeupCounter.set(0); } if (close) { events(); timeout(0, false); try { selector.close(); } catch (IOException ioe) { log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe); } break; } } catch (Throwable x) { ExceptionUtils.handleThrowable(x); log.error("",x); continue; } //either we timed out or we woke up, process events first if ( keyCount == 0 ) hasEvents = (hasEvents | events()); Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null; // Walk through the collection of ready keys and dispatch // any active event. while (iterator != null && iterator.hasNext()) { SelectionKey sk = iterator.next(); NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment(); // Attachment may be null if another thread has called // cancelledKey() if (attachment == null) { iterator.remove(); } else { iterator.remove(); processKey(sk, attachment); } }//while //process timeouts timeout(keyCount,hasEvents); }//while getStopLatch().countDown(); }
最后,开启 Acceptor 监听, 默认为 1
protected final void startAcceptorThreads() { int count = getAcceptorThreadCount(); acceptors = new Acceptor[count]; for (int i = 0; i < count; i++) { acceptors[i] = createAcceptor(); String threadName = getName() + "-Acceptor-" + i; acceptors[i].setThreadName(threadName); Thread t = new Thread(acceptors[i], threadName); t.setPriority(getAcceptorThreadPriority()); t.setDaemon(getDaemon()); t.start(); } } @Override protected AbstractEndpoint.Acceptor createAcceptor() { return new Acceptor(); } // Acceptor 是 NioEndpoint的内部类, 其处理逻辑如下 // 主要功能为: 1. 负责接入所有的请求处理进行分发; 2. 负责hold处超出最大连接的请求 @Override public void run() { int errorDelay = 0; // Loop until we receive a shutdown command while (running) { // Loop if endpoint is paused while (paused && running) { state = AcceptorState.PAUSED; try { Thread.sleep(50); } catch (InterruptedException e) { // Ignore } } if (!running) { break; } state = AcceptorState.RUNNING; try { //if we have reached max connections, wait countUpOrAwaitConnection(); SocketChannel socket = null; try { // Accept the next incoming connection from the server // socket socket = serverSock.accept(); } catch (IOException ioe) { // We didn't get a socket countDownConnection(); if (running) { // Introduce delay if necessary errorDelay = handleExceptionWithDelay(errorDelay); // re-throw throw ioe; } else { break; } } // Successful accept, reset the error delay errorDelay = 0; // Configure the socket if (running && !paused) { // setSocketOptions() will hand the socket off to // an appropriate processor if successful if (!setSocketOptions(socket)) { closeSocket(socket); } } else { closeSocket(socket); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("endpoint.accept.fail"), t); } } state = AcceptorState.ENDED; }
接到请求后交由外部类简单解析处理:
/** * Process the specified connection. * @param socket The socket channel * @return <code>true</code> if the socket was correctly configured * and processing may continue, <code>false</code> if the socket needs to be * close immediately */ protected boolean setSocketOptions(SocketChannel socket) { // Process the connection try { //disable blocking, APR style, we are gonna be polling it socket.configureBlocking(false); Socket sock = socket.socket(); socketProperties.setProperties(sock); NioChannel channel = nioChannels.pop(); if (channel == null) { SocketBufferHandler bufhandler = new SocketBufferHandler( socketProperties.getAppReadBufSize(), socketProperties.getAppWriteBufSize(), socketProperties.getDirectBuffer()); if (isSSLEnabled()) { channel = new SecureNioChannel(socket, bufhandler, selectorPool, this); } else { channel = new NioChannel(socket, bufhandler); } } else { channel.setIOChannel(socket); channel.reset(); } getPoller0().register(channel); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); try { log.error("",t); } catch (Throwable tt) { ExceptionUtils.handleThrowable(tt); } // Tell to close the socket return false; } return true; }
如上,整个启动过程就差不多了!还剩点关闭脚本,回到 Catalina:
// Register shutdown hook, 注册优雅停机 if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( false); } } // 等待关闭信号,通过等socket信号来处理,否则一直循环等待,不停止 main() 线程 if (await) { await(); stop(); }
下面是请求流程的开始:
// 接收请求是: org.apache.tomcat.util.net.NioEndpoint$Acceptor
// 接收到后, NioEndpoint.setSocketOptions(SocketChannel socket)
// 注册处理事件 NioEndpoint$Poller.register(final NioChannel socket)
// NioEndpoint$Poller.doRun 进行事件处理
/** * Registers a newly created socket with the poller. * * @param socket The newly created socket */ public void register(final NioChannel socket) { socket.setPoller(this); NioSocketWrapper ka = new NioSocketWrapper(socket, NioEndpoint.this); socket.setSocketWrapper(ka); ka.setPoller(this); ka.setReadTimeout(getSocketProperties().getSoTimeout()); ka.setWriteTimeout(getSocketProperties().getSoTimeout()); ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests()); ka.setSecure(isSSLEnabled()); ka.setReadTimeout(getConnectionTimeout()); ka.setWriteTimeout(getConnectionTimeout()); PollerEvent r = eventCache.pop(); ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into. if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER); else r.reset(socket,ka,OP_REGISTER); addEvent(r); } // 添加到事件Poller 队列, 并唤醒处理 selector private void addEvent(PollerEvent event) { events.offer(event); if ( wakeupCounter.incrementAndGet() == 0 ) selector.wakeup(); } // 后台消费队列, org.apache.tomcat.util.net.NioEndpoint$Poller /** * The background thread that adds sockets to the Poller, checks the * poller for triggered events and hands the associated socket off to an * appropriate processor as events occur. */ @Override public void run() { // Loop until destroy() is called while (true) { boolean hasEvents = false; try { if (!close) { hasEvents = events(); if (wakeupCounter.getAndSet(-1) > 0) { //if we are here, means we have other stuff to do //do a non blocking select keyCount = selector.selectNow(); } else { keyCount = selector.select(selectorTimeout); } wakeupCounter.set(0); } if (close) { events(); timeout(0, false); try { selector.close(); } catch (IOException ioe) { log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe); } break; } } catch (Throwable x) { ExceptionUtils.handleThrowable(x); log.error("",x); continue; } //either we timed out or we woke up, process events first if ( keyCount == 0 ) hasEvents = (hasEvents | events()); Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null; // Walk through the collection of ready keys and dispatch // any active event. while (iterator != null && iterator.hasNext()) { SelectionKey sk = iterator.next(); NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment(); // Attachment may be null if another thread has called // cancelledKey() if (attachment == null) { iterator.remove(); } else { iterator.remove(); processKey(sk, attachment); } }//while //process timeouts timeout(keyCount,hasEvents); }//while getStopLatch().countDown(); } // NioEndpoint$Poller.timeout() protected void timeout(int keyCount, boolean hasEvents) { long now = System.currentTimeMillis(); // This method is called on every loop of the Poller. Don't process // timeouts on every loop of the Poller since that would create too // much load and timeouts can afford to wait a few seconds. // However, do process timeouts if any of the following are true: // - the selector simply timed out (suggests there isn't much load) // - the nextExpiration time has passed // - the server socket is being closed if (nextExpiration > 0 && (keyCount > 0 || hasEvents) && (now < nextExpiration) && !close) { return; } //timeout int keycount = 0; try { for (SelectionKey key : selector.keys()) { keycount++; try { NioSocketWrapper ka = (NioSocketWrapper) key.attachment(); if ( ka == null ) { cancelledKey(key); //we don't support any keys without attachments } else if (close) { key.interestOps(0); ka.interestOps(0); //avoid duplicate stop calls processKey(key,ka); } else if ((ka.interestOps()&SelectionKey.OP_READ) == SelectionKey.OP_READ || (ka.interestOps()&SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) { boolean isTimedOut = false; // Check for read timeout if ((ka.interestOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) { long delta = now - ka.getLastRead(); long timeout = ka.getReadTimeout(); isTimedOut = timeout > 0 && delta > timeout; } // Check for write timeout if (!isTimedOut && (ka.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) { long delta = now - ka.getLastWrite(); long timeout = ka.getWriteTimeout(); isTimedOut = timeout > 0 && delta > timeout; } if (isTimedOut) { key.interestOps(0); ka.interestOps(0); //avoid duplicate timeout calls ka.setError(new SocketTimeoutException()); // 提交 excutor 执行逻辑,如交由框架处理 if (!processSocket(ka, SocketEvent.ERROR, true)) { cancelledKey(key); } } } }catch ( CancelledKeyException ckx ) { cancelledKey(key); } }//for } catch (ConcurrentModificationException cme) { // See https://bz.apache.org/bugzilla/show_bug.cgi?id=57943 log.warn(sm.getString("endpoint.nio.timeoutCme"), cme); } long prevExp = nextExpiration; //for logging purposes only nextExpiration = System.currentTimeMillis() + socketProperties.getTimeoutInterval(); if (log.isTraceEnabled()) { log.trace("timeout completed: keys processed=" + keycount + "; now=" + now + "; nextExpiration=" + prevExp + "; keyCount=" + keyCount + "; hasEvents=" + hasEvents + "; eval=" + ((now < prevExp) && (keyCount>0 || hasEvents) && (!close) )); } } }
// 处理业务逻辑 org.apache.tomcat.util.net.AbstractEndpoint.processSocket()
/** * Process the given SocketWrapper with the given status. Used to trigger * processing as if the Poller (for those endpoints that have one) * selected the socket. * * @param socketWrapper The socket wrapper to process * @param event The socket event to be processed * @param dispatch Should the processing be performed on a new * container thread * * @return if processing was triggered successfully */ public boolean processSocket(SocketWrapperBase<S> socketWrapper, SocketEvent event, boolean dispatch) { try { if (socketWrapper == null) { return false; } // 此处获取处理线程,即: org.apache.tomcat.util.net.NioEndpoint$SocketProcessor SocketProcessorBase<S> sc = processorCache.pop(); if (sc == null) { sc = createSocketProcessor(socketWrapper, event); } else { sc.reset(socketWrapper, event); } Executor executor = getExecutor(); // 如果有线程池,就异步处理,否则同步调用 if (dispatch && executor != null) { executor.execute(sc); } else { sc.run(); } } catch (RejectedExecutionException ree) { getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree); return false; } catch (Throwable t) { ExceptionUtils.handleThrowable(t); // This means we got an OOM or similar creating a thread, or that // the pool and its queue are full getLog().error(sm.getString("endpoint.process.fail"), t); return false; } return true; }
// org.apache.tomcat.util.net.SocketProcessorBase 处理 run(), 子类实现 doRun() 方法。
@Override public final void run() { synchronized (socketWrapper) { // It is possible that processing may be triggered for read and // write at the same time. The sync above makes sure that processing // does not occur in parallel. The test below ensures that if the // first event to be processed results in the socket being closed, // the subsequent events are not processed. if (socketWrapper.isClosed()) { return; } doRun(); } } // NioEndpoint$SocketProcessor 处理逻辑 /** * This class is the equivalent of the Worker, but will simply use in an * external Executor thread pool. */ protected class SocketProcessor extends SocketProcessorBase<NioChannel> { public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) { super(socketWrapper, event); } @Override protected void doRun() { NioChannel socket = socketWrapper.getSocket(); SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector()); try { int handshake = -1; try { if (key != null) { if (socket.isHandshakeComplete()) { // No TLS handshaking required. Let the handler // process this socket / event combination. handshake = 0; } else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT || event == SocketEvent.ERROR) { // Unable to complete the TLS handshake. Treat it as // if the handshake failed. handshake = -1; } else { handshake = socket.handshake(key.isReadable(), key.isWritable()); // The handshake process reads/writes from/to the // socket. status may therefore be OPEN_WRITE once // the handshake completes. However, the handshake // happens when the socket is opened so the status // must always be OPEN_READ after it completes. It // is OK to always set this as it is only used if // the handshake completes. event = SocketEvent.OPEN_READ; } } } catch (IOException x) { handshake = -1; if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x); } catch (CancelledKeyException ckx) { handshake = -1; } if (handshake == 0) { SocketState state = SocketState.OPEN; // Process the request from this socket // 转交控控权给业务系统 if (event == null) { state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ); } else { state = getHandler().process(socketWrapper, event); } if (state == SocketState.CLOSED) { close(socket, key); } } else if (handshake == -1 ) { close(socket, key); } else if (handshake == SelectionKey.OP_READ){ socketWrapper.registerReadInterest(); } else if (handshake == SelectionKey.OP_WRITE){ socketWrapper.registerWriteInterest(); } } catch (CancelledKeyException cx) { socket.getPoller().cancelledKey(key); } catch (VirtualMachineError vme) { ExceptionUtils.handleThrowable(vme); } catch (Throwable t) { log.error("", t); socket.getPoller().cancelledKey(key); } finally { socketWrapper = null; event = null; //return to cache if (running && !paused) { processorCache.push(this); } } } }
// org.apache.coyote.AbstractProtocol$ConnectionHandler -> 获取到 @Override public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.process", wrapper.getSocket(), status)); } if (wrapper == null) { // Nothing to do. Socket has been closed. return SocketState.CLOSED; } S socket = wrapper.getSocket(); Processor processor = connections.get(socket); if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.connectionsGet", processor, socket)); } // Async timeouts are calculated on a dedicated thread and then // dispatched. Because of delays in the dispatch process, the // timeout may no longer be required. Check here and avoid // unnecessary processing. if (SocketEvent.TIMEOUT == status && (processor == null || !processor.isAsync() || !processor.checkAsyncTimeoutGeneration())) { // This is effectively a NO-OP return SocketState.OPEN; } if (processor != null) { // Make sure an async timeout doesn't fire getProtocol().removeWaitingProcessor(processor); } else if (status == SocketEvent.DISCONNECT || status == SocketEvent.ERROR) { // Nothing to do. Endpoint requested a close and there is no // longer a processor associated with this socket. return SocketState.CLOSED; } ContainerThreadMarker.set(); try { if (processor == null) { String negotiatedProtocol = wrapper.getNegotiatedProtocol(); if (negotiatedProtocol != null) { UpgradeProtocol upgradeProtocol = getProtocol().getNegotiatedProtocol(negotiatedProtocol); if (upgradeProtocol != null) { processor = upgradeProtocol.getProcessor( wrapper, getProtocol().getAdapter()); } else if (negotiatedProtocol.equals("http/1.1")) { // Explicitly negotiated the default protocol. // Obtain a processor below. } else { // TODO: // OpenSSL 1.0.2's ALPN callback doesn't support // failing the handshake with an error if no // protocol can be negotiated. Therefore, we need to // fail the connection here. Once this is fixed, // replace the code below with the commented out // block. if (getLog().isDebugEnabled()) { getLog().debug(sm.getString( "abstractConnectionHandler.negotiatedProcessor.fail", negotiatedProtocol)); } return SocketState.CLOSED; /* * To replace the code above once OpenSSL 1.1.0 is * used. // Failed to create processor. This is a bug. throw new IllegalStateException(sm.getString( "abstractConnectionHandler.negotiatedProcessor.fail", negotiatedProtocol)); */ } } } if (processor == null) { processor = recycledProcessors.pop(); if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.processorPop", processor)); } } if (processor == null) { processor = getProtocol().createProcessor(); register(processor); } processor.setSslSupport( wrapper.getSslSupport(getProtocol().getClientCertProvider())); // Associate the processor with the connection connections.put(socket, processor); SocketState state = SocketState.CLOSED; do { state = processor.process(wrapper, status); if (state == SocketState.UPGRADING) { // Get the HTTP upgrade handler UpgradeToken upgradeToken = processor.getUpgradeToken(); // Retrieve leftover input ByteBuffer leftOverInput = processor.getLeftoverInput(); if (upgradeToken == null) { // Assume direct HTTP/2 connection UpgradeProtocol upgradeProtocol = getProtocol().getUpgradeProtocol("h2c"); if (upgradeProtocol != null) { processor = upgradeProtocol.getProcessor( wrapper, getProtocol().getAdapter()); wrapper.unRead(leftOverInput); // Associate with the processor with the connection connections.put(socket, processor); } else { if (getLog().isDebugEnabled()) { getLog().debug(sm.getString( "abstractConnectionHandler.negotiatedProcessor.fail", "h2c")); } return SocketState.CLOSED; } } else { HttpUpgradeHandler httpUpgradeHandler = upgradeToken.getHttpUpgradeHandler(); // Release the Http11 processor to be re-used release(processor); // Create the upgrade processor processor = getProtocol().createUpgradeProcessor(wrapper, upgradeToken); if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.upgradeCreate", processor, wrapper)); } wrapper.unRead(leftOverInput); // Mark the connection as upgraded wrapper.setUpgraded(true); // Associate with the processor with the connection connections.put(socket, processor); // Initialise the upgrade handler (which may trigger // some IO using the new protocol which is why the lines // above are necessary) // This cast should be safe. If it fails the error // handling for the surrounding try/catch will deal with // it. if (upgradeToken.getInstanceManager() == null) { httpUpgradeHandler.init((WebConnection) processor); } else { ClassLoader oldCL = upgradeToken.getContextBind().bind(false, null); try { httpUpgradeHandler.init((WebConnection) processor); } finally { upgradeToken.getContextBind().unbind(false, oldCL); } } } } } while ( state == SocketState.UPGRADING); if (state == SocketState.LONG) { // In the middle of processing a request/response. Keep the // socket associated with the processor. Exact requirements // depend on type of long poll longPoll(wrapper, processor); if (processor.isAsync()) { getProtocol().addWaitingProcessor(processor); } } else if (state == SocketState.OPEN) { // In keep-alive but between requests. OK to recycle // processor. Continue to poll for the next request. connections.remove(socket); release(processor); wrapper.registerReadInterest(); } else if (state == SocketState.SENDFILE) { // Sendfile in progress. If it fails, the socket will be // closed. If it works, the socket either be added to the // poller (or equivalent) to await more data or processed // if there are any pipe-lined requests remaining. } else if (state == SocketState.UPGRADED) { // Don't add sockets back to the poller if this was a // non-blocking write otherwise the poller may trigger // multiple read events which may lead to thread starvation // in the connector. The write() method will add this socket // to the poller if necessary. if (status != SocketEvent.OPEN_WRITE) { longPoll(wrapper, processor); } } else if (state == SocketState.SUSPENDED) { // Don't add sockets back to the poller. // The resumeProcessing() method will add this socket // to the poller. } else { // Connection closed. OK to recycle the processor. Upgrade // processors are not recycled. connections.remove(socket); if (processor.isUpgrade()) { UpgradeToken upgradeToken = processor.getUpgradeToken(); HttpUpgradeHandler httpUpgradeHandler = upgradeToken.getHttpUpgradeHandler(); InstanceManager instanceManager = upgradeToken.getInstanceManager(); if (instanceManager == null) { httpUpgradeHandler.destroy(); } else { ClassLoader oldCL = upgradeToken.getContextBind().bind(false, null); try { httpUpgradeHandler.destroy(); } finally { try { instanceManager.destroyInstance(httpUpgradeHandler); } catch (Throwable e) { ExceptionUtils.handleThrowable(e); getLog().error(sm.getString("abstractConnectionHandler.error"), e); } upgradeToken.getContextBind().unbind(false, oldCL); } } } else { release(processor); } } return state; } catch(java.net.SocketException e) { // SocketExceptions are normal getLog().debug(sm.getString( "abstractConnectionHandler.socketexception.debug"), e); } catch (java.io.IOException e) { // IOExceptions are normal getLog().debug(sm.getString( "abstractConnectionHandler.ioexception.debug"), e); } catch (ProtocolException e) { // Protocol exceptions normally mean the client sent invalid or // incomplete data. getLog().debug(sm.getString( "abstractConnectionHandler.protocolexception.debug"), e); } // Future developers: if you discover any other // rare-but-nonfatal exceptions, catch them here, and log as // above. catch (Throwable e) { ExceptionUtils.handleThrowable(e); // any other exception or error is odd. Here we log it // with "ERROR" level, so it will show up even on // less-than-verbose logs. getLog().error(sm.getString("abstractConnectionHandler.error"), e); } finally { ContainerThreadMarker.clear(); } // Make sure socket/processor is removed from the list of current // connections connections.remove(socket); release(processor); return SocketState.CLOSED; } // org.apache.coyote.http11.Http11Processor -> org.apache.coyote.AbstractProcessorLight @Override public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status) throws IOException { SocketState state = SocketState.CLOSED; Iterator<DispatchType> dispatches = null; do { if (dispatches != null) { DispatchType nextDispatch = dispatches.next(); state = dispatch(nextDispatch.getSocketStatus()); } else if (status == SocketEvent.DISCONNECT) { // Do nothing here, just wait for it to get recycled } else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) { state = dispatch(status); if (state == SocketState.OPEN) { // There may be pipe-lined data to read. If the data isn't // processed now, execution will exit this loop and call // release() which will recycle the processor (and input // buffer) deleting any pipe-lined data. To avoid this, // process it now. state = service(socketWrapper); } } else if (status == SocketEvent.OPEN_WRITE) { // Extra write event likely after async, ignore state = SocketState.LONG; } else if (status == SocketEvent.OPEN_READ){ state = service(socketWrapper); } else { // Default to closing the socket if the SocketEvent passed in // is not consistent with the current state of the Processor state = SocketState.CLOSED; } if (getLog().isDebugEnabled()) { getLog().debug("Socket: [" + socketWrapper + "], Status in: [" + status + "], State out: [" + state + "]"); } if (state != SocketState.CLOSED && isAsync()) { state = asyncPostProcess(); if (getLog().isDebugEnabled()) { getLog().debug("Socket: [" + socketWrapper + "], State after async post processing: [" + state + "]"); } } if (dispatches == null || !dispatches.hasNext()) { // Only returns non-null iterator if there are // dispatches to process. dispatches = getIteratorAndClearDispatches(); } } while (state == SocketState.ASYNC_END || dispatches != null && state != SocketState.CLOSED); return state; }
启动过程接口调用时序图如下(图片来源于网络):
已经很接近了,看到已经走到了service方法,与servlet的service只差一步之遥……
最后,我们以一个 http 请求的栈跟踪作为结束语吧,从中我可以清楚地看到整个处理线程运行情况!
"http-apr-8080-exec-7@4763" daemon prio=5 tid=0x30 nid=NA runnable java.lang.Thread.State: RUNNABLE at com.xxx.controller.HelloController.test(HelloController.java:40) at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) at javax.servlet.http.HttpServlet.service(HttpServlet.java:624) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:731) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:442) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1082) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:623) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2517) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2506) - locked <0x13dc> (a org.apache.tomcat.util.net.AprEndpoint$AprSocketWrapper) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745)
未完,待续...