Tomcat源码分析(四)--容器处理链接之责任链模式

系统 1648 0

本系列转载自 http://blog.csdn.net/haitao111313/article/category/1179996 
目标:在这篇文章希望搞明白 connector.getContainer().invoke(request, response); 调用容器的invoke后是怎么传递到 servlet或者jsp的?

   由上篇文章 Tomcat源码分析(三)--连接器是如何与容器关联的? 可知, connector.getContainer()得到的容器应该是 StandardEngine(其实应该是由server.xml文件配置得到的,这里先假定是 StandardEngine ), StandardEngine没有invoke方法,它继承与ContainerBase(事实上所有的容器都继承于ContainerBase,在ContainerBase类有一些容器的公用方法和属性),抽象类 ContainerBase 的invoke方法如下:

  1. protected  Pipeline pipeline =  new  StandardPipeline( this ); //标准管道的实现StandardPipeline   
  2. public   void  invoke(Request request, Response response)  
  3.         throws  IOException, ServletException {  
  4.        pipeline.invoke(request, response); //调用管道里的invoke   
  5.    }  

由代码可知 ContainerBase 的invoke方法是传递到Pipeline,调用了Pipeline的invoke方法。这里要说一下Pipeline这个类,这是一个管道类,每一个管道类 Pipeline 包含数个阀类,阀类是实现了Valve接口的类,Valve接口声明了invoke方法。管道和阀的概念跟servlet编程里面的过滤器机制非常像, 管道就像过滤器链,阀就好比是过滤器 。不过管道中还有一个基础阀的概念,所谓基础阀就是在管道中当管道把所有的普通阀都调用完成后再调用的。不管是普通阀还是基础阀,都实现了Value接口,也都继承于抽象类ValveBase。在tomcat中,当调用了管道的invoke方法,管道则会顺序调用它里面的阀的invoke方法。先看看管道StandardPipeline的invoke方法:

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

其中StandardPipelineValveContext是管道里的一个内部类,内部类的作用是帮助管道顺序调用阀Value的invoke方法,下面看它的定义代码:

  1. protected   class  StandardPipelineValveContext  
  2.       implements  ValveContext {  
  3.       protected   int  stage =  0 ;  
  4.       public  String getInfo() {  
  5.           return  info;  
  6.      }  
  7.       public   void  invokeNext(Request request, Response response)  
  8.           throws  IOException, ServletException {  
  9.           int  subscript = stage; //阀的访问变量   
  10.          stage = stage +  1 ; //当前访问到第几个阀   
  11.           // Invoke the requested Valve for the current request thread   
  12.           if  (subscript < valves.length) {  
  13.              valves[subscript].invoke(request, response,  this ); //管道的阀数组   
  14.          }  else   if  ((subscript == valves.length) && (basic !=  null )) {  
  15.              basic.invoke(request, response,  this ); //当基础阀调用完成后,调用管道的基础阀的invoke阀   
  16.          }  else  {  
  17.               throw   new  ServletException  
  18.                  (sm.getString( "standardPipeline.noValve" ));  
  19.          }  
  20.      }  
  21.  }  
内部类 StandardPipelineValveContext的invokeNext方法通过使用局部变量来访问下一个管道数组,管道类的变量stage保存当前访问到第几个阀,valves保存管道的所有阀, 在调用普通阀的invoke方法是,会把内部类 StandardPipelineValveContext本身传进去,这样在普通阀中就能调用invokeNext方法以便访问下一个阀的invoke方法 ,下面 看一个普通阀的invoke方法:

  1. public   void  invoke(Request request, Response response, ValveContext valveContext)  
  2.    throws  IOException, ServletException {  
  3.    // Pass this request on to the next valve in our pipeline   
  4.   valveContext.invokeNext(request, response); //使用调用下一个阀的invoke方法   
  5.   System.out.println( "Client IP Logger Valve" );  
  6.   ServletRequest sreq = request.getRequest();  
  7.   System.out.println(sreq.getRemoteAddr());  
  8.   System.out.println( "------------------------------------" );  
  9. }  
这个阀的invoke方法,通过传进来到 StandardPipelineValveContext(实现了ValveContext接口 )的invokeNext方法来实现调用下一个阀的invoke方法。然后简单的打印了请求的ip地址。

最后再看 StandardPipelineValveContext的invokeNext方法,调用完普通阀数组valves的阀后,开始调用基础阀basic的 invoke方法,这里先说基础阀的初始化,在每一个容器的构造函数类就已经初始化了基础阀,看容器StandardEngine的构造函数:

  1. public  StandardEngine() {  
  2.         super ();  
  3.        pipeline.setBasic( new  StandardEngineValve()); //容器StandardEngine的基础阀StandardEngineValve   
  4.    }  
