news 2026/9/3 16:28:28

自己动手写一个tomcat之8多应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自己动手写一个tomcat之8多应用

写在前面

本文看下我们的tomcat如何支持多应用的场景。

1:正文

当多应用时,就不能简单用context容器调用wrapper容器了,因为此时会有多个context容器,需要根据url的上下文信息来确定使用哪个context容器。这相当于是多了一个新的职责,所以来定义一个新的类StandardHost,如下:

packagemonitomcat.server.core;// ...publicclassStandardHostextendsContainerBase{HttpConnectorconnector=null;Map<String,StandardContext>contextMap=newConcurrentHashMap<>();//contextName - servletContextprivateArrayList<ContainerListenerDef>listenerDefs=newArrayList<>();privateArrayList<ContainerListener>listeners=newArrayList<>();publicStandardHost(){super();pipeline.setBasic(newStandardHostValve());log("Host created.");}publicStandardContextgetContext(Stringname){StandardContextcontext=contextMap.get(name);if(context==null){context=newStandardContext();context.setDocBase(name);context.setConnector(connector);WebappClassLoaderloader=newWebappClassLoader();context.setLoader(loader);loader.start();this.contextMap.put(name,context);}returncontext;}// ...}

getContext方法负责根据url中的标识信息找到对应的context容器,没有则创建。
对应的要定义basic valve,负责根据url路径信息找到对应的context容器完成调用:

packagemonitomcat.server.core;// ...publicclassStandardHostValveextendsValveBase{@Overridepublicvoidinvoke(Requestrequest,Responseresponse,ValveContextcontext)throwsIOException,ServletException{System.out.println("StandardHostValve invoke()");Stringdocbase=((HttpRequestImpl)request).getDocbase();System.out.println("StandardHostValve invoke getdocbase : "+docbase);StandardHosthost=(StandardHost)getContainer();StandardContextservletContext=host.getContext(docbase);try{servletContext.invoke(request,response);}catch(Exceptione){System.out.println(e.toString());}catch(Throwablee){System.out.println(e.toString());}}}

这里的docbase就是需要的上下文信息。根据docbase从本文新增的host容器中获取context容器。
因为现在是多个应用,要加载的类可能是有所不同的,后续为了解决不同应用类名相同问题,也需要定义不同的类加载器,因此定义一个新的类加载器,来进行包装增强现有的类加载器:

packagemonitomcat.server.core;// ...publicclassWebappClassLoader{ClassLoaderclassLoader;Stringpath;Stringdocbase;Containercontainer;// ...publicsynchronizedvoidstart(){System.out.println("Starting WebappLoader");try{// create a URLClassLoaderURL[]urls=newURL[1];URLStreamHandlerstreamHandler=null;FileclassPath=newFile(System.getProperty("minit.base"));Stringrepository=(newURL("file",null,classPath.getCanonicalPath()+File.separator)).toString();if(docbase!=null&&!docbase.equals("")){repository=repository+docbase+File.separator;}urls[0]=newURL(null,repository,streamHandler);System.out.println("Webapp classloader Repository : "+repository);classLoader=newURLClassLoader(urls);}catch(Exceptione){System.out.println(e.toString());}}publicvoidstop(){}}

start()方法中初始化类加载器,这里方法名start并不是真的要启动什么东西,而只是给定一个更加形象的名字,不用纠结!!!
接着要修改connector连接器,使用新的顶层容器host,如下:

