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

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

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

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

  • Connector: tomcat监听相关的配置对象。
    import org.apache.catalina.connector.Connector;
    
    Connector connector = new Connector();
    conn.setProperty("address", hostname); // 监听地址
    conn.setPort(port); // 监听端口
    
    // 关联到tomcat实例,启动监听
    tomcat.setConnector(connector);
    tomcat.start();
    tomcat.getServer().await();

     

  • 工作目录,绝对路径,该目录下必须有一个"webapps"目录,同时Tomcat还会生成一个work目录,因此建议使用当前目录"."
  • tomcat.setBaseDir(new File(".").getAbsolutePath());
    
    // 指定任意工作目录,自动创建webapps目录
        private void configDir(String baseDir) {
            File root = new File(baseDir);
            if (!root.isDirectory() && !root.mkdirs()) {
                throw new RuntimeException("请提供Tomcat工作目录");
            }
            String path4root = root.getAbsolutePath();
            tomcat.setBaseDir(path4root);
            File webapps = new File(path4root + "/webapps");
            if (!webapps.isDirectory() && !webapps.mkdirs()) {
                throw new RuntimeException("无法创建webapps目录");
            }
        }
    
  • 上下文、上下文目录
    添加上下文时,Tomcat会在工作目录下生成目录work/Tomcat/{Hostname}/{contextPath}
    相关代码:
  • tomcat.setHostname("my_tomcat");
    
    tomcat.addContext(contextPath, docBase) // docBase:您可以为此目录或WAR文件指定绝对路径名,或者相对于所属主机的appBase目录的相对路径名,除非在server.xml中定义了Context元素,或者docBase不在主机的appBase下,否则不得设置此字段的值
    
  • 上下文映射
  • context = tomcat.addContext("", new File(baseDir).getAbsolutePath());
    Tomcat.addServlet(context, "default", new HelloServlet()); // HelloServlet : HttpServlet
    context.addServletMappingDecoded("/", "default");
    

    现在, 让我们把 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<Class<?>>() {
    							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)
    }

     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值