servlet容器是用来处理请求servlet资源,并为Web客户端填充response对象的模块。在上一篇文章(也就是书的第四章)我们设计了SimpleContainer类,让他实现Container接口,也基本完成了容器的作用。但是我们得知道在实际的tomcat中有4类容器:
Engine: 表示整个Catalina servlet引擎;
Host: 包含一个或多个Context容器的虚拟主机;
Context:表示一个Web应用程序,一个Context可以包含多个Wrapper
Wrapper: 表示一个独立的Servlet;
UML图如下:
Container容器中包含addChild(Container con),removeChild(Container con),findChild(String name),findChildren()四个方法,方法的作用就不解释了,Wrapper就表示一个独立的Servlet所以,它的addChild方法体为空。
在本节,我们主要展示Wrapper的特性,其他的容器以后再说。
首先我们看看时序图(第一次画 可能在时序图规则上有些问题)
管道任务
这节主要说明档连接器调用了servlet容器的invoke方法后发生的事情。
管道包含了一个servlet容器要执行的任务(和本身的servlet无关,是指额外执行的任务)
一个Wrapper会包含一个管道,而一个管道会包含若干个Valve(阀)
public class SimpleWrapper implements Wrapper, Pipeline { // the servlet instance private Servlet instance = null; private String servletClass; private Loader loader; private String name; private SimplePipeline pipeline = new SimplePipeline(this); protected Container parent = null; ... } public class SimplePipeline implements Pipeline { public SimplePipeline(Container container) { setContainer(container); } // The basic Valve (if any) associated with this Pipeline. protected Valve basic = null; // The Container with which this Pipeline is associated. protected Container container = null; // the array of Valves protected Valve valves[] = new Valve[0]; //所有的阀 }
在阀中,请求会像水一样经过管道中的每一个阀,上图中的阀n会调用我们请求的servlet类。
再调用容器的invoke方法后,会到如下的方法
invokeNext是StandardPipelineValveContext类中的方法(StandardPipelineValveContext是管道的一个内部类,因此可以访问管道的所有成员) public void invokeNext(Request request, Response response) throws IOException, ServletException { int subscript = stage; stage = stage + 1; // Invoke the requested Valve for the current request thread if (subscript < valves.length) { valves[subscript].invoke(request, response, this); } else if ((subscript == valves.length) && (basic != null)) { basic.invoke(request, response, this); } else { throw new ServletException("No valve"); } } } // end of inner class
ok?如水般流过。下面这行代码值得我们仔细看看
valves[subscript].invoke(request, response, this);
也可参阅拙作
<<说说struts2中拦截器的请求流程一(模拟大致流程)>>
http://blog.csdn.net/dlf123321/article/details/40078583
public class HeaderLoggerValve implements Valve, Contained { protected Container container; public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { // Pass this request on to the next valve in our pipeline valveContext.invokeNext(request, response); System.out.println("Header Logger Valve"); ServletRequest sreq = request.getRequest(); if (sreq instanceof HttpServletRequest) { HttpServletRequest hreq = (HttpServletRequest) sreq; Enumeration<?> headerNames = hreq.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement().toString(); String headerValue = hreq.getHeader(headerName); System.out.println(headerName + ":" + headerValue); } } else System.out.println("Not an HTTP Request"); System.out.println("------------------------------------"); } }
这行代码 valveContext.invokeNext(request, response); 看懂了吧
另一方面 大家应该能看出来,所有的阀都是逆向执行的,每次执行一个阀的时候,阀都会先传递给后面的阀,等到后面的执行完毕后,才执行自己的业务逻辑。
当通道里的处了最后一个基础阀外,都走了一遍的时候自然就执行如下代码
basic.invoke(request, response, this);
问题是basic是谁?
这个咱们等会再说。
Pipeline接口
我们在这里使用的是SimplePipeline,它实现了Pipeline的接口。
public interface Pipeline { public Valve getBasic(); public void setBasic(Valve valve); public void addValve(Valve valve); public Valve[] getValves(); public void invoke(Request request, Response response) throws IOException, ServletException; public void removeValve(Valve valve); }
Pipeline里面有个基础阀,是最后调用的,负责处理request对象鱼response对象。因而这里有getset方法
通道里面存放的是阀,所以剩下的方法就不用多说了吧。
另外tomcat4中,还存在一个StandardPipelineValveContext,上面已经说它是SimplePipeline的一个内部类。
Valve接口
public interface Valve { public String getInfo(); public void invoke(Request request, Response response,ValveContext context) throws IOException, ServletException; }
很清晰,不是吗?
Contained接口
public interface Contained { public Container getContainer(); public void setContainer(Container container); }
实现这个接口的类,至多与一个servlet容器相关联。
Wrapper接口
首先我们在文章开始的时候就说了,Wrapper包装的是一个最基础的servlet
这个接口里面有两个方法比较重要
public javax.servlet.Servlet allocate() throws javax.servlet.ServletException;
public void load() throws javax.servlet.ServletException;
allocate会分配一个已经初始化的servlet实例,
load会载入它。
Wrapper应用程序
类图如下:
SimpleLoader类
public class SimpleLoader implements Loader { public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; ClassLoader classLoader = null; Container container = null; public SimpleLoader() { try { URL[] urls = new URL[1]; URLStreamHandler streamHandler = null; File classPath = new File(WEB_ROOT); String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ; urls[0] = new URL(null, repository, streamHandler); classLoader = new URLClassLoader(urls); } catch (IOException e) { System.out.println(e.toString() ); } } }
构造函数会初始会类加载器,供SimpleWrapper使用
SimpleWrapper类
该类实现了 org.apache.catalina.Wrapper 接口并且实现了 allocate 方法和load 方法。
器构造函数如下
public SimpleWrapper() {
pipeline.setBasic(new SimpleWrapperValve());
}
上文我们曾问过,basic从哪里来,从这里来!
SimpleWrapperValve类
通过pipeline.setBasic(new SimpleWrapperValve())我们知道SimpleWrapperValve类是一个基础阀,用来处理传递Servlet的参数,如下:
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { SimpleWrapper wrapper = (SimpleWrapper) getContainer(); ServletRequest sreq = request.getRequest(); ServletResponse sres = response.getResponse(); Servlet servlet = null; HttpServletRequest hreq = null; if (sreq instanceof HttpServletRequest) hreq = (HttpServletRequest) sreq; HttpServletResponse hres = null; if (sres instanceof HttpServletResponse) hres = (HttpServletResponse) sres; // Allocate a servlet instance to process this request try { servlet = wrapper.allocate(); if (hres!=null && hreq!=null) { servlet.service(hreq, hres); } else { servlet.service(sreq, sres); } } catch (ServletException e) { } }
应该没有什么问题。
ClientIPLoggerValve类
是用来显示客户端的ip地址的,代码从略,另外还有HeaderLoggerValve,用来显示http的请求头,代码从略。
Bootstrap1启动类
public final class Bootstrap1 { @SuppressWarnings("deprecation") public static void main(String[] args) { /* call by using http://localhost:8080/ModernServlet, but could be invoked by any name */ HttpConnector connector = new HttpConnector(); Wrapper wrapper = new SimpleWrapper(); wrapper.setServletClass("ModernServlet"); Loader loader = new SimpleLoader(); Valve valve1 = new HeaderLoggerValve(); Valve valve2 = new ClientIPLoggerValve(); wrapper.setLoader(loader); ((Pipeline) wrapper).addValve(valve1); ((Pipeline) wrapper).addValve(valve2); connector.setContainer(wrapper); try { connector.initialize(); connector.start(); // make the application wait until we press a key. System.in.read(); } catch (Exception e) { e.printStackTrace(); } } }
运行结果如下:
看到了,其实在部分里,不管你请求那个服务,返回的结果都一样,为什么?这个还用我来解释吗?
这一章东西比较多,关于context容器的使用,我们放在下一节中。