嵌入式 Tomcat 调校

这篇具有很好参考价值的文章主要介绍了嵌入式 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 调校,Java,tomcat,java
另外补充一下两个配置的地方:

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

嵌入式 Tomcat 调校,Java,tomcat,java

完整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");
    }
}

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

嵌入式 Tomcat 调校,Java,tomcat,java

增强特性

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

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》。文章来源地址https://www.toymoban.com/news/detail-718193.html

参考

  • 仿SpringBoot的启动方式

到了这里,关于嵌入式 Tomcat 调校的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 嵌入式要卷成下一个Java了吗?

    不会! 说不会也是有自己的原因的 前几天写了一篇 Linux 和单片机的文章 不做Linux就没前途吗? 单片机容易,门槛低,无非不就是单片机技术知识点比较少,特别是面向过程式的编程也更容易掌握。嵌入式 Linux 是多任务式的,多任务之后就引申了更多的东西出来,技术更复

    2024年02月13日
    浏览(91)
  • 机械女生,双非本985硕,目前学了C 基础知识,转嵌入式还是java更好?

    作为单片机项目开发的卖课佬,个人建议,先转嵌入式单片机开发方向,哈哈。 java我也学过,还学过oracle、mysql数据库,只是当时没做笔记,找不好充分的装逼证据了。 从实习通过业余时间,学到快正式毕业,真的有种呕心沥血的感觉。 当时还是学生,脸皮比较薄,又不是

    2024年03月16日
    浏览(52)
  • Java在物联网领域的应用非常广泛,涵盖了设备连接、数据处理、应用程序开发、安全性、嵌入式系统开发、消息队列和流处理、机器学习和人工智能以及跨平台和多语言集成等方面

    Java作为一种通用编程语言,在物联网(IoT)领域的应用也非常广泛。以下是一些Java在物联网中的典型应用: 开发物联网应用程序 :Java是一种高级编程语言,具有丰富的库和工具,使得开发物联网应用程序变得容易。Java可以用于开发各种物联网应用程序,如智能家居、智能

    2024年02月03日
    浏览(79)
  • 嵌入式系统>嵌入式硬件知识

    AI芯片的特点包括 :新型计算范式AI芯片的关键特征: 1、新型的计算范式 AI 计算既不脱离传统计算,也具有新的计算特质,如处理的内容往往是非结构化数据(视频、图片等)。处理的过程通常需要很大的计算量,基本的计算主要是线性代数运算,而控制流程则相对简单。

    2024年02月08日
    浏览(55)
  • 嵌入式操作系统(嵌入式学习)

    嵌入式操作系统是一种专门设计和优化用于嵌入式系统的操作系统。它是在资源受限的嵌入式设备上运行的操作系统,如微控制器、嵌入式处理器和系统芯片等。 嵌入式操作系统的主要目标是提供对硬件资源的有效管理和对应用程序的调度,以实现系统的可靠性、实时性和效

    2024年02月10日
    浏览(60)
  • 关于嵌入式开发的一些信息汇总:嵌入式C开发人员、嵌入式系统Linux

    这篇文章是关于嵌入式开发的一些基本信息,供想入行的人参考。有一些作者本人的想法,以及来自外网的大拿的文章翻译而来,原文链接在此Learning Linux for embedded systems,再次感谢,支持原创。 普通C开发人员和嵌入式C开发人员之间的 基本区别在于 ,因为嵌入式C程序被设

    2024年02月03日
    浏览(71)
  • 适合嵌入式开发的GUI(嵌入式学习)

    嵌入式开发中的GUI(图形用户界面)是指在嵌入式系统中实现图形化的用户界面,以便用户可以通过触摸屏、按钮、鼠标或其他输入设备与系统进行交互。 以下是一些常见的嵌入式GUI开发方法: 轻量级GUI库:一些轻量级的GUI库适用于嵌入式系统,例如uGFX、LittlevGL、Nanogui等。

    2024年02月09日
    浏览(69)
  • 嵌入式Linux:如何进行嵌入式Linux开发?

    目录 1、裸机开发 2、SDK开发 3、驱动开发 3.1、字符设备驱动 3.2、块设备驱动 3.3、网络设备驱动 4、应用开发 嵌入式Linux开发主要有四种方式:裸机开发、SDK开发、驱动开发和应用开发。 裸机开发通常指在没有操作系统支持的环境中直接在硬件上运行程序的开发。这种开发方

    2024年01月25日
    浏览(80)
  • 【嵌入式学习笔记】嵌入式入门2——中断(外部中断)

    打断CPU执行正常的程序,转而处理紧急程序,然后返回原暂停的程序继续运行,就叫中断 作用1: 实时控制在确定时间内对相应事件作出响应 ——定时器中断 作用2: 故障处理检测到故障,需要第一时间处理 ——外部中断 作用3: 数据传输不确定数据何时会来 ——串口中断

    2024年02月08日
    浏览(74)
  • 【计算机三级嵌入式】考试自学笔记(三)——嵌入式系统硬件组成、嵌入式处理芯片以及存储器介绍

    常考知识点: 嵌入式最小硬件组成 嵌入式处理芯片 嵌入式系统的存储器 I/O接口及常用I/O设备 ARM内核典型嵌入式处理芯片 嵌入式系统外部通信接口 嵌入式最小硬件系统的组成如下: 嵌入式硬件系统≠嵌入式最小硬件系统 电源电路 :为整个嵌入式系统提供能量 时钟电路 :

    2023年04月12日
    浏览(88)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包