嵌入式 Tomcat (Embedded Tomcat)

嵌入式 Tomcat 作为嵌入 Java 应用程序的库, 你可以在 mvnrepository 下载 发行版Jar 以及源码

https://mvnrepository.com/search?q=embed+tomcat

作为最基本的依赖, 你需要以下几个库

  • Tomcat Embed Core
  • Tomcat Embed Logging JULI
  • Tomcat Annotations API

现在, 让我们把 tomcat 跑起来

package develon.test;

import java.io.File;
import java.io.IOException;
import java.io.Writer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;

public final class Main {
    File tmpDir = new File("F:\\Game\\tomcat");
    Tomcat tomcat = new Tomcat();
    
    public static void main(String[] args) throws Throwable {
        new Main().init();
    }
    
    private void init() throws Throwable {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                try {
                    tomcat.destroy();
                } catch (LifecycleException e) {
                    e.printStackTrace();
                }
            })
        );
        test();
    }

    private void test() throws Throwable {
        tomcat.setBaseDir(tmpDir.getAbsolutePath()); // 设置工作目录
        tomcat.setHostname("localhost"); // 主机名, 将生成目录: {工作目录}/work/Tomcat/{主机名}/ROOT
        System.out.println("工作目录: " + tomcat.getServer().getCatalinaBase().getAbsolutePath());

        tomcat.setPort(80);
        Connector conn = tomcat.getConnector(); // Tomcat 9.0 必须调用 Tomcat#getConnector() 方法之后才会监听端口
        System.out.println("连接器设置完成: " + conn);
        
        // contextPath要使用的上下文映射,""表示根上下文
        // docBase上下文的基础目录,用于静态文件。相对于服务器主目录必须存在 ({主目录}/webapps/{docBase})
        Context ctx = tomcat.addContext("", /*{webapps}/~*/ "/ROOT");

        Tomcat.addServlet(ctx, "globalServlet", new HttpServlet() {
            private static final long serialVersionUID = 1L;

            @Override
            protected void service(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                response.setCharacterEncoding("UTF-8");
                response.setContentType("text/plain");
                response.setHeader("Server", "Embedded Tomcat");
                try (Writer writer = response.getWriter()) {
                    writer.write("Hello, Embedded Tomcat!");
                    writer.flush();
                }
            }
        });
        ctx.addServletMappingDecoded("/", "globalServlet");

        tomcat.start();
        System.out.println("tomcat 已启动");
        tomcat.getServer().await();
    }

}

tomcat 嵌入正常, 让我们继续, 如何令 tomcat 加载 Spring Framework ?

嵌入式 tomcat 集成 Spring 框架

package develon.tomc;

import java.util.HashSet;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.SpringServletContainerInitializer;

public class Main {
    Tomcat tomcat;
    
    {
        tomcat = new Tomcat();
//      tomcat.setAddDefaultWebXmlToWebapp(false);
//      tomcat.noDefaultWebXmlPath();
    }

