嵌入式 Tomcat 调校

SpringBoot 嵌入了 Web 容器如 Tomcat/Jetty/Undertow,——这是怎么做到的?我们以 Tomcat 为例子,尝试调用嵌入式 Tomcat。

调用嵌入式 Tomcat,如果按照默认去启动,一个 main 函数就可以了。

简单的例子

下面是启动 Tomcat 的一个简单例子。

Tomcat tomcat = new Tomcat();
tomcat.enableNaming();
tomcat.getHost().setAutoDeploy(false);
tomcat.getHost().setAppBase("webapp");
// 在对应的 host 下面创建一个 context 并制定他的工作路径,会加载该目录下的所有 class 文件,或者静态文件
//        tomcat.setBaseDir(Thread.currentThread().getContextClassLoader().getResource("").getPath()); // 设置 tomcat 启动后的工作目录
//        System.out.println(Thread.currentThread().getContextClassLoader().getResource("").getPath());// 读取项目路径
System.out.println(System.getProperty("user.dir"));
String jspDir = System.getProperty("user.dir");
StandardContext ctx = (StandardContext) tomcat.addWebapp("/", new File(jspDir).getAbsolutePath());
ctx.setReloadable(false);// 禁止重新载入
WebResourceRoot resources = new StandardRoot(ctx);// 创建WebRoot
resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes", new File("target/classes").getAbsolutePath(), "/"));// tomcat 内部读取 Class 执行// 创建连接器,并且添加对应的连接器,同时连接器指定端口 设置 IO 协议
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(port);
connector.setThrowOnFailure(true);tomcat.getService().addConnector(connector);// 只能设置一个 service,直接拿默认的
tomcat.setConnector(connector); // 设置执行器try {tomcat.start(); // tomcat 启动
} catch (LifecycleException e) {throw new RuntimeException(e);
}tomcat.getServer().await(); // 保持主线程不退出,让其阻塞,不让当前线程结束,等待处理请求

配置化你的 Tomcat

当然,我们不会满足于默认的 Tomcat 配置。Tomcat 本身提供开放的配置选项,一般是 server.xml 或 web.xml 的形式,而换到嵌入式 Tomcat 的话,那些 xml 配置则不可用了,于是我们得采取手动编码(Programmatically)在 Java 完成配置。

面对众多的 Tomcat 配置,我们选出下面若干最常见的。

import lombok.Data;
import org.springframework.util.StringUtils;/*** Tomcat 配置参数*/
@Data
public class TomcatConfig {/*** 主机名称*/private String hostName = "localhost";/*** 访问的端口*/private Integer port = 8082;/*** Web 上下文目录*/private String contextPath;/*** Web 目录的磁盘路径,如 D:/1sync/static*/private String docBase;/*** Tomcat 临时文件的目录*/private String tomcatBaseDir;/*** 关闭的端口*/private Integer shutdownPort = 8005;/*** 是否激活 SSI(服务器端嵌入)*/private Boolean enableSsi = false;/*** 是否激活 JSP*/private Boolean enableJsp = true;/*** 是否激活 JMX 监控*/private boolean enableJMX = false;/*** 自定义连接器*/private boolean customerConnector = false;/*** 最大工作线程数 Maximum amount of worker threads.*/private int maxThreads = 0;/*** 最小工作线程数,默认是 10。Minimum amount of worker threads. if not set, default value is 10*/private int minSpareThreads = 0;/*** 当客户端从 Tomcat 获取数据时候,距离关闭连接的等待时间* When Tomcat expects data from the client, this is the time Tomcat will wait for that data to arrive before closing the connection.*/private int connectionTimeout = 0;/*** 最大连接数* Maximum number of connections that the server will accept and process at any* given time. Once the limit has been reached, the operating system may still* accept connections based on the "acceptCount" property.*/private int maxConnections = 0;/*** 当请求超过可用的线程试试,最大的请求排队数* Maximum queue length for incoming connection requests when all possible request processing threads are in use.*/private int acceptCount = 0;/*** Tomcat 临时文件的目录。如果不需要(如不需要 jsp)禁止 work dir。* Tomcat needs a directory for temp files. This should be the first method called.** <p>* By default, if this method is not called, we use:* <ul>*  <li>system properties - catalina.base, catalina.home</li>*  <li>$PWD/tomcat.$PORT</li>* </ul>* (/tmp doesn't seem a good choice for security).** <p>* TODO: disable work dir if not needed ( no jsp, etc ).*/public void setTomcatBaseDir(String tomcatBaseDir) {this.tomcatBaseDir = tomcatBaseDir;}public String getContextPath() {return StringUtils.hasText(contextPath) ? contextPath : "";}
}

