Host
Host是Context的父容器。如果想在一个tomcat上部署多个context就需要使用Host了。上下文容器的父容器是主机,但是可能有一些其它实现,没有必要的时候也可以忽略。不过在实践中,即使是一个Context,我们也使用了Host,为什么?后面我们再说。
Host是个接口,里面有个map方法比较重要
/** * Return the Context that would be used to process the specified * host-relative request URI, if any; otherwise return <code>null</code>. * * @param uri Request URI to be mapped */ public Context map(String uri);
根据url来返回一个Context。
Host的标准实现是StandardHost
构造函数,不解释
public StandardHost() { super(); pipeline.setBasic(new StandardHostValve()); }
StandardHost并没有invoke方法,它需要调用ContainerBase的invoke方法。
//ContainerBase.java public void invoke(Request request, Response response)throws IOException, ServletException { pipeline.invoke(request, response); }
另外StandardHostValve这个基础阀会调用StandardHost的map方法获得一个context容器。
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { .... StandardHost host = (StandardHost) getContainer(); //这里调用的是ContainerBase的map方法 最终会调用StandardHost的map方法(两个map方法没有关系 没有//复写 参数都不一样) Context context = (Context) host.map(request, true); .... }
StandHostMapper
在ContainerBase的start中有如下的代码
addDefaultMapper(this.mapperClass);
看ConntainerBase
ContainerBase.java protected void addDefaultMapper(String mapperClass) { ...... // Instantiate and add a default Mapper Class<?> clazz = Class.forName(mapperClass); Mapper mapper = (Mapper) clazz.newInstance(); mapper.setProtocol("http"); addMapper(mapper); ...... }
这里我又一个小问题,为什么ContainerBase的start中的
addDefaultMapper(this.mapperClass);
调用的是子类中的addDefaultMapper呢?
StandardHost.java protected void addDefaultMapper(String mapperClass) { // 参数默认为 "org.apache.catalina.core.StandardHostMapper"; super.addDefaultMapper(this.mapperClass); }
StandardHost的start方法在结尾的时候会调用父类的start方法。
注意:Tomcat4 中的 standardContext 使用了不同的方法来创建一个默认映射器。它的 start 方法中并没有调用 super.start()。相反 Standardcontext 的start 方法调用 addDefaultMapper 来传递 mapperClass 变量。
StandardHostMapper的map方法就是调用StandardHost的map方法。
public Container map(Request request, boolean update) { ... String uri = ((HttpRequest) request).getDecodedRequestURI(); Context context = host.map(uri); ... return (context); }
在得到上下对象的时候需要一个往返过程,map 方法介绍两个参数,该方法是在 ContainerBase 中的。然后 ContainerBase 类又在它的子对象中查找合适的映射器并调用它的 map 方法。
StandardHostValve
这是Host的基础阀,什么时候注入的?大家自己想。
StandardHostValve这个基础阀会调用StandardHost的map方法获得一个context容器。
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { .... StandardHost host = (StandardHost) getContainer(); //这里调用的是ContainerBase的map方法 最终会调用StandardHost的map方法(两个map方法没有关系 没有//复写 参数都不一样) Context context = (Context) host.map(request, true); .... HttpServletRequest hreq = (HttpServletRequest) request.getRequest(); String sessionId = hreq.getRequestedSessionId(); if (sessionId != null) { Manager manager = context.getManager(); if (manager != null) { Session session = manager.findSession(sessionId); if ((session != null) && session.isValid()) session.access(); //修改session的最后访问时间 } } context.invoke(request, response); //最后调用context的invoke }
为什么必须要有一个Host容器
想想我们之前的SimpleConextConfig,它的作用是将context的configured设置为true,为什么不在context里直接设置?参见观察者模式。
在ConextConfig的标准实现的applicationConfig中
private void applicationConfig() { ... synchronized (webDigester) { try { URL url = servletContext.getResource(Constants.ApplicationWebXml); ..... }
servletContext为ApplicationContext的实例
public URL getResource(String path) throws MalformedURLException { DirContext resources = context.getResources(); if (resources != null) { String fullPath = context.getName() + path; // this is the problem. Host must not be null String hostName = context.getParent().getName(); .... }
看到最后一行的getParent大家都明白了吧。
当然如果使用的是之前的SimpleContextConfig,就不需要Host了。
Engine
在tomcat中,Engine的标准实现是:org.apache.catalina.core.StandardEngine。
同样的Engine也有一个基础阀:StandardEngineValve
同是Engine不能再有父容器了,子容器也只能是Host。
StandardEngineValve
StandardEngineValve的invoke如下:
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { .......... // Select the Host to be used for this Request StandardEngine engine = (StandardEngine) getContainer(); Host host = (Host) engine.map(request, true); //同样调用的是Containerbase的map //addDefaultMapper 怎么来的就不用说//了吧 if (host == null) { ((HttpServletResponse) response.getResponse()).sendError (HttpServletResponse.SC_BAD_REQUEST, sm.getString("standardEngine.noHost", request.getRequest().getServerName())); return; } // Ask this Host to process this request host.invoke(request, response); }
验证了request与response后,就调用Containerbase的map获得一个Host,然后调用host的invoke。