写在前面
本文看下我们的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 。