嵌入式 Tomcat 调校

news2024/11/15 8:59:30

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 work
        WebResourceRoot resources = new StandardRoot(context);// 创建 WebRoot
        String 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); // 设置执行器
        } else
            connector = 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 work
            ThreadPoolExecutor 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 port
            tomcat.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);
                // 去掉JSP
                context.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 方式连接 JMXConnectorServer
            JMXConnectorServer 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 mappings
        context.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.
     */
    @Override
    protected 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();

    @Override
    public JarScanFilter getJarScanFilter() {
        return jarScanFilter;
    }

    @Override
    public 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
     */
    @Override
    public 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/lib
        Set<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 JAR
                    if (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/classes
        if (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 classpath
        if (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 processed

                        ClassPathEntry 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 skipped
                            if (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);
            } else
                callback.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 JAR
                            URL 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-throw
                        IOException 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());
        }
    }

    @Override
    public void close() {
        closeStream();

        if (warFile != null) {
            try {
                warFile.close();
            } catch (IOException ignored) {
            }
        }
    }

    @Override
    protected 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.coloradmin.cn/o/1133604.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

T113-S3-buildroot文件系统tar解压缩gz文件

目录 前言 一、现象描述 二、解决方案 三、tar解压缩.gz文件 总结 前言 本文主要介绍全志T113-S3平台官方SDK&#xff0c;buildroot文件系统tar不支持.gz文件解压缩的问题以及如何配置buildroot文件系统解决该问题的方法介绍。 一、现象描述 在buildroot文件系统中&#xff…

图像压缩(4)《数字图像处理》第八章 8.3节 数字图像水印

图像压缩&#xff08;3&#xff09;《数字图像处理》第八章8.3节数字图像水印 一. 前言二.章节引言三.简单综述三.本章小结四.参考文献四. 小结 一. 前言 始于那本深蓝色的大块头&#xff0c;冈萨勒斯的《数字图像处理》&#xff0c;从此走上了图像信号处理的不归路&#xff0…

Java开发-WebSocket

WebSocket是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工通信-浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c;并实现 双向数据传输。 使用 导入maven坐标 <dependency><groupId>org.springframework.bo…

【Linux】安装配置虚拟机及虚拟机操作系统的安装

目录 一、操作系统 1. 介绍 2. 功能 3. 有哪些 4. 个人版本和服务器版本的区别 二、VMWare虚拟机 1. 安装 2. 配置 三、安装配置Windows Server 1. 配置 2. 安装 四、虚拟机的环境配置及连接 1. 主机连接虚拟机 2. 虚拟机环境配置及共享 3. 环境配置 一、操作系…

音乐网站播放器前后端【源码好优多】

介绍 音乐网站播放器音乐网站管理系统音乐网站后台 软件架构 使用vuespringbootmybatis-plusmysql技术的前后端分离项目 前端 安装教程 npm install&#xff08;安装运行环境&#xff09; npm run dev&#xff08;运行&#xff09; 需要有node.js环境&#xff0c;不建议使…

设计模式——UML类图

目录 UML类图介绍UML图UML图的分类 关系依赖关系泛化关系实现关系关联关系聚合关系 UML类图 介绍 uml(统一建模语言)&#xff0c;是一种用于软件系统分析和设计的语言工具&#xff0c;他用于帮助软件开发人员进行思考和记录思路的结果UML本身是一套符号的规定&#xff0c;就像…

linuxnfs服务安装与配置实践

目录 一.NFS服务 二.NFS和RPC的概念 三.什么是RPC 四.工具 五.安装配置NFS环境 1.查看一下是否有nfs-utils和rpcbind软件包的安装rpm包 2.安装nfs-utils 、rpcbind 3.环境配置 1.nfs配置文件需要遵守如下规则&#xff1a; 2.nfs语法参数解释&#xff1a; 3.nfs客户端…

Python:实现日历到excel文档

背景 日历是一种常见的工具,用于记录事件和显示日期。在编程中,可以使用Python编码来制作日历。 Python提供了一些内置的模块和函数,使得制作日历变得更加简单。 在本文,我们将探讨如何使用Python制作日历,并将日历输出到excel文档中。 效果展示 实现 在代码中会用到cale…

