• 嵌入式 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(); // 保持主线程不退出,让其阻塞,不让当前线程结束,等待处理请求
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    配置化你的 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.
         *
         * 

    * By default, if this method is not called, we use: *

      *
    • system properties - catalina.base, catalina.home
    • *
    • $PWD/tomcat.$PORT
    • *
    * (/tmp doesn't seem a good choice for security). * *

    * 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 : ""; } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

    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();
    
    • 1
    • 2
    • 3

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

    • 禁止 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
         * 参见 ...
         */
        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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355

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

    在这里插入图片描述

    增强特性

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

    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("!/");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    使用方式

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

    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;
                        }
                    }
                }
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    使用方式

    context.setJarScanner(new EmbeddedStandardJarScanner());
    
    • 1

    关闭 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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    整合 SpringMVC

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

    参考

  • 相关阅读:
    源码深度剖析Spring Bean标签的解析及注册
    Tmux 使用教程
    洛谷刷题C语言:数字统计、你的飞碟在这儿、哥德巴赫猜想、数字翻转、低洼地
    【没用的小知识又增加了--CCS】
    DDD领域驱动设计-为什么要用DDD
    汽车零部件行业智能采购协同系统平台开发,提升企业管理效益
    JAVA计算机毕业设计电影评分网站Mybatis+系统+数据库+调试部署
    朴素贝叶斯算法
    全国2021年夜间灯光数据,有图有数据
    用Python写一个自动下载视频、弹幕、评论的软件(2022最新)
  • 原文地址:https://blog.csdn.net/zhangxin09/article/details/134044518