即在容器构造的时候就已经把基础阀添加进管道pipeline中,这样在 StandardPipelineValveContext中的invokeNext方法里就能调用 基础阀的invoke了,当basic.invoke(request, response, this);进入基础阀StandardEngineValve,看基础阀 StandardEngineValve的invoke方法:

  1. public   void  invoke(Request request, Response response,  
  2.                     ValveContext valveContext)  
  3.       throws  IOException, ServletException {  
  4.     ...........................  
  5.   
  6.       // Ask this Host to process this request   
  7.      host.invoke(request, response);  
  8.   
  9.  }  

这里省略了很多代码,主要是为了更加理解调用逻辑,在StandardEngine的基础阀StandardEngineValve里,调用了子容器invoke方法(这里子容器就是StandardHost), 还记得一开始connector.invoke(request, response)(即StandardEngine的invoke方法)现在顺利的传递到子容器StandardHost的invoke方法,变成了StandardHost.invoke(request, response)。 由此可以猜测StandardHost也会传递给它的子容器,最后传递到最小的容器StandardWrapper的invoke方法,然后调用StandardWrapper的基础阀StandardWrapperValue的invoke方法,由于StandardWrapper是最小的容器了,不能再传递到其他容器的invoke方法了,那它的invoke方法做了什么?主要做了两件事, 1:创建一个过滤器链并  2:分配一个servlet或者jsp,主要代码如下:

  1. StandardWrapperValue的invoke方法  
  2.             servlet = wrapper.allocate();   //分配一个servlet   
  3. ....................................................................  
  4.           // Create the filter chain for this request   
  5.             ApplicationFilterChain filterChain =  
  6.             createFilterChain(request, servlet);  
  7. .........................................................  
  8.             String jspFile = wrapper.getJspFile(); //分配一个jsp   
  9.              if  (jspFile !=  null )  
  10.                 sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);  
  11.              else   
  12.                 sreq.removeAttribute(Globals.JSP_FILE_ATTR);  
  13.              if  ((servlet !=  null ) && (filterChain !=  null )) {  
  14.                 filterChain.doFilter(sreq, sres); //调用过滤器链处理请求,sreq和sres是request和response的包装类,在这里面会调用servlet的services方法   
  15.             }  

这里先不关注jsp,只关注一下servlet,通过servlet = wrapper.allocate(); 进入StandardWrapper的allocate方法,allocate主要就是调用了loadServlet方法,在loadServlet方法类用tomcat自己的类加载器实例化了一个servlet对象,并调用了该servlet的init和service方法:

  1. StandardWrapper的loadServlet方法(这里省略了很多其他的代码)  
  2.  Servlet servlet =  null ;  
  3. String actualClass = servletClass; //servlet的字节码字符串   
  4.  Loader loader = getLoader();  
  5. ClassLoader classLoader = loader.getClassLoader(); //得到类加载器   
  6.  Class classClass =  null ;  
  7.                  if  (classLoader !=  null ) {  
  8.                     System.out.println( "Using classLoader.loadClass" );  
  9.                     classClass = classLoader.loadClass(actualClass); //通过类加载器实例化servlet   
  10.                 }  else  {  
  11.                     System.out.println( "Using forName" );  
  12.                     classClass = Class.forName(actualClass); //通过反射实例化servlet   
  13.                 }  
  14.  servlet = (Servlet) classClass.newInstance(); //实例化servlet   
  15.   servlet.init(facade); //调用servlet的init   
  16.       if  ((loadOnStartup >  0 ) && (jspFile !=  null )) {  
  17.                      // Invoking jspInit   
  18.                     HttpRequestBase req =  new  HttpRequestBase();  
  19.                     HttpResponseBase res =  new  HttpResponseBase();  
  20.                     req.setServletPath(jspFile);  
  21.                     req.setQueryString( "jsp_precompile=true" );  
  22.                     servlet.service(req, res);  
  23.                 }; //调用jsp的service方法,jsp会被编译成servlet,所以也会有service方法   

至此已经把请求传递到servlet的service(或者jsp的service)方法,整个处理请求到这里就结束了,剩下的就是返回客户端了。这里提出几个问题。 1:那么多的servlet,tomcat是怎么知道要请求到哪个servlet? 这个问题留待下篇博客再来讲吧。

Tomcat源码分析(四)--容器处理链接之责任链模式


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论