关于容器之wrapper包装器

时间:2022-11-02 08:21:36

关于容器之wrapper包装器的思考:

Tomcat引入了连接器connector,容器container,容器的这种思想在很多框架中都能看到,例如struts2里的xwork容器,spring里的容器。Tomcat中的容器包括:

①  Engine:表示整个Catalina的servlet引擎

②  Host:表示一个拥有数个上下文的虚拟主机

③  Context:表示一个web应用,一个context包含一个或多个wrapper

④  Wrapper:表示一个独立的servlet

下面简单介绍只包含wrapper容器的包装器,下图表示一个请求在单个容器wrapper中的流转:

关于容器之wrapper包装器

启动服务器后,连接器中的监听就已经蓄势待发,等待客户端的连接,这时客户端发送请求,连接器将请求交给了容器,如果说,我们需要处理一个servlet,容器应该怎么办?

① 容器中要有一个能加载servlet的类加载器

② 容器要知道加载哪个类

③ 容器要引入流水线pipeline

④  为流水线配上阀门

⑤  将连接器和容器连接上

Tomcat为什么要这样设计,大师这样设计肯定是有理由的,而我只能找生活的实例去说服自己,这样做确实是好的。

客户端的请求就像一瓶浑浊不堪的水,里面什么都有,这瓶水需要进行加工最后得到纯净水,连接器就像运输车,时刻待命,要将这些水运送到指定的工厂(容器),工厂拿到材料,为了效率和规范,会有很多条流水线,由于原材料中含有很多杂质,流水线上会有很多阀门,像过滤器一样,每过一个阀门,你都可以进行处理,但是,必须经过所有阀门,当经过最后一个阀门,纯净水也就生产出来了。

所以很多框架引入容器还是很方便的,比如,我想记录一下这些水都是在什么时间被依次处理的(就好比是日志),那么,我们就可以在容器中引入日志组件,让他专门来记录日志……

 

那么,基于上面只包含一个容器即wrapper该如何实现:

下面的代码引自《How TomcatWork》

关于容器之wrapper包装器

根据上面的步奏我们先看看启动类:

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();
}
}

这是启动类的main方法,首先声明连接器,再声明容器,类的加载器,声明阀门,将类的加载器、阀门放入容器,最后连接器启动。那么容器什么时候被调用呢,查看连接器的代码就可发现这段代码:

try {
((HttpServletResponse) response).setHeader
("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
connector.getContainer().invoke(request, response);
}

connector.getContainer().invoke(request,response);

容器调用invoke方法。

那么wrapper容器的invoke又是什么呢:

  public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);
}

显然,容器调用了流水线pipeline的invoke方法。

public void invoke(Request request, Response response)
throws IOException, ServletException {
// Invoke the first Valve in this pipeline for this request
(new SimplePipelineValveContext()).invokeNext(request, response);
}

流水线的invoke方法又调用它内部类SimplePipelineValveContext的invokeNext方法。

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");
}
}
}

然后我们看看每个阀门是怎么定义的:

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);也就是下一个阀门的invoke方法,最后当调用最基本的阀门(也就是最后一个阀门)后,容器的处理过程结束。

所以基本阀门是最后被执行的,前面的方法都在栈中,前面两个阀门先入栈,入栈后先调用了invokeNext,下一个阀门入栈,下一个阀门又调用invokeNext,基本阀门入栈,因为他是最后一个阀门,执行完出栈。