packagemonitomcat.server.startup;importmonitomcat.server.connector.http.HttpConnector;importmonitomcat.server.core.StandardHost;importjava.io.File;/** * 总的服务器对象,负责管理类工作的对象(类似于公司的管理层,负责管理,但不负责具体干活,但管理本身不也是一种职责嘛!所以也可以用单一职责来进行说明) */publicclassBootstrap{// public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";publicstaticfinalStringWEB_ROOT=System.getProperty("user.dir")+File.separator+"target"+File.separator+"classes"+File.separator+"webroot";publicstaticfinalStringWEB_ROOT_SERVLET=System.getProperty("user.dir")+File.separator+"target"+File.separator+"classes"+File.separator+"monitomcat"+File.separator+"server"+File.separator+"test/";publicstaticvoidmain(String[]args){// 启动连接器,试用连接器组件HttpConnectorconnector=newHttpConnector();StandardHosthostContainer=newStandardHost();// StandardContext servletContainer = new StandardContext();// 设置日志// servletContainer.setLogger(new FileLogger());// connector.setContainer(servletContainer);connector.setContainer(hostContainer);// FilterDef filterDef = new FilterDef();// filterDef.setFilterName("TestFilter");// filterDef.setFilterClass("test.TestFilter");// filterDef.setFilterClass("monitomcat.server.test.TestFilter");// servletContainer.addFilterDef(filterDef);// FilterMap filterMap = new FilterMap();// filterMap.setFilterName("TestFilter");// filterMap.setURLPattern("/*");// servletContainer.addFilterMap(filterMap);// servletContainer.filterStart();// ContainerListenerDef listenerDef = new ContainerListenerDef();// listenerDef.setListenerName("TestListener");// listenerDef.setListenerClass("monitomcat.server.test.TestListener");// servletContainer.addListenerDef(listenerDef);// servletContainer.listenerStart();// servletContainer.start();// hostContainer.start();System.setProperty("minit.base",WEB_ROOT_SERVLET);connector.start();}}

这里我们将filter和listener初始化的工作也从connector中剥离出去了,具体看源码吧!

1.1:测试

分别使用app1,app2文件夹模拟两个不同的应用,如下:

测试:

C:\Users\dell9020>curl http://localhost:8080/app1/servlet/monitomcat.server.test.app1.HelloServlet <!DOCTYPE html> <html> <head><meta charset="utf-8"><title>Test</title></head> <body bgcolor="#f0f0f0"> <h1 align="center">Hello World from standard servlet from app1!!!</h1> C:\Users\dell9020>curl http://localhost:8080/app2/servlet/monitomcat.server.test.app2.HelloServlet <!DOCTYPE html> <html> <head><meta charset="utf-8"><title>Test</title></head> <body bgcolor="#f0f0f0"> <h1 align="center">Hello World from standard servlet from app2!!!</h1>

不同的url路径对应到不同的模拟应用中。
源码 。

2:自定义类加载器支持多应用

在多应用时,应该做到应用之间隔离,tomcat本身其实也是一个应用,所以应用和tomcat本身之间也要隔离,因为大家都是运行在同一个jvm中的,所以当存在相同的类,或者一个类的不同版本时,不能互相影响,此时使用独立的类加载器就能做到这点,如下:

为了规范自定义类加载器,先来定义一个顶层接口:

packagemonitomcat.server;/** * 自定义类加载器接口 */publicinterfaceLoader{publicContainergetContainer();publicvoidsetContainer(Containercontainer);publicStringgetPath();publicvoidsetPath(Stringpath);publicStringgetDocbase();publicvoidsetDocbase(Stringdocbase);publicClassLoadergetClassLoader();publicStringgetInfo();publicvoidaddRepository(Stringrepository);publicString[]findRepositories();publicvoidstart();publicvoidstop();}

定义用于加载公用类库的类加载器,这里是tomcat的lib,即tomcat应用本身的依赖:

packagemonitomcat.server.core;// ...publicclassCommonLoaderimplementsLoader{// ...publicsynchronizedvoidstart(){System.out.println("Starting Common Loader, docbase: "+docbase);try{// create a URLClassLoaderURL[]urls=newURL[1];URLStreamHandlerstreamHandler=null;FileclassPath=newFile(System.getProperty("minit.home"));Stringrepository=(newURL("file",null,classPath.getCanonicalPath()+File.separator)).toString();repository=repository+"lib"+File.separator;urls[0]=newURL(null,repository,streamHandler);System.out.println("Common classloader Repository : "+repository);classLoader=newCommonClassLoader(urls);}catch(Exceptione){System.out.println(e.toString());}}publicvoidstop(){}}

minit.home是tomcat的安装目录,如D:\programs\apache-tomcat-7.0.81\apache-tomcat-7.0.81这种。
定义加载应用类库的类加载器:

packagemonitomcat.server.core;// .../** * 应用类加载器 */publicclassWebappLoaderimplementsLoader{// ...publicsynchronizedvoidstart(){System.out.println("Starting WebappLoader");try{// create a URLClassLoaderURL[]urls=newURL[1];URLStreamHandlerstreamHandler=null;FileclassPath=newFile(System.getProperty("minit.base"));Stringrepository=(newURL("file",null,classPath.getCanonicalPath()+File.separator)).toString();if(docbase!=null&&!docbase.equals("")){repository=repository+docbase+File.separator;}// repository = repository + "WEB-INF" + File.separator + "classes" + File.separator;urls[0]=newURL(null,repository,streamHandler);System.out.println("WEbapp classloader Repository : "+repository);classLoader=newWebappClassLoader(urls,parent);}catch(Exceptione){System.out.println(e.toString());}}publicvoidstop(){}}

这里minit.base就是应用部署的目录,如D:\programs\apache-tomcat-7.0.81\apache-tomcat-7.0.81\webapps\manager
接着还要修改容器接口使用Loader接口,如下:

packagemonitomcat.server;// ...publicinterfaceContainer{// ...// public ClassLoader getLoader();// public WebappClassLoader getLoader();publicLoadergetLoader();// public void setLoader(ClassLoader loader);// public void setLoader(WebappClassLoader loader);publicvoidsetLoader(Loaderloader);// ...}

对应的ContainerBase,以及StandardHost,StandardContext等容器也要修改,这里不再列出,具体看源码吧!
测试同上。
源码 。

写在后面

参考文章列表

手把手带你写一个 MiniTomcat 。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 16:24:16

PyTorch实现U-Net图像分割:从原理到实战的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 16:22:23

Gemini Spark AI Agent框架:从原理到实践的智能办公自动化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 16:22:17

基于PID控制与视觉识别的滚球控制系统设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 16:19:07

07-01-并发-ConcurrentDictionary-TKey-TValue-无锁读取与分锁写入

ConcurrentDictionary<TKey, TValue>&#xff1a;无锁读取与分锁写入 系列&#xff1a;C# 与常用数据结构源码剖析 并发集合篇 阅读时间&#xff1a;约 80 分钟 源码基线&#xff1a;.NET 8.0.0&#xff0c;dotnet/runtime 的 System.Collections.Concurrent/Concurrent…

作者头像 李华