hostName 主机名称、port 端口这些大家应该都知道,就不多说了。其他有关配置说明如下:

  • Web 上下文目录 contextPath。就是第一级的目录,你可以不设,但不要设为/,否则会有警告;设为空字符串""就好。一般都加上。
  • Web 目录的磁盘路径 docBase,就是 WebRoot 对应的磁盘目录,如 D:/1sync/static,浏览器可以访问这里的静态文件和 JSP 文件等。
  • Tomcat 临时文件的目录,tomcatBaseDir。可不设,默认system properties - catalina.base, catalina.home$PWD/tomcat.$PORT。如果不需要运行 JSP,或者可以禁止该目录
  • enableSsi 是否激活 SSI(服务器端嵌入)
  • 关闭的端口 shutdownPort。可以通过 Socket 关闭 tomcat:telnet 127.0.0.1 8005,输入SHUTDOWN字符串(后面有介绍方法)
  • 是否激活 JSP enableJsp
  • 是否激活 JMX 监控 enableJMX。用于 JMX 监控,关闭会提高启动速度
  • 其他并发的性能调优 maxThreads、minSpareThreads、connectionTimeout、maxConnections、acceptCount

启动 Tomcat

有了配置,自然可以启动 Tomcat,我们把TomcatConfig作为构造器参数传给TomcatStarter解析各个参数去配置 Tomcat 最终启动。

如下是按照默认参数启动。

TomcatConfig cfg = new TomcatConfig();
TomcatStarter t = new TomcatStarter(cfg);
t.start();

在这里插入图片描述
另外补充一下两个配置的地方:

  • 禁止 Tomcat 自动扫描 jar 包,会提高启动速度
  • Tomcat 的 startStopThreads 属性用于配置 Tomcat 服务器启动和关闭时的线程池大小。它决定了 Tomcat 在启动和关闭过程中能够同时处理的任务数。但对于 Tomcat 8,没有直接的编程方式来设置 startStopThreads 属性
  • 下面设置:设置核心线程数和最大线程数,又不会走到这里,这是悬而未决的问题

在这里插入图片描述

完整TomcatStarter源码如下。

