码上焚香

Yahocen

内嵌 Tomcat 简易教程

17
2024-05-10

Spring Boot等微服务框架之所以能够独立运行,是因为它们默认内嵌了web服务器。下面通过一个运行Groovy脚本的小项目类来说明内嵌Tomcat的使用方式。

引入相关依赖

    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.50</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-logging-juli</artifactId>
            <version>9.0.0.M6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-annotations-api</artifactId>
            <version>9.0.50</version>
        </dependency>
    </dependencies>

创建 Tomcat 服务

我通过继承的方式对tomcat进行了基本封装,请自行参考。ServerConfig类只是普通的Java类,不做解释,可以参考全参数的构造方法。 Groovlet最终是继承自Servlet接口,对于Tomcat来说,只要实现Servlet就可以传入。这里我创建的是一个Web服务,所以实现了HttpServlet抽象类,用于获取HttpServletRequest和HttpServletResponse。

import lombok.Getter;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;

import java.io.File;
import java.util.UUID;

/**
 * 用于快速创建执行GroovyServlet的Tomcat容器
 * @Author Yahocen
 * @Date 2021/7/30 15:27
 * @Version 1.0
 * @Describe
 */
public class GroovyServer extends Tomcat {

    @Getter
    private String servletName;

    @Getter
    private Boolean listeningState;

    @Getter
    private Groovlet servlet;

    public GroovyServer(){
        this(ServerConfig.DEFAULT);
    }

    /**
     * @param config
     */
    public GroovyServer(ServerConfig config) {
        this(config.getBaseDir(),
                config.getHostName(),
                config.getPort(),
                config.getContextPath(),
                config.getAppBase(),
                config.getDocBase(),
                config.getPattern(),
                config.getWelcomeFile());
    }

    /**
     * @param basedir 工作目录
     * @param hostName 主机名 本机填写:localhost
     * @param port 端口号
     * @param contextPath 请求根路径 比如 /api 就要使用  http://ip:port/api 来访问
     * @param appBase tomcat服务项目目录,推荐:/webapps
     * @param docBase 上下文的基础目录,推荐标准目录 /ROOT
     * @param pattern 要映射的URL模式 web.xml 中的 url-pattern 例如:xxx.do,推荐默认 /
     * @param welcomeFile 默认首页,映射 /
     */
    public GroovyServer(String basedir, String hostName, int port, String contextPath, String appBase,
                        String docBase, String pattern, String welcomeFile){
        this.listeningState = false;
        //初始化项目目录
        File dir = new File(basedir + appBase + docBase);
        if(!dir.exists()){
            dir.mkdirs();
        }
        // 设置工作目录
        this.setBaseDir(basedir);
        // 主机名, 将生成目录: {工作目录}/work/Tomcat/{主机名}/ROOT
        this.setHostname(hostName);
        this.setPort(port);
        // contextPath要使用的上下文映射,""表示根上下文
        // docBase上下文的基础目录,用于静态文件。相对于服务器主目录必须存在 ({主目录}/webapps/{docBase})
        /*{webapps}/~*/
        Host host = getHost();
        host.setAppBase(appBase);
        Context ctx = this.addContext(contextPath, docBase);
        this.servletName = UUID.randomUUID().toString();
        //注意此处的
        this.servlet = new Groovlet(welcomeFile);
        Tomcat.addServlet(ctx, servletName, this.servlet);
        ctx.addServletMappingDecoded(pattern, servletName);
    }

    /**
     * 启动服务监听
     * @param await  是否等待直到收到正确的关机命令,然后返回。
     * @throws LifecycleException
     */
    public void start(boolean await) throws LifecycleException {
        if(this.listeningState){
            return;
        }
        super.start();
        this.listeningState = true;
        //Tomcat 9.0 必须调用 Tomcat#getConnector() 方法之后才会监听端口
        this.getConnector();
        if(!await){
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                try {
                    //先停止再销毁
                    this.stop();
                    this.destroy();
                } catch (LifecycleException e) {
                    e.printStackTrace();
                }
            }));
        }else{
            this.await();
        }
    }

    /**
     * 停止服务监听
     * @throws LifecycleException
     */
    @Override
    public void stop() throws LifecycleException {
        if(this.listeningState){
            this.listeningState = false;
            super.stop();
        }
    }

    /**
     * 销毁服务
     * @throws LifecycleException
     */
    @Override
    public void destroy() throws LifecycleException {
        if(this.listeningState){
            this.stop();
        }
        super.destroy();
    }

    /**
     * 等待直到收到正确的关机命令,然后返回。
     */
    public void await(){
        this.getServer().await();
    }

}