    public void run() throws Throwable {
        tomcat.setBaseDir("F:\\Game\\tomcat");
        tomcat.setHostname("localhost");
        tomcat.setPort(80);
//      tomcat.enableNaming();
        
//      tomcat.getHost().setAutoDeploy(false);
//      tomcat.getEngine().setBackgroundProcessorDelay(-1);
        
        Context ctx = tomcat.addContext("", "ROOT");
        
        ctx.addLifecycleListener(new LifecycleListener() {
            public void lifecycleEvent(LifecycleEvent event) {
//              System.out.println(event.getLifecycle().getState().name());
                if (event.getLifecycle().getState() == LifecycleState.STARTING_PREP) {
                    try {
                        new SpringServletContainerInitializer().onStartup(new HashSet>() {
                            private static final long serialVersionUID = 1L;
                            {
                                add(WebAppInitializer.class);
                            }
                        }, ctx.getServletContext());
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        
//      tomcat.init();
        tomcat.getConnector();
        tomcat.start();
        tomcat.getServer().await();
    }
    
    public static void main(String[] args) throws Throwable {
        new Main().run();
    }
}

其中 WebAppInitializer 是继承 AbstractAnnotationConfigDispatcherServletInitializer 的一个配置类
由于 AbstractAnnotationConfigDispatcherServletInitializer 继承了 SpringServletContainerInitializer, 所以可以简写为

        Context ctx = tomcat.addContext("", "ROOT");
        
        ctx.addLifecycleListener(new LifecycleListener() {
            public void lifecycleEvent(LifecycleEvent event) {
                if (event.getLifecycle().getState() == LifecycleState.STARTING_PREP) {
                    try {
                        new WebAppInitializer().onStartup(ctx.getServletContext());
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                }
            }
        });

这种方式好像会报一个错误, 不过可以忽略它, 但是注意这是一个运行时异常, 我们最好捕获 Throwable, 否则程序直接退出了
(经查, 是由于注射 dispacherServlet 两次造成的, 实际上第一次已经注射完成了)

java.lang.IllegalStateException: Failed to register servlet with name 'dispatcher'. Check if there is another servlet registered under the same name.
    at org.springframework.web.servlet.support.AbstractDispatcherServletInitializer.registerDispatcherServlet(AbstractDispatcherServletInitializer.java:90)
    at org.springframework.web.servlet.support.AbstractDispatcherServletInitializer.onStartup(AbstractDispatcherServletInitializer.java:63)
    at develon.tomc.Main$1.lifecycleEvent(Main.java:37)

然后我们还能用闭包进一步简化程序, 并且把烦人的栈痕迹删除

        ctx.addLifecycleListener((LifecycleEvent event) -> {
            if (event.getLifecycle().getState() == LifecycleState.STARTING_PREP) {
                try {
                    new WebAppInitializer().onStartup(ctx.getServletContext());
                } catch (Throwable e) {
//                  e.printStackTrace();
                }
            }
        });

我用 kotlin 简单地封装了一个 EmbeddedTomcat 类

import org.apache.catalina.startup.Tomcat
import org.apache.catalina.Context
import org.apache.catalina.LifecycleState

class EmbeddedTomcat {
    var tomcat: Tomcat = Tomcat()
    var ctx: Context? = null
    
    init {
        
    }
    
    /** 初始化嵌入式 tomcat */
    fun init() {
        tomcat.setBaseDir("""F:\\Game\\tomcat""")
        tomcat.setHostname("localhost")
        tomcat.setPort(80)
        
        ctx = tomcat.addContext("", "ROOT")
    }
    
    /** 开始监听服务 */
    fun run() {
        tomcat.getConnector()
        tomcat.start()
        tomcat.getServer().await()
    }
    
    /** 启动 Spring 框架, 注射 DispatcherServlet */
    fun spring() {
        var tyusya = false
        ctx?.addLifecycleListener {
            if (tyusya == false && it.getLifecycle().getState() == LifecycleState.STARTING_PREP) {
                println("开始注射 -> ${ it.getLifecycle().getState() }")
                val sctx = ctx?.getServletContext()
                try {
                    WebAppInitializer().onStartup(sctx)
                    println("完成")
                    tyusya = true
                } catch(e: Throwable) {
                    println("失败: ${ e.message }")
                }
            }
        }
    }

    fun spring2() { // 调用了 removeLifecycleListener 移除 tomcat 生命周期监听器
        ctx?.addLifecycleListener(object : LifecycleListener {
            override fun lifecycleEvent(it: LifecycleEvent) {
                if (it.getLifecycle().getState() == LifecycleState.STARTING_PREP) {
                    println("开始注射 DispatcherServlet -> ${ it.getLifecycle().getState() }")
                    try {
                        WebAppInitializer().onStartup(ctx?.getServletContext())
                        println("注射完成")
                        ctx?.removeLifecycleListener(this)
                    } catch(e: Throwable) {
                        println("注射失败: ${ e.message }")
                    }
                }
            }
        })
    }
}

fun main() {
    val tomcat = EmbeddedTomcat()
    tomcat.init()
    tomcat.spring()
    tomcat.run()
}
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
import org.springframework.context.annotation.ComponentScan

@ComponentScan(basePackageClasses = [DefController::class])
class WebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
    override fun getRootConfigClasses() = null
    override fun getServletMappings() = arrayOf("/")
    override fun getServletConfigClasses() = arrayOf(WebAppInitializer::class.java)
}

你可能感兴趣的:(嵌入式 Tomcat (Embedded Tomcat))