import com.ajaxjs.Version;
import com.ajaxjs.framework.embeded_tomcat.jar_scan.EmbededContextConfig;
import com.ajaxjs.util.io.FileHelper;
import com.ajaxjs.util.io.Resources;
import com.ajaxjs.util.logger.LogHelper;
import org.apache.catalina.*;
import org.apache.catalina.WebResourceRoot.ResourceSetType;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.webresources.DirResourceSet;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import org.apache.tomcat.util.scan.StandardJarScanFilter;import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.servlet.Filter;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.registry.LocateRegistry;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;/*** Tomcat 的功能*/
public class TomcatStarter {private static final LogHelper LOGGER = LogHelper.getLog(TomcatStarter.class);public TomcatStarter(TomcatConfig cfg) {this.cfg = cfg;}TomcatConfig cfg;Tomcat tomcat;/*** 获取监控信息用*/public static Tomcat TOMCAT;Context context;public static long startedTime;public static long springTime;public void start() {startedTime = System.currentTimeMillis();initTomcat();initConnector();initContext();runTomcat();}private void initTomcat() {tomcat = new Tomcat();tomcat.setPort(cfg.getPort());tomcat.setHostname(cfg.getHostName());tomcat.enableNaming();//        String tomcatBaseDir = cfg.getTomcatBaseDir();
//
//        if (tomcatBaseDir == null)
//            tomcatBaseDir = TomcatUtil.createTempDir("tomcat_embed_works_tmpdir").getAbsolutePath();
//
//        tomcat.setBaseDir(tomcatBaseDir);TOMCAT = tomcat;}private void runTomcat() {try {tomcat.start(); // tomcat 启动} catch (LifecycleException e) {LOGGER.warning(e);throw new RuntimeException(e);}Runtime.getRuntime().addShutdownHook(new Thread(() -> {try {LOGGER.info("关闭 Tomcat");tomcat.destroy();} catch (LifecycleException e) {LOGGER.warning(e);}}));//        ac.register(clz);
//        ac.refresh();
//        ac.registerShutdownHook();String tpl = "Web 服务启动完毕。Spring 耗时:%sms,总耗时:%sms 127.0.0.1:" + cfg.getPort() + cfg.getContextPath();tpl = String.format(tpl, springTime, System.currentTimeMillis() - startedTime);LOGGER.info(tpl);// 注册关闭端口以进行关闭// 可以通过Socket关闭tomcat: telnet 127.0.0.1 8005,输入SHUTDOWN字符串tomcat.getServer().setPort(cfg.getShutdownPort());tomcat.getServer().await(); // 保持主线程不退出,让其阻塞,不让当前线程结束,等待处理请求LOGGER.info("正在关闭 Tomcat,shutdown......");try {tomcat.stop();} catch (LifecycleException e) {LOGGER.warning(e);}// 删除 tomcat 临时路径
//        TomcatUtil.deleteAllFilesOfDir(tomcatBaseDirFile);}/*** 读取项目路径*/private void initContext() {String jspFolder = getDevelopJspFolder();if (jspFolder == null) {jspFolder = Resources.getJarDir() + "/../webapp"; // 部署阶段。这个并不会实际保存 jsp。因为 jsp 都在 META-INF/resources 里面。但因为下面的 addWebapp() 又需要FileHelper.mkDir(jspFolder);}//        System.out.println("jspFolder::::::" + Resources.getJarDir());
//        StandardContext ctx = (StandardContext) tomcat.addWebapp("/", new File("/mycar/mycar-service-4.0/security-oauth2-uam/sync/jsp").getAbsolutePath());
//        context = tomcat.addWebapp(contextPath, jspFolder);Host host = tomcat.getHost();host.setAutoDeploy(false);host.setAppBase("webapp");context = tomcat.addWebapp(host, cfg.getContextPath(), jspFolder, (LifecycleListener) new EmbededContextConfig());context.setReloadable(false);// 禁止重新载入context.addLifecycleListener(new Tomcat.FixContextListener());// required if you don't use web.xml// seems not workWebResourceRoot resources = new StandardRoot(context);// 创建 WebRootString classDir = new File("target/classes").getAbsolutePath();resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes", classDir, "/"));// tomcat 内部读取 Class 执行if (cfg.getEnableSsi())ssi();if (!cfg.getEnableJsp())disableJsp();//        context.setJarScanner(new EmbeddedStandardJarScanner());
//        context.setParentClassLoader(TomcatStarter.class.getClassLoader());// needs?addWebXmlMountListener();setTomcatDisableScan();
//        initFilterByTomcat(UTF8CharsetFilter.class);}public static String getDevelopJspFolder() {return Resources.getResourcesFromClasspath("META-INF\\resources");// 开放调试阶段,直接读取源码的}/*** 禁止 Tomcat 自动扫描 jar 包,那样会很慢*/private void setTomcatDisableScan() {StandardJarScanFilter filter = (StandardJarScanFilter) context.getJarScanner().getJarScanFilter();filter.setDefaultTldScan(false);/** 这个对启动 tomcat 时间影响很大 又 很多 Servlet 3.0 新特性,不能禁掉,比如在 jar 里面放* jsp(部署时候就会这样,但开放阶段不用)。 故,用 isDebug 判断下*/if (Version.isDebug)filter.setDefaultPluggabilityScan(false);
//      String oldTldSkip = filter.getTldSkip();
//      System.out.println("-------" + oldTldSkip);
//      String newTldSkip = oldTldSkip == null || oldTldSkip.trim().isEmpty() ? "pdq.jar" : oldTldSkip + ",pdq.jar";
//      filter.setTldSkip(newTldSkip);}/*** 设置 Connector*/void initConnector() {Connector connector;if (cfg.isCustomerConnector()) {// 创建连接器,并且添加对应的连接器,同时连接器指定端口 设置 IO 协议connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");connector.setPort(cfg.getPort());connector.setThrowOnFailure(true);tomcat.getService().addConnector(connector);// 只能设置一个 service,直接拿默认的tomcat.setConnector(connector); // 设置执行器} elseconnector = tomcat.getConnector();connector.setURIEncoding("UTF-8"); // 设置 URI 编码支持中文ProtocolHandler handler = connector.getProtocolHandler();// 设置 Tomcat 配置if (handler instanceof AbstractProtocol) {AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler;if (cfg.getMinSpareThreads() > 0)protocol.setMinSpareThreads(cfg.getMinSpareThreads());if (cfg.getMaxThreads() > 0)protocol.setMaxThreads(cfg.getMaxThreads());if (cfg.getConnectionTimeout() > 0)protocol.setConnectionTimeout(cfg.getConnectionTimeout());if (cfg.getMaxConnections() > 0)protocol.setMaxConnections(cfg.getMaxConnections());if (cfg.getAcceptCount() > 0)protocol.setAcceptCount(cfg.getAcceptCount());}// Tomcat 的 startStopThreads 属性用于配置 Tomcat 服务器启动和关闭时的线程池大小。它决定了 Tomcat 在启动和关闭过程中能够同时处理的任务数。// 对于 Tomcat 8,没有直接的编程方式来设置 startStopThreads 属性Executor executor = handler.getExecutor();if (executor instanceof ThreadPoolExecutor) {// doesn't workThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;threadPoolExecutor.setCorePoolSize(3);// 设置核心线程数和最大线程数threadPoolExecutor.setMaximumPoolSize(3);}if (cfg.isEnableJMX()) {Connector jmxConnector = new Connector("org.apache.coyote.jmx.JmxProtocol");jmxConnector.setPort(8999); // Set the desired JMX porttomcat.getService().addConnector(jmxConnector);}}/*** context load WEB-INF/web.xml from classpath*/void addWebXmlMountListener() {context.addLifecycleListener(event -> {if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {Context context = (Context) event.getLifecycle();WebResourceRoot resources = context.getResources();if (resources == null) {resources = new StandardRoot(context);context.setResources(resources);}/** When run as embedded tomcat, context.getParentClassLoader() is AppClassLoader,so it can load "WEB-INF/web.xml" from app classpath.*/URL resource = context.getParentClassLoader().getResource("WEB-INF/web.xml");if (resource != null) {String webXmlUrlString = resource.toString();try {URL root = new URL(webXmlUrlString.substring(0, webXmlUrlString.length() - "WEB-INF/web.xml".length()));resources.createWebResourceSet(ResourceSetType.RESOURCE_JAR, "/WEB-INF", root, "/WEB-INF");} catch (MalformedURLException e) {LOGGER.warning(e);}}}});}/*** 禁用 JSP*/void disableJsp() {LifecycleListener tmplf = null;for (LifecycleListener lfl : context.findLifecycleListeners()) {if (lfl instanceof Tomcat.DefaultWebXmlListener) {tmplf = lfl;break;}}if (tmplf != null)context.removeLifecycleListener(tmplf);context.addLifecycleListener(event -> {if (Lifecycle.BEFORE_START_EVENT.equals(event.getType())) {Context context = (Context) event.getLifecycle();Tomcat.initWebappDefaults(context);// 去掉JSPcontext.removeServletMapping("*.jsp");context.removeServletMapping("*.jspx");context.removeChild(context.findChild("jsp"));}});}/*** 在 Tomcat 初始化阶段设置 Filter*/@SuppressWarnings("unused")private void initFilterByTomcat(Class<? extends Filter> filterClz) {FilterDef filter1definition = new FilterDef();filter1definition.setFilterName(filterClz.getSimpleName());filter1definition.setFilterClass(filterClz.getName());context.addFilterDef(filter1definition);FilterMap filter1mapping = new FilterMap();filter1mapping.setFilterName(filterClz.getSimpleName());filter1mapping.addURLPattern("/*");context.addFilterMap(filter1mapping);}/*** 将定义好的 Tomcat MBean 注册到 MBeanServer* 参见 <a href="https://blog.csdn.net/zhangxin09/article/details/132136748">...</a>*/private static void connectMBeanServer() {try {LocateRegistry.createRegistry(9011); //这个步骤很重要,注册一个端口,绑定url  后用于客户端通过 rmi 方式连接 JMXConnectorServerJMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:9011/jmxrmi"), null, ManagementFactory.getPlatformMBeanServer() // 获取当前 JVM 的 MBeanServer,ObjectName 是 MBean 的唯一标示,一个 MBeanServer 不能有重复。// 完整的格式「自定义命名空间:type=自定义类型,name=自定义名称」。当然你可以只声明 type ,不声明 name);cs.start();LOGGER.info("成功启动 JMXConnectorServer");} catch (IOException e) {LOGGER.warning(e);}}/*** SSI(服务器端嵌入)*/void ssi() {context.setPrivileged(true);Wrapper servlet = Tomcat.addServlet(context, "ssi", "org.apache.catalina.ssi.SSIServlet");servlet.addInitParameter("buffered", "1");servlet.addInitParameter("inputEncoding", "UTF-8");servlet.addInitParameter("outputEncoding", "UTF-8");servlet.addInitParameter("debug", "0");servlet.addInitParameter("expires", "666");servlet.addInitParameter("isVirtualWebappRelative", "4");servlet.setLoadOnStartup(4);servlet.setOverridable(true);// Servlet mappingscontext.addServletMappingDecoded("*.html", "ssi");context.addServletMappingDecoded("*.shtml", "ssi");}
}

无非就是按部就班地执行如下

在这里插入图片描述

增强特性

下面特性好像用处不大,大家视情况加入。

EmbededContextConfig

扫描包含 web-fragment.xml 文件的 JAR 文件,以查看它们是否还包含静态资源,并将其添加到上下文中。 如果找到静态资源,则按照 web-fragment.xml 的优先级顺序添加。

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Set;import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.startup.ContextConfig;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.descriptor.web.WebXml;
import org.apache.tomcat.Jar;
import org.apache.tomcat.util.scan.JarFactory;/*** Support jar in jar. when boot by spring boot loader, jar url will be: fat.jar!/lib/!/test.jar!/ .*/
public class EmbededContextConfig extends ContextConfig {private static final Log log = LogFactory.getLog(EmbededContextConfig.class);/*** 扫描包含 web-fragment.xml 文件的 JAR 文件,以查看它们是否还包含静态资源,并将其添加到上下文中。* 如果找到静态资源,则按照 web-fragment.xml 的优先级顺序添加。* Scan JARs that contain web-fragment.xml files that will be used to* configure this application to see if they also contain static resources. If static resources are found,* add them to the context. Resources are added in web-fragment.xml priority order.*/@Overrideprotected void processResourceJARs(Set<WebXml> fragments) {for (WebXml fragment : fragments) {URL url = fragment.getURL();String urlString = url.toString();// It's a nested jar, but we now don't want the suffix// because Tomcat is going to try and locate it as a root URL (not the resource inside it)if (isInsideNestedJar(urlString))urlString = urlString.substring(0, urlString.length() - 2);try {url = new URL(urlString);if ("jar".equals(url.getProtocol())) {try (Jar jar = JarFactory.newInstance(url)) {jar.nextEntry();String entryName = jar.getEntryName();while (entryName != null) {if (entryName.startsWith("META-INF/resources/")) {context.getResources().createWebResourceSet(WebResourceRoot.ResourceSetType.RESOURCE_JAR,"/", url, "/META-INF/resources");break;}jar.nextEntry();entryName = jar.getEntryName();}}} else if ("file".equals(url.getProtocol())) {File file = new File(url.toURI());File resources = new File(file, "META-INF/resources/");if (resources.isDirectory())context.getResources().createWebResourceSet(WebResourceRoot.ResourceSetType.RESOURCE_JAR, "/",resources.getAbsolutePath(), null, "/");}} catch (IOException | URISyntaxException ioe) {log.error(sm.getString("contextConfig.resourceJarFail", url, context.getName()));}}}private static boolean isInsideNestedJar(String dir) {return dir.indexOf("!/") < dir.lastIndexOf("!/");}
}

使用方式

