Java Web后台入门实战(一)-YuaneQi Sharing
2016-12-26 10:46
281 查看
很多小伙伴对Java Web后台开发感兴趣,但是又苦于入门,一是书上和网络上的其他资料大多是千篇一律,要么就是偏知识,大量篇幅介绍Servlet相关内容,看不进去,要么太浅,看完又觉得好像并没有学到什么。所以从大家的需求出发,内容主要包括以下四个部分:
第一部分从javax.servlet.http.HttpServlet类源码开始,了解一次http请求过来后,服务器的处理逻辑是什么样儿的。
第二部分是动手实践部分,如何在Eclipse中创建Web工程,并对web工程的目录结构进行一个分析。
第三部分结合Tomcat中的examples了解Servlet相关类的应用。
第四部分设计模式篇来看下Template Pattern在Servlet中的应用,不要觉得设计模式很高深,是无法理解的知识,我们平时在写代码的过程中已经不知不觉在应用它们了。
需要的小伙伴可以下载此分享对应的PPT和FirstJavaWeb工程来对照着看
根据HttpServlet service方法的处理逻辑,HttpServlet目前只可响应客户端的GET,HEAD,POST,PUT,DELETE,OPTIONS,TRACE请求。
PUT,DELETE,OPTIONS,TRACE请求并不常使用,OPTIONS请求作者只在处理非简单CORS(Cross-origin resource sharing)的时候遇见过。
通常我们的请求只有GET和POST两种,为了响应这两种请求,一般需要重写doGet和doPost两个方法,也可以通过重写service方法的方式。但是注意如果你重写的service方法没有调用do method 方法,即使你在Servlet中又重写了其他do method 方法也是不会被调用的,原因我们看了HttpServlet类的source code就会明白。
HttpServlet是一个不包含任何抽象方法的抽象类,继承GenericServlet类,很巧妙的编码,既不能够直接创建它的实例,也不强迫子类去实现任何方法,就是说我们自己的Servlet类可以通过继承它,不重写任何方法就能够直接对外提供服务。
HttpServlet类中有两个的service方法,public void service(ServletRequest req, ServletResponse res)方法是其父类GenericServlet类的抽象方法实现,作用是接受客户端的请求并将其传递给重载的service方法,protected void service(HttpServletRequest req, HttpServletResponse resp)。容器会针对每个客户端请求创建一个处理线程,准确来说应该是使用线程池中空闲的线程,并创建Request和Response对象传递给处理线程,就是说浏览器的一次http请求的所有信息都封装在HttpServletRequest中,而HttpServletResponse对象代表服务器对客户端的响应,可以通过操作这两个对象来交换数据。
protected void service(HttpServletRequest req, HttpServletResponse resp),是HttpServlet类定义的重载方法,访问权限是protected,可用于子类继承。方法首先获取本次请求的http方法,并根据方法的类型去进入相应的if else分支处理,除了GET请求处理相对复杂一些,其他处理很简单,直接调用相应请求的do method 方法,当用户的请求不是上面列出的请求方法时,会向客户端返回501状态码,Method is not implemented by this servlet for this URI。在service中的GET请求逻辑部分首先调用getLastModified(HttpServletRequest req)方法得到一个long整数lastModified,如果为-1L则调用doGet方法,否则获取本次请求的“If-Modified-Since”头部得值,“If-Modified-Since”带一个时间值代表请求数据的上次修改时间,它会与lastModified值比较,如果lastModified值比较新,容器调用maybeSetLastModified(resp, lastModified)方法,这个方法的作用是在响应头中加一个“Last-Modified”头,告诉浏览器你应该更新本地缓存了。除此来之外的任何结果,则返回304状态码,告诉浏览器从你上次访问我之后,请求的网页未修改,你可以使用本地缓存直接加载页面,节省带宽和开销。我们看到子类如果不重写getLastModified方法的话,这个方法将永远返回-1L,每次GET请求都只是调用doGet方法而已。
当浏览器发现响应中有“Last-Modified”头部,那么它在下一次请求同一数据时会加上“If-Modified-Since”请求头,值为上一次响应的“Last-Modified”时间值。304状态码告诉客户端自从上次请求后,请求的网页未修改,本地cache的页面是最新的。服务器返回此响应时,不会返回网页内容,也就是说浏览器只在每次启动后第一次访问这个页面时,才向服务器发出请求,对于后续的访问都是直接加载本地缓存的页面,这在提高网站性能方面特别有用。
带大家演示一下,我们先写一个servlet,代码贴上,重写了父类的doGet方法,逻辑也很简单,就是打印系统当前时间相对GMT1970年1月1日0点0时0分的毫秒数。
Tomcat中运行,浏览器查看,此时请求头和响应头中并没有任何特殊的头部。
解注释第一个重写的getLastModified方法并运行,这个getLastModified方法每次返回一个新的时间值,并且总是大于请求头”If-Modified-Since”的时间值。这次我们看到响应头中多了“Last-Modified”字段,刷新下浏览器,此时请求头中多了”If-Modified-Since”,并且响应头中依然包含“Last-Modified”头,响应状态码为200,浏览器窗口中显示的字符串的时间值也一直在随着时间推进。
注释这个getLastModified方法,并解注释第二个重写的getLastModified方法,这个方法每次返回的值为请求头”If-Modified-Since”的值。刷新下浏览器,我们看到此时的响应头中并没有”Last-Modified”头,并且响应的状态码为304。再刷新下浏览器,我们看到浏览器窗口中显示的字符串中的时间值没变,证明了此时浏览器加载的是缓存的数据,并不是服务器端传回来的新数据。还有”If-Modified-Since”的时间值也没有改变。
演示完了http协议中与缓存相关的头部,第一部分源码解读的任务内容结束~
第一部分从javax.servlet.http.HttpServlet类源码开始,了解一次http请求过来后,服务器的处理逻辑是什么样儿的。
第二部分是动手实践部分,如何在Eclipse中创建Web工程,并对web工程的目录结构进行一个分析。
第三部分结合Tomcat中的examples了解Servlet相关类的应用。
第四部分设计模式篇来看下Template Pattern在Servlet中的应用,不要觉得设计模式很高深,是无法理解的知识,我们平时在写代码的过程中已经不知不觉在应用它们了。
需要的小伙伴可以下载此分享对应的PPT和FirstJavaWeb工程来对照着看
HttpServlet源码
Servlet是运行在服务器端的程序,用于处理及响应客户端的请求,它是个特殊的Java类,这个Java类必须继承javax.servlet.http.HttpServlet,HttpServlet类提供了不同的方法用于处理客户端的请求。方法 | 描述 |
---|---|
doDelete | 用于处理DELETE请求 |
doGet | 用于处理GET请求 |
doHead | 用于处理HEAD请求 |
doOptions | 用于处理OPTIONS请求 |
doPost | 用于处理POST请求 |
doPut | 用于处理PUT请求 |
doTrace | 用于处理TRACE请求 |
getLastModified | 返回一个long整数,值为所请求数据的最后修改时间相对于GMT时间1970年1月1号0时0分0秒的毫秒数 |
service | 用于映射请求,根据请求的HTTP方法,调用do Method |
http请求 | 描述 |
---|---|
GET | 获取服务器上某一资源 |
HEAD | HEAD和GET本质是一样的,区别在于HEAD请求的响应不包含响应实体,而仅仅包含响应消息头 |
POST | 向服务器提交数据 |
PUT | PUT和POST极为相似,PUT通常指定了资源的存放位置,向指定资源位置上传其最新内容 |
DELETE | 删除服务器上某一个资源 |
OPTIONS | 获取服务器针对特定资源所支持的HTTP请求方法,请求头等 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断 |
通常我们的请求只有GET和POST两种,为了响应这两种请求,一般需要重写doGet和doPost两个方法,也可以通过重写service方法的方式。但是注意如果你重写的service方法没有调用do method 方法,即使你在Servlet中又重写了其他do method 方法也是不会被调用的,原因我们看了HttpServlet类的source code就会明白。
public abstract class HttpServlet extends GenericServlet { public HttpServlet() { } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported"); if(protocol.endsWith("1.1")) resp.sendError(405, msg); else resp.sendError(400, msg); } protected long getLastModified(HttpServletRequest req) { return -1L; } protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(DispatcherType.INCLUDE.equals(req.getDispatcherType())) { doGet(req, resp); } else { NoBodyResponse response = new NoBodyResponse(resp); doGet(req, response); response.setContentLength(); } } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_post_not_supported"); if(protocol.endsWith("1.1")) resp.sendError(405, msg); else resp.sendError(400, msg); } protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_put_not_supported"); if(protocol.endsWith("1.1")) resp.sendError(405, msg); else resp.sendError(400, msg); } protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_delete_not_supported"); if(protocol.endsWith("1.1")) resp.sendError(405, msg); else resp.sendError(400, msg); } private static Method[] getAllDeclaredMethods(Class c) { if(c.equals(javax/servlet/http/HttpServlet)) return null; Method parentMethods[] = getAllDeclaredMethods(c.getSuperclass()); Method thisMethods[] = c.getDeclaredMethods(); if(parentMethods != null && parentMethods.length > 0) { Method allMethods[] = new Method[parentMethods.length + thisMethods.length]; System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length); System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length); thisMethods = allMethods; } return thisMethods; } protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Method methods[] = getAllDeclaredMethods(getClass()); boolean ALLOW_GET = false; boolean ALLOW_HEAD = false; boolean ALLOW_POST = false; boolean ALLOW_PUT = false; boolean ALLOW_DELETE = false; boolean ALLOW_TRACE = true; boolean ALLOW_OPTIONS = true; for(int i = 0; i < methods.length; i++) { Method m = methods[i]; if(m.getName().equals("doGet")) { ALLOW_GET = true; ALLOW_HEAD = true; } if(m.getName().equals("doPost")) ALLOW_POST = true; if(m.getName().equals("doPut")) ALLOW_PUT = true; if(m.getName().equals("doDelete")) ALLOW_DELETE = true; } String allow = null; if(ALLOW_GET) allow = "GET"; if(ALLOW_HEAD) if(allow == null) allow = "HEAD"; else allow = (new StringBuilder()).append(allow).append(", HEAD").toString(); if(ALLOW_POST) if(allow == null) allow = "POST"; else allow = (new StringBuilder()).append(allow).append(", POST").toString(); if(ALLOW_PUT) if(allow == null) allow = "PUT"; else allow = (new StringBuilder()).append(allow).append(", PUT").toString(); if(ALLOW_DELETE) if(allow == null) allow = "DELETE"; else allow = (new StringBuilder()).append(allow).append(", DELETE").toString(); if(ALLOW_TRACE) if(allow == null) allow = "TRACE"; else allow = (new StringBuilder()).append(allow).append(", TRACE").toString(); if(ALLOW_OPTIONS) if(allow == null) allow = "OPTIONS"; else allow = (new StringBuilder()).append(allow).append(", OPTIONS").toString(); resp.setHeader("Allow", allow); } protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String CRLF = "\r\n"; StringBuilder buffer = (new d668 StringBuilder("TRACE ")).append(req.getRequestURI()).append(" ").append(req.getProtocol()); String headerName; for(Enumeration reqHeaderEnum = req.getHeaderNames(); reqHeaderEnum.hasMoreElements(); buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName))) headerName = (String)reqHeaderEnum.nextElement(); buffer.append(CRLF); int responseLength = buffer.length(); resp.setContentType("message/http"); resp.setContentLength(responseLength); ServletOutputStream out = resp.getOutputStream(); out.print(buffer.toString()); out.close(); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if(method.equals("GET")) { long lastModified = getLastModified(req); if(lastModified == -1L) { doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader("If-Modified-Since"); } catch(IllegalArgumentException iae) { ifModifiedSince = -1L; } if(ifModifiedSince < (lastModified / 1000L) * 1000L) { maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(304); } } } else if(method.equals("HEAD")) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if(method.equals("POST")) doPost(req, resp); else if(method.equals("PUT")) doPut(req, resp); else if(method.equals("DELETE")) doDelete(req, resp); else if(method.equals("OPTIONS")) doOptions(req, resp); else if(method.equals("TRACE")) { doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object errArgs[] = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } } private void maybeSetLastModified(HttpServletResponse resp, long lastModified) { if(resp.containsHeader("Last-Modified")) return; if(lastModified >= 0L) resp.setDateHeader("Last-Modified", lastModified); } 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); } private static final long serialVersionUID = 1L; private static final String METHOD_DELETE = "DELETE"; private static final String METHOD_HEAD = "HEAD"; private static final String METHOD_GET = "GET"; private static final String METHOD_OPTIONS = "OPTIONS"; private static final String METHOD_POST = "POST"; private static final String METHOD_PUT = "PUT"; private static final String METHOD_TRACE = "TRACE"; private static final String HEADER_IFMODSINCE = "If-Modified-Since"; private static final String HEADER_LASTMOD = "Last-Modified"; private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings"); }
HttpServlet是一个不包含任何抽象方法的抽象类,继承GenericServlet类,很巧妙的编码,既不能够直接创建它的实例,也不强迫子类去实现任何方法,就是说我们自己的Servlet类可以通过继承它,不重写任何方法就能够直接对外提供服务。
HttpServlet类中有两个的service方法,public void service(ServletRequest req, ServletResponse res)方法是其父类GenericServlet类的抽象方法实现,作用是接受客户端的请求并将其传递给重载的service方法,protected void service(HttpServletRequest req, HttpServletResponse resp)。容器会针对每个客户端请求创建一个处理线程,准确来说应该是使用线程池中空闲的线程,并创建Request和Response对象传递给处理线程,就是说浏览器的一次http请求的所有信息都封装在HttpServletRequest中,而HttpServletResponse对象代表服务器对客户端的响应,可以通过操作这两个对象来交换数据。
protected void service(HttpServletRequest req, HttpServletResponse resp),是HttpServlet类定义的重载方法,访问权限是protected,可用于子类继承。方法首先获取本次请求的http方法,并根据方法的类型去进入相应的if else分支处理,除了GET请求处理相对复杂一些,其他处理很简单,直接调用相应请求的do method 方法,当用户的请求不是上面列出的请求方法时,会向客户端返回501状态码,Method is not implemented by this servlet for this URI。在service中的GET请求逻辑部分首先调用getLastModified(HttpServletRequest req)方法得到一个long整数lastModified,如果为-1L则调用doGet方法,否则获取本次请求的“If-Modified-Since”头部得值,“If-Modified-Since”带一个时间值代表请求数据的上次修改时间,它会与lastModified值比较,如果lastModified值比较新,容器调用maybeSetLastModified(resp, lastModified)方法,这个方法的作用是在响应头中加一个“Last-Modified”头,告诉浏览器你应该更新本地缓存了。除此来之外的任何结果,则返回304状态码,告诉浏览器从你上次访问我之后,请求的网页未修改,你可以使用本地缓存直接加载页面,节省带宽和开销。我们看到子类如果不重写getLastModified方法的话,这个方法将永远返回-1L,每次GET请求都只是调用doGet方法而已。
当浏览器发现响应中有“Last-Modified”头部,那么它在下一次请求同一数据时会加上“If-Modified-Since”请求头,值为上一次响应的“Last-Modified”时间值。304状态码告诉客户端自从上次请求后,请求的网页未修改,本地cache的页面是最新的。服务器返回此响应时,不会返回网页内容,也就是说浏览器只在每次启动后第一次访问这个页面时,才向服务器发出请求,对于后续的访问都是直接加载本地缓存的页面,这在提高网站性能方面特别有用。
带大家演示一下,我们先写一个servlet,代码贴上,重写了父类的doGet方法,逻辑也很简单,就是打印系统当前时间相对GMT1970年1月1日0点0时0分的毫秒数。
@WebServlet(urlPatterns = ("/example/one")) public class ExampleOneServlet extends HttpServlet { /** * serialVersionUID:TODO. */ private static final long serialVersionUID = 6686766899053457864L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().append("Frist Servlet " + System.currentTimeMillis()); } // @Override // protected long getLastModified(HttpServletRequest req) { // long now = System.currentTimeMillis(); // System.out.println("getLastModified: " + now); // return now; // } // @Override // protected long getLastModified(HttpServletRequest req) { // return req.getDateHeader("If-Modified-Since"); // } }
Tomcat中运行,浏览器查看,此时请求头和响应头中并没有任何特殊的头部。
解注释第一个重写的getLastModified方法并运行,这个getLastModified方法每次返回一个新的时间值,并且总是大于请求头”If-Modified-Since”的时间值。这次我们看到响应头中多了“Last-Modified”字段,刷新下浏览器,此时请求头中多了”If-Modified-Since”,并且响应头中依然包含“Last-Modified”头,响应状态码为200,浏览器窗口中显示的字符串的时间值也一直在随着时间推进。
注释这个getLastModified方法,并解注释第二个重写的getLastModified方法,这个方法每次返回的值为请求头”If-Modified-Since”的值。刷新下浏览器,我们看到此时的响应头中并没有”Last-Modified”头,并且响应的状态码为304。再刷新下浏览器,我们看到浏览器窗口中显示的字符串中的时间值没变,证明了此时浏览器加载的是缓存的数据,并不是服务器端传回来的新数据。还有”If-Modified-Since”的时间值也没有改变。
演示完了http协议中与缓存相关的头部,第一部分源码解读的任务内容结束~
相关文章推荐
- Atlas快速入门之实战Atlas
- 会话Bean入门实战
- MyEclipse 6 实战开发讲解视频入门 0: 下载 安装 运行 HelloWorld
- 『Joomla!入门实用宝典』第十一章.书籍支持站建站实战 完成
- Groovy轻松入门——Grails实战基础篇(1)
- 微信小程序从入门到项目实战
- 『Joomla!入门实战宝典』内容简介
- MyEclipse 6 实战开发讲解视频入门 0: 下载 安装 运行 HelloWorld
- java web后台开发SSM框架(Spring+SpringMVC+MyBaitis)搭建与优化
- 【转】Atlas快速入门之实战Atlas。
- JavaRMI入门实战
- DbUnit入门实战
- MyEclipse 6 实战开发讲解视频入门 0: 下载 安装 运行 HelloWorld
- MyEclipse 6 实战开发讲解视频入门 0: 下载 安装 运行 HelloWorld
- J2ME蓝牙实战入门
- J2ME蓝牙实战入门
- JavaRMI入门实战
- Atlas快速入门之实战Atlas
- ORACLE存储过程实战之一:入门
- 第三章 3.2 文件共享入门简介 Introduction of File Sharing System