springboot actuator 常用接口

概述 微服务作为一项在云中部署应用和服务的新技术是当下比较热门话题&#xff0c;而微服务的特点决定了功能模块的部署是分布式的&#xff0c;运行在不同的机器上相互通过服务调用进行交互&#xff0c;业务流会经过多个微服务的处理和传递&#xff0c;在这种框架下&#xff0…

轻松驾驭!Prometheus 如何监控指标,快速定位故障

Prometheus 监控业务指标 在 Kubernetes 已经成了事实上的容器编排标准之下&#xff0c;微服务的部署变得非常容易。但随着微服务规模的扩大&#xff0c;服务治理带来的挑战也会越来越大。在这样的背景下出现了服务可观测性&#xff08;observability&#xff09;的概念。 在…

vue 记账清单

通过发送请求来从后台获取更改数据&#xff0c;需要用到钩子函数。 使用ECharts来渲染饼图。 在渲染函数中setOption动态更新饼图&#xff08;map&#xff09; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" />&l…

linux-防火墙

目录 一、防火墙概念 1.软件防火墙 2.iptables默认规则 3.iptables的五链 4.iptables动作 5.四表五链 6.iptables实例 一、防火墙概念 linux下防火墙一般分为软件防火墙、硬件防火墙 硬件防火墙&#xff1a;在硬件的级别实现防火墙过滤功能&#xff0c;性能高&#xf…

360智慧生活旗舰产品率先接入“360智脑”能力实现升级

10月25日&#xff0c;360智慧生活秋季新品及视觉云方案发布会在深圳召开。360智能硬件产品&#xff0c;诸如 360可视门铃、360智能摄像机、360行车记录仪、360儿童手表和家庭防火墙等&#xff0c;都在各自的行业有着举足轻重得地位&#xff0c;而这次发布的系列新品&#xff0c…

使用Golang策略和最佳实践高效处理一百万个请求

有效处理一百万个请求的策略与最佳实践 在不断发展的Web应用程序领域&#xff0c;处理大规模请求的能力是成功的关键因素。作为一名全栈开发人员&#xff0c;您可能熟悉Golang的效率和性能。在本文中&#xff0c;我们将深入探讨如何利用Golang来优雅地处理处理一百万个请求的挑…

【ICLR23论文】Can CNNs Be More Robust Than Transformers?

文章目录 0 Abstract1 Introduction2 Related Works3 Settings3.1 CNN Block Instantiations3.2 Computational Cost3.3 Robustness Benchmarks3.4 Training Recipe3.5 Baseline Results 4 Component Diagnosis4.1 Patchief Stem4.2 Large Kernel Size4.3 Reducing Activation …

安装 tensorflow==1.15.2 遇见的问题

一、直接安装 命令&#xff1a;pip install tensorflow1.15.2 二、换 阿里云 镜像源 命令&#xff1a;pip install -i http://mirrors.aliyun.com/pypi/simple tensorflow1.15.2 三、换 豆瓣 镜像源 命令&#xff1a;pip install http://pypi.douban.com/simple tensorflow1…

Android Glide判断图像资源是否缓存onlyRetrieveFromCache,使用缓存数据,Kotlin

Android Glide判断图像资源是否缓存onlyRetrieveFromCache&#xff0c;使用缓存数据&#xff0c;Kotlin import android.graphics.Bitmap import android.os.Bundle import android.util.Log import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity…

“第五十天” 机组--数据的表示

数据的表示和运算&#xff1a; 进位计数制&#xff1a; 对于我们现在使用的如十进制计数法&#xff0c;二进制计数法等&#xff0c;符号反映权重&#xff0c;比如十进制的0~9&#xff0c;9这个符号的权重和0是不一样的&#xff0c;而且现在的计数法符号所在位置也反映权重&am…

去雨去雪去雾算法运行问题汇总记录

在进行去雨去雪去雾算法过程中&#xff0c;遇到了一个问题&#xff0c;这在先前的电脑运行是都没有出现过&#xff0c;但在博主新买的电脑上却出现了&#xff0c;讲道理是有点小抑郁的。 RuntimeWarning: invalid value encountered in scalar divideret ret.dtype.type(ret …