 context = tomcat.addWebapp(host, cfg.getContextPath(), jspFolder, (LifecycleListener) new EmbededContextConfig());

EmbeddedStandardJarScanner

老实说,我也不太懂用来干嘛的。先记着,,

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;import javax.servlet.ServletContext;import lombok.Data;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.JarScanFilter;
import org.apache.tomcat.JarScanType;
import org.apache.tomcat.JarScanner;
import org.apache.tomcat.JarScannerCallback;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.descriptor.web.FragmentJarScannerCallback;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.scan.Constants;
import org.apache.tomcat.util.scan.JarFileUrlJar;
import org.apache.tomcat.util.scan.StandardJarScanFilter;
import org.apache.tomcat.util.scan.UrlJar;/*** When boot by SpringBoot loader, WebappClassLoader.getParent() is LaunchedURLClassLoader,* Just need to scan WebappClassLoader and LaunchedURLClassLoader.* When boot in IDE, WebappClassLoader.getParent() is AppClassLoader,* Just need to scan WebappClassLoader and AppClassLoader.*/
@Data
public class EmbeddedStandardJarScanner implements JarScanner {private static final Log log = LogFactory.getLog(EmbeddedStandardJarScanner.class);/*** The string resources for this package.*/private static final StringManager sm = StringManager.getManager(Constants.Package);/*** Controls the classpath scanning extension.*/private boolean scanClassPath = true;/*** Controls the testing all files to see of they are JAR files extension.*/private boolean scanAllFiles = false;/*** Controls the testing all directories to see of they are exploded JAR* files extension.*/private boolean scanAllDirectories = false;/*** Controls the testing of the bootstrap classpath which consists of the* runtime classes provided by the JVM and any installed system extensions.*/private boolean scanBootstrapClassPath = false;/*** Controls the filtering of the results from the scan for JARs*/private JarScanFilter jarScanFilter = new StandardJarScanFilter();@Overridepublic JarScanFilter getJarScanFilter() {return jarScanFilter;}@Overridepublic void setJarScanFilter(JarScanFilter jarScanFilter) {this.jarScanFilter = jarScanFilter;}/*** Scan the provided ServletContext and class loader for JAR files. Each JAR* file found will be passed to the callback handler to be processed.** @param scanType The type of JAR scan to perform. This is passed to the filter which uses it to determine how to filter the results* @param context  The ServletContext - used to locate and access WEB-INF/lib* @param callback The handler to process any JARs found*/@Overridepublic void scan(JarScanType scanType, ServletContext context, JarScannerCallback callback) {if (log.isTraceEnabled())log.trace(sm.getString("jarScan.webinflibStart"));Set<URL> processedURLs = new HashSet<>();// Scan WEB-INF/libSet<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB);if (dirList != null) {Iterator<String> it = dirList.iterator();while (it.hasNext()) {String path = it.next();if (path.endsWith(Constants.JAR_EXT) && getJarScanFilter().check(scanType, path.substring(path.lastIndexOf('/') + 1))) {// Need to scan this JARif (log.isDebugEnabled())log.debug(sm.getString("jarScan.webinflibJarScan", path));URL url = null;try {url = context.getResource(path);processedURLs.add(url);process(scanType, callback, url, path, true);} catch (IOException e) {log.warn(sm.getString("jarScan.webinflibFail", url), e);}} else if (log.isTraceEnabled())log.trace(sm.getString("jarScan.webinflibJarNoScan", path));}}// Scan WEB-INF/classesif (isScanAllDirectories()) {try {URL url = context.getResource("/WEB-INF/classes/META-INF");if (url != null) {// Class path scanning will look at WEB-INF/classes since that is the URL that Tomcat's web application class// loader returns. Therefore, it is this URL that needs to be added to the set of processed URLs.URL webInfURL = context.getResource("/WEB-INF/classes");if (webInfURL != null)processedURLs.add(webInfURL);try {callback.scanWebInfClasses();} catch (IOException e) {log.warn(sm.getString("jarScan.webinfclassesFail"), e);}}} catch (MalformedURLException e) {// Ignore}}// Scan the classpathif (isScanClassPath()) {if (log.isTraceEnabled())log.trace(sm.getString("jarScan.classloaderStart"));ClassLoader classLoader = context.getClassLoader();ClassLoader stopLoader = null;if (classLoader.getParent() != null) {// there are two cases:// 1. boot by SpringBoot loader// 2. boot in IDE// in two case, just need to scan WebappClassLoader and// WebappClassLoader.getParent()stopLoader = classLoader.getParent().getParent();}// JARs are treated as application provided until the common class// loader is reached.boolean isWebapp = true;while (classLoader != null && classLoader != stopLoader) {if (classLoader instanceof URLClassLoader) {URL[] urls = ((URLClassLoader) classLoader).getURLs();for (URL url : urls) {if (processedURLs.contains(url))continue;// Skip this URL it has already been processedClassPathEntry cpe = new ClassPathEntry(url);// JARs are scanned unless the filter says not to.// Directories are scanned for pluggability scans or if scanAllDirectories is enabled unless the filter says not to.if ((cpe.isJar() || scanType == JarScanType.PLUGGABILITY || isScanAllDirectories()) && getJarScanFilter().check(scanType, cpe.getName())) {if (log.isDebugEnabled())log.debug(sm.getString("jarScan.classloaderJarScan", url));try {process(scanType, callback, url, null, isWebapp);} catch (IOException ioe) {log.warn(sm.getString("jarScan.classloaderFail", url), ioe);}} else {// JAR / directory has been skippedif (log.isTraceEnabled())log.trace(sm.getString("jarScan.classloaderJarNoScan", url));}}}classLoader = classLoader.getParent();}}}private boolean nestedJar(String url) {int idx = url.indexOf(".jar!");int idx2 = url.lastIndexOf(".jar!");return idx != idx2;}/** Scan a URL for JARs with the optional extensions to look at all files and all directories.*/private void process(JarScanType scanType, JarScannerCallback callback, URL url, String webappPath, boolean isWebapp) throws IOException {if (log.isTraceEnabled())log.trace(sm.getString("jarScan.jarUrlStart", url));URLConnection conn = url.openConnection();String urlStr = url.toString();if (conn instanceof JarURLConnection) {System.out.println("-----scan UrlJar: " + urlStr);if (nestedJar(urlStr) && !(callback instanceof FragmentJarScannerCallback)) {//JarFileUrlNestedJar.scanTest(new UrlJar(conn.getURL()), webappPath, isWebapp);//callback.scan(new JarFileUrlNestedJar(conn.getURL()), webappPath, isWebapp);} elsecallback.scan(new UrlJar(conn.getURL()), webappPath, isWebapp);//			callback.scan((JarURLConnection) conn, webappPath, isWebapp);} else {System.out.println("-----scan: " + urlStr);if (urlStr.startsWith("file:") || urlStr.startsWith("http:") || urlStr.startsWith("https:")) {if (urlStr.endsWith(Constants.JAR_EXT)) {
//					URL jarURL = new URL("jar:" + urlStr + "!/");
//					callback.scan((JarURLConnection) jarURL.openConnection(), webappPath, isWebapp);
//					System.out.println("-----" + jarURL);
//					callback.scan(new UrlJar(jarURL), webappPath, isWebapp);callback.scan(new JarFileUrlJar(url, false), webappPath, isWebapp);} else {File f;try {f = new File(url.toURI());if (f.isFile() && isScanAllFiles()) {// 把这个文件当作 JAR 包 Treat this file as a JARURL jarURL = new URL("jar:" + urlStr + "!/");
//							callback.scan((JarURLConnection) jarURL.openConnection(), webappPath, isWebapp);callback.scan(new UrlJar(jarURL), webappPath, isWebapp);} else if (f.isDirectory()) {if (scanType == JarScanType.PLUGGABILITY)callback.scan(f, webappPath, isWebapp);else {File metaInf = new File(f.getAbsoluteFile() + File.separator + "META-INF");if (metaInf.isDirectory())callback.scan(f, webappPath, isWebapp);}}} catch (Throwable t) {ExceptionUtils.handleThrowable(t);// Wrap the exception and re-throwIOException ioe = new IOException();ioe.initCause(t);throw ioe;}}}}}
}

ClassPathEntry

import java.net.URL;import org.apache.tomcat.util.scan.Constants;public class ClassPathEntry {private final boolean jar;private final String name;public ClassPathEntry(URL url) {String path = url.getPath();int end = path.indexOf(Constants.JAR_EXT);if (end != -1) {jar = true;int start = path.lastIndexOf('/', end);name = path.substring(start + 1, end + 4);} else {jar = false;if (path.endsWith("/"))path = path.substring(0, path.length() - 1);int start = path.lastIndexOf('/');name = path.substring(start + 1);}}public boolean isJar() {return jar;}public String getName() {return name;}
}

JarFileUrlNestedJar

import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;import org.apache.tomcat.Jar;
import org.apache.tomcat.util.scan.AbstractInputStreamJar;
import org.apache.tomcat.util.scan.JarFactory;
import org.apache.tomcat.util.scan.NonClosingJarInputStream;/*** 这是一个实现了 {@link org.apache.tomcat.Jar} 接口的类,针对基于文件的 JAR URL 进行了优化,* 这些 URL 引用了 WAR 内部嵌套的 JAR 文件(例如形如 jar:file: ... .war!/ ... .jar 的 URL)* Implementation of {@link org.apache.tomcat.Jar} that is optimised for file* based JAR URLs that refer to a JAR file nested inside a WAR (e.g. URLs of the form jar:file: ... .war!/ ... .jar).*/
public class JarFileUrlNestedJar extends AbstractInputStreamJar {private final JarFile warFile;private final JarEntry jarEntry;public JarFileUrlNestedJar(URL url) throws IOException {super(url);JarURLConnection jarConn = (JarURLConnection) url.openConnection();jarConn.setUseCaches(false);warFile = jarConn.getJarFile();String urlAsString = url.toString();int pathStart = urlAsString.indexOf("!/") + 2;String jarPath = urlAsString.substring(pathStart);System.out.println("==== " + jarPath);jarEntry = warFile.getJarEntry(jarPath);Enumeration<JarEntry> ens = warFile.entries();while (ens.hasMoreElements()) {JarEntry e = ens.nextElement();System.out.println(e.getName());}}@Overridepublic void close() {closeStream();if (warFile != null) {try {warFile.close();} catch (IOException ignored) {}}}@Overrideprotected NonClosingJarInputStream createJarInputStream() throws IOException {return new NonClosingJarInputStream(warFile.getInputStream(jarEntry));}private static final String TLD_EXT = ".tld";public static void scanTest(Jar jar, String webappPath, boolean isWebapp) throws IOException {URL jarFileUrl = jar.getJarFileURL();System.out.println("xxxx------" + jarFileUrl.toString());jar.nextEntry();for (String entryName = jar.getEntryName(); entryName != null; jar.nextEntry(), entryName = jar.getEntryName()) {if (!(entryName.startsWith("META-INF/") && entryName.endsWith(TLD_EXT)))continue;URL entryUrl = JarFactory.getJarEntryURL(jarFileUrl, entryName);System.out.println(entryName + ": " + entryUrl);entryUrl.openStream();}}
}

使用方式

context.setJarScanner(new EmbeddedStandardJarScanner());

关闭 Tomcat

可以通过 Socket 关闭 tomcat: telnet 127.0.0.1 8005,输入 SHUTDOWN 字符串。

import java.io.*;
import java.net.Socket;/*** 可以通过 Socket 关闭 tomcat: telnet 127.0.0.1 8005,输入 SHUTDOWN 字符串*/
public class TomcatUtil {public static void shutdown() {shutdown("localhost", 8005);}public static void shutdown(String serverHost, Integer serverPort) {send("SHUTDOWN", serverHost, serverPort);}/*** 小型 Socket 客户端*/public static String send(String msg, String host, int port) {try (Socket socket = new Socket(host, port);BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream())) {out.write(msg.getBytes());out.flush();socket.shutdownOutput();String ackMsg = socketRead(socket);socket.shutdownInput();System.out.println("[" + System.currentTimeMillis() + "] Reply from server " + host + ":" + port + ": ");System.out.println("\t" + ackMsg);return ackMsg;} catch (IOException e) {e.printStackTrace();return null;}}static String socketRead(Socket socket) throws IOException {socket.setSoTimeout(5000);int byteCount = 0;char[] buffer = new char[4096];int bytesRead;try (InputStreamReader in = new InputStreamReader(socket.getInputStream()); StringWriter out = new StringWriter()) {while ((bytesRead = in.read(buffer)) != -1) {out.write(buffer, 0, bytesRead);byteCount += bytesRead;}
//            out.flush();return out.toString();}}public static File createTempDir(String folderName) {File tmpdir = new File(System.getProperty("java.io.tmpdir"));tmpdir = new File(tmpdir, folderName);if (!tmpdir.exists())tmpdir.mkdir();return tmpdir;}public static File createTempDir(String prefix, int port) {File tempDir;try {tempDir = File.createTempFile(prefix + ".", "." + port);} catch (IOException e) {throw new RuntimeException(e);}tempDir.delete();tempDir.mkdir();tempDir.deleteOnExit();return tempDir;}public static void deleteAllFilesOfDir(File path) {if (!path.exists())return;try {if (path.isFile()) {java.nio.file.Files.delete(path.toPath());return;}File[] files = path.listFiles();assert files != null;for (File file : files) deleteAllFilesOfDir(file);java.nio.file.Files.delete(path.toPath());} catch (IOException e) {throw new RuntimeException(e);}}
}

整合 SpringMVC

本文只是讨论纯 Tomcat 的启动,关于整合 Spring 我在另外一篇文章中介绍《轻量级仿 SpringBoot=嵌入式 Tomcat+SpringMVC》。

参考

