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

发布时间:2026/9/3 16:28:48
自己动手写一个tomcat之8多应用 写在前面本文看下我们的tomcat如何支持多应用的场景。1正文当多应用时就不能简单用context容器调用wrapper容器了因为此时会有多个context容器需要根据url的上下文信息来确定使用哪个context容器。这相当于是多了一个新的职责所以来定义一个新的类StandardHost如下packagemonitomcat.server.core;// ...publicclassStandardHostextendsContainerBase{HttpConnectorconnectornull;MapString,StandardContextcontextMapnewConcurrentHashMap();//contextName - servletContextprivateArrayListContainerListenerDeflistenerDefsnewArrayList();privateArrayListContainerListenerlistenersnewArrayList();publicStandardHost(){super();pipeline.setBasic(newStandardHostValve());log(Host created.);}publicStandardContextgetContext(Stringname){StandardContextcontextcontextMap.get(name);if(contextnull){contextnewStandardContext();context.setDocBase(name);context.setConnector(connector);WebappClassLoaderloadernewWebappClassLoader();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();StandardContextservletContexthost.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[]urlsnewURL[1];URLStreamHandlerstreamHandlernull;FileclassPathnewFile(System.getProperty(minit.base));Stringrepository(newURL(file,null,classPath.getCanonicalPath()File.separator)).toString();if(docbase!null!docbase.equals()){repositoryrepositorydocbaseFile.separator;}urls[0]newURL(null,repository,streamHandler);System.out.println(Webapp classloader Repository : repository);classLoadernewURLClassLoader(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_ROOTSystem.getProperty(user.dir)File.separatortargetFile.separatorclassesFile.separatorwebroot;publicstaticfinalStringWEB_ROOT_SERVLETSystem.getProperty(user.dir)File.separatortargetFile.separatorclassesFile.separatormonitomcatFile.separatorserverFile.separatortest/;publicstaticvoidmain(String[]args){// 启动连接器试用连接器组件HttpConnectorconnectornewHttpConnector();StandardHosthostContainernewStandardHost();// 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测试分别使用app1app2文件夹模拟两个不同的应用如下测试C:\Users\dell9020curl http://localhost:8080/app1/servlet/monitomcat.server.test.app1.HelloServlet !DOCTYPE html html headmeta charsetutf-8titleTest/title/head body bgcolor#f0f0f0 h1 aligncenterHello World from standard servlet from app1!!!/h1 C:\Users\dell9020curl http://localhost:8080/app2/servlet/monitomcat.server.test.app2.HelloServlet !DOCTYPE html html headmeta charsetutf-8titleTest/title/head body bgcolor#f0f0f0 h1 aligncenterHello 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[]urlsnewURL[1];URLStreamHandlerstreamHandlernull;FileclassPathnewFile(System.getProperty(minit.home));Stringrepository(newURL(file,null,classPath.getCanonicalPath()File.separator)).toString();repositoryrepositorylibFile.separator;urls[0]newURL(null,repository,streamHandler);System.out.println(Common classloader Repository : repository);classLoadernewCommonClassLoader(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[]urlsnewURL[1];URLStreamHandlerstreamHandlernull;FileclassPathnewFile(System.getProperty(minit.base));Stringrepository(newURL(file,null,classPath.getCanonicalPath()File.separator)).toString();if(docbase!null!docbase.equals()){repositoryrepositorydocbaseFile.separator;}// repository repository WEB-INF File.separator classes File.separator;urls[0]newURL(null,repository,streamHandler);System.out.println(WEbapp classloader Repository : repository);classLoadernewWebappClassLoader(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以及StandardHostStandardContext等容器也要修改这里不再列出具体看源码吧测试同上。源码 。写在后面参考文章列表手把手带你写一个 MiniTomcat 。