问题:Web容器(例如Tomcat)是怎么来执行jsp文件的?
首先它会将放在webapps目录下的jsp文件(这里以hello.jsp为例)翻译成hello_jsp.java文件并编译为hello_jsp.class(注意:生成的文件名都是小写字母),存放在Tomcat安装目录\work\Catalina\localhost\webDemo\org\apache\jsp路径下,打开.java源文件,你会看到hello_jsp类继承了httpJspBase类。而HttpJspBase类又是HttpServlet的子类,所以hello_jsp类也就间接实现了Servlet接口,也就是说Web容器把jsp文件转换成了一个servlet类了
然后Web容器根据配置文件(web.xml)和客户端(浏览器)的请求URL来确定使用哪一个servlet类.接下来调用这个servlet类的service()方法。在hello_jsp.java中看不到这个service()方法,并不代表它没有这个方法,只代表它没有重写这个方法。(除了重写之外,从父类继承的方法在其子类中,都是隐藏的,所以你看不到。)那么如果想看看在这个类中从其父类继承得到的方法长什么样?很简单!找到有这个方法的父类,查看方法的源码即可。那么如果需要调用这个类的service()方法怎么办?放心,No problem呀,它会去寻找所有父类里有这个方法的父类,然后再执行这个父类的方法体(在这里我们把这个机制或者行为笑称为“儿子继承的东西没找到,咱不怕,去找老子”),在这里说明一下:在子类中调用继承的方法,这个方法就是子类的方法,即SubClass.method(),只不过执行的方法体内容和其父类中的方法体内容一模一样而已。那么接着咱们就来说说这个父类HttpJspBase,在它的源码中,你会看到两个方法,第一个方法_japService(),被其子类hello_jsp类重写了。另外一个方法,源码如下
public void service(HttpServletRequest quest,HttpServletResponse response) throws ServletException,IOException{ _jspService(request,response); }
这个父类中的servce()方法,子类hello_jsp继承得到的service方法也就是它。看到没有,多么简单的一个方法呀!上面提过了Web容器在确定使用哪一个sevlet类后,接着要干嘛?当然就是调用这个servlet的service()方法了,开始执行它的方法体,方法体中也没啥复杂的事情,就是调用本类中的_japService()方法,也就是hello_jsp类中的重写父类的_jspService()。
咱们接着看这个重写的_jspService()的方法体,里面有若干个out.write();这个方法的实参不就是jsp文件的html内容吗?哦,所以才有我们在浏览器上看到的输出内容。
所以在父类里定义的service()方法的作用我觉得很大:它把我们原始的继承Serlet接口并重写其service()方法的习惯转变为了继承HttpJspBase类并重写其_jspService()方法。这是一种技术上的改进和分支吧,也就造就了JSP技术的发展吧。与之类似的是HttpServlet类,它也间接实现了Servlet接口,重写了service()方法,特别是它继承了元老级的GenericServlet(这个类可是直接实现Servlet接口的第一代类呀,可谓是亲生的嫡长子呀!它提供了很多方法,可以说是一个重大改革,也为HttpServlet铺好了路,关于它以后有空再讲)。那么我们来看看Httpservlet类实现的service()方法吧
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response); }
HttpServletRequest和HttpServletResponse分别是ServletRequest和ServletResponse的子类,这样经过强转型,再最后调用自己的service方法,成功完成了接口方法到基类方法的转移了呀!还没有完,接下来才是HttpServlet的高明之处:看看他自己定义的那个service()的源码吧
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn‘t support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
比较长,言而言之,就是这些个逻辑实现,将Web容器最先调用servlet类的service()方法转移到了调用servlet类的doGet(),doPOST()等方法上,因为看不到这些底层的实现代码,所以就让我们产生了HttpServlet的入口是doGet()方法的假象。当然这也带来了简化流程的好处:继承了HttpServlet类的子类只要重写doGet (),doPOST()等方法即可。
总结:jsp实质上还是servlet,是一种加强版的servlet技术吧