  • 仿SpringBoot的启动方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/173044.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

故障诊断入门书籍资料免费领取

前言 本期分享免费提供9本故障诊断领域相关的书籍资料&#xff0c;可自行下载 一、主要内容 二、书籍获取

VR结合|山海鲸虚拟展厅解决方案

方案背景 虚拟现实技术是另一项革命性的创新&#xff0c;它可以将用户带入一个完全虚拟的环境中。借助VR头盔和控制器&#xff0c;用户可以亲临虚拟现实中&#xff0c;与数字世界互动&#xff0c;仿佛置身于其中。 山海鲸根据用户实际需求变化将数字孪生与虚拟现实技术相结合…

Web攻防06_sqlmap的使用

文章目录 参考链接&#xff1a; SQLMAP简介支持五种不同的注入模式 数据猜解-库表列数据权限操作引出权限&#xff1a;引出文件&#xff1a;引出命令&#xff08;执行命令&#xff09;&#xff1a; 提交方法-POST&HEAD&JSONPost注入cookie注入注入请求头中&#xff08;…

【1++的Linux】之进程间通信

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的Linux】 文章目录 一&#xff0c;进程间通信的目的二&#xff0c;管道 一&#xff0c;进程间通信的目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程资源共享&#xff1a;…

深度学习:张量 介绍

张量[1]是向量和矩阵到 n 维的推广。了解它们如何相互作用是机器学习的基础。 简介 虽然张量看起来是复杂的对象&#xff0c;但它们可以理解为向量和矩阵的集合。理解向量和矩阵对于理解张量至关重要。 向量是元素的一维列表&#xff1a; 矩阵是向量的二维列表&#xff1a; 下标…

unity button移动位置some values driven by canvas

1 可以在button父节点把限制取消勾选 2 在不动整个布局的情况下&#xff0c;只修改局部变量&#xff1a;忽略布局即可

【C++】list的介绍及使用 | 模拟实现list(万字详解)

目录 一、list的介绍及使用 什么是list&#xff1f; list的基本操作 增删查改 获取list元素 不常见操作的使用说明 ​编辑 接合splice ​编辑 移除remove 去重unique 二、模拟实现list 大框架 构造函数 尾插push_back 迭代器__list_iterator list的迭代器要如何…

2023年MathorCup高校数学建模挑战赛大数据挑战赛赛题浅析

比赛时长为期7天的妈杯大数据挑战赛如期开赛&#xff0c;为了帮助大家更好的选题&#xff0c;首先给大家带来赛题浅析&#xff0c;为了方便大家更好的选题。 赛道 A&#xff1a;基于计算机视觉的坑洼道路检测和识别 A题&#xff0c;图像处理类题目。这种题目的难度数模独一档…

TextureView和SurfaceView

1、Surface Surface对应了一块屏幕的缓冲区&#xff0c;每一个window对应一个Surface&#xff0c;任何View都是画在Surface上的&#xff0c;传统的View共享一块屏幕缓冲区&#xff0c;所有的绘制都必须在UI线程上进行。 2、SurfaceView 顾名思义就是Surface的View&#xff0c;…

Python爬虫网易云音乐,Tkinter制作音乐播放器

目录 一、效果展示 二、环境 三、实现过程 四、源码 一、效果展示 页面的美化以及功能还有待升级~ 先来说一下已有功能吧&#xff1a; 可以在搜索框中通过歌曲或歌手名称进行搜索&#xff0c;效果和在网易云官网搜索一样。 点击开始下载&#xff0c;就会将搜索结果的第一…

FoLR:Focus on Local Regions for Query-based Object Detection论文学习笔记

论文地址&#xff1a;https://arxiv.org/abs/2310.06470 自从DETR问询式检测器首次亮相以来&#xff0c;基于查询的方法在目标检测中引起了广泛关注。然而&#xff0c;这些方法面临着收敛速度慢和性能亚优等挑战。值得注意的是&#xff0c;在目标检测中&#xff0c;自注意力机制…

H5游戏源码分享-手机捉鬼游戏

H5游戏源码分享-手机捉鬼游戏 一款考验手速的游戏 <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"><title>手机捉鬼 微信HTML5在线朋友圈游戏</title><meta name&…

SSM度假村管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 度假村管理系统是一套完善的信息系统&#xff0c;结合SSM框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要 采用B/S模式开发…

计算机毕设 opencv 图像识别 指纹识别 - python

文章目录 0 前言1 课题背景2 效果展示3 具体实现3.1 图像对比过滤3.2 图像二值化3.3 图像侵蚀细化3.4 图像增强3.5 特征点检测 4 OpenCV5 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往…

VulnHub DC-1

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏…

自动驾驶,从“宠儿”走进“淘汰赛”

从“一步到位”到场景、技术降维。从拼落地路径&#xff0c;到拼雷达、算力&#xff0c;再到如今的性价比之争&#xff0c;自动驾驶似乎变得愈发“接地气”。 作者|斗斗 编辑|皮爷 出品|产业家 比起去年&#xff0c;黄文欢和张放今年显得更加忙碌。 “自动驾驶赛道&…

“爱知道”,你知道吗?

拥抱时代浪潮&#xff0c;加速科技变革。数字经济时代&#xff0c;杭州重点贯彻市委市政府数字经济创新提质“一号发展工程”&#xff0c;加快发展数字经济&#xff0c;推动全市数字经济往高攀升、向新进军、以融提效。基于政府对数字经济新活力的赋能、优化数字社会环节、构建…

若依微服务上传图片文件代理配置

在使用若依微服务文件上传时候,文件上传成功会上传到D:/ruoyi/uploadPath目录下。默认使用9300端口进行访问图片文件,现在我想把它代理到80端口应该怎么做呢? 配置前:http://localhost:9300/statics/2023/09/24/test.jpg 配置后:http://localhost/statics/2023/09/24/test…

msigdbr hallmarks gsea broad研究所

使用msigdbr r包 #BiocManager::install("msigdb") #https://www.gsea-msigdb.org/gsea/msigdb #https://cran.r-project.org/web/packages/msigdbr/vignettes/msigdbr-intro.html #https://bioconductor.org/packages/release/data/experiment/vignettes/msigdb/ins…

LVS-DR模式+keepalived+nginx+tomcat实现动静分离、负载均衡、高可用实验

实验条件&#xff1a; test2——20.0.0.20——主服务器——ipvsadm、keepalived服务 test3——20.0.0.30——备服务器——ipvsadm、keepalived服务 nginx5——20.0.0.51——后端真实服务器1&#xff08;tomcat的代理服务器&#xff09;——nginx服务 nginx6——20.0.0.61—…