不少小伙伴对Java Web后台开发感兴趣,可是又苦于入门,一是书上和网络上的其余资料大可能是千篇一概,要么就是偏知识,大量篇幅介绍Servlet相关内容,看不进去,要么太浅,看完又以为好像并无学到什么。因此从你们的需求出发,内容主要包括如下四个部分:html
须要的小伙伴能够下载FirstJavaWeb工程来对照着看。java
Servlet是运行在服务器端的程序,用于处理及响应客户端的请求,它是个特殊的Java类,这个Java类必须继承javax.servlet.http.HttpServlet,HttpServlet类提供了不一样的方法用于处理客户端的请求。 web
方法 | 描述 |
---|---|
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 |
根据HttpServlet service方法的处理逻辑,HttpServlet目前只可响应客户端的GET,HEAD,POST,PUT,DELETE,OPTIONS,TRACE请求。spring
http请求 | 描述 |
---|---|
GET | 获取服务器上某一资源 |
HEAD | HEAD和GET本质是同样的,区别在于HEAD请求的响应不包含响应实体,而仅仅包含响应消息头 |
POST | 向服务器提交数据 |
PUT | PUT和POST极为类似,PUT一般指定了资源的存放位置,向指定资源位置上传其最新内容 |
DELETE | 删除服务器上某一个资源 |
OPTIONS | 获取服务器针对特定资源所支持的HTTP请求方法,请求头等 |
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就会明白。apache
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 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中运行,浏览器查看,此时请求头和响应头中并无任何特殊的头部。 api
解注释第一个重写的getLastModified方法并运行,这个getLastModified方法每次返回一个新的时间值,而且老是大于请求头”If-Modified-Since”的时间值。此次咱们看到响应头中多了“Last-Modified”字段,刷新下浏览器,此时请求头中多了”If-Modified-Since”,而且响应头中依然包含“Last-Modified”头,响应状态码为200,浏览器窗口中显示的字符串的时间值也一直在随着时间推动。 浏览器
注释这个getLastModified方法,并解注释第二个重写的getLastModified方法,这个方法每次返回的值为请求头”If-Modified-Since”的值。刷新下浏览器,咱们看到此时的响应头中并无”Last-Modified”头,而且响应的状态码为304。再刷新下浏览器,咱们看到浏览器窗口中显示的字符串中的时间值没变,证实了此时浏览器加载的是缓存的数据,并非服务器端传回来的新数据。还有”If-Modified-Since”的时间值也没有改变。 缓存
打开Eclipse,注意安装用于Web开发的Java EE版本的Eclipse,将下载好的Apache Tomcat® [Tomcat是一个轻量级免费开源的Servlet容器,可以运行咱们的Java Web应用] 配置进来。File->New->Other。点击Server->next。相关工具你们也能够直接去个人百度网盘下载,Eclipse两个版本均可以用,zip是luna,exe是最新的neon,本身安装。tomcat
选择相应的Tomcat版本->next,找到Tomcat安装路径,Finish,一个server就给配好了。
接下来咱们建立一个Java Dynamic Web Project。File->New->Dynamic Web Project,如若没找到Dynamic Web Project菜单,则File->New->other,在Wizards输入要搜索的项。在弹出的New Dynamic Web Project窗口输入你的Project name,一路next,在点击最后Finish按钮以前勾选Generate web.xml deployment discriptor。Servlet 3.0以后支持注解的配置方式,因此生成web.xml成为可选项,这里勾选上,咱们一下子会演示使用注解和xml文件两种方式配置Servlet。
看下这个project的Web工程目录结构,就是下面这个WebContent文件夹,这个文件夹下对应着project部署到容器中时的上下文根路径,根目录,就是咱们在浏览器输入根URL所访问的服务器地址,其余资源地址都是相对于这个地址的相对路径。
再来看下这个project部署到容器中时,它的目录结构。在这个project上右键->Run As->Run on Server。根据这个路径找到eclipse中运行的tomcat的发布文件夹%you eclipse workplace%\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps
找到咱们的project目录,进去后发现其下内容与eclipse中project的WebContent下的内容彻底同样,验证了咱们以前的说法,小伙伴想问why?回到Eclipse中,project上右键->Properties,找到Deployment Assembly项,Deployment Assembly项为web工程指定在发布的时候要部署到容器中的部件。咱们看到WebContent文件夹对应的就是部署后的项目的上下文根路径,就这么个缘由。
咱们能够更改这个目录为咱们想要的目录,将Deployment Assembly中的WebContent Remove,新建立一个webapp目录,并照着WebContent目录下的文件,文件夹新建一份,以后把它配置为Deploy Path。此时咱们的项目结构是这样的:
我又新加了一个index.jsp,内容很简单,显示”hello world!”,运行一下,成功,彻底是本身掌控。
对于使用Eclipse的初学者来讲,知道这个至关重要,好比当使用maven spring的时候,咱们的项目第一次运行后可能会起不起来,出现第三方库的ClassNotFoundException,这个时候通常去检查Deployment Assembly中是否将Maven Dependencies库加到了WEB-INF/lib下面,很快就解决了。
回过头看下WebContent目录下的内容,META-INF,看名字就能明白,存放一些meta information,这个目录下的文件应该都是build工具来生成的。WEB-INF是一个特殊的文件夹,容器会保护此文件下的内容不被浏览器访问,就是说咱们直接在浏览器经过URL的方式是访问不到的,因此咱们通常会将图片文件夹,JS,CSS文件夹,以及咱们的JSP页面放到WEB项目的根目录下,而将配置文件放到WEB-INF下面。WEB-INF的lib文件夹存放本应用所依赖的第三方包。
web.xml文件用来配置Web应用的组件。咱们本身的Servlet要在文件中配置后才能访问,不过Servlet 3.0以后也能够不用配置文件而直接使用注解的方式配置servlet,两种方式各有利弊,注解散落在个各种中,很差管理,而使用配置文件都集中在一处配置,一目了然,可是随着内容的增长会略显臃肿。<welcome-file-list/>元素配置Web应用的首页列表。默认生成的配置信息指定该Web应用的首页依次是index.html,index.htm,index.jsp等,当index.html不存在的时候,则由index.htm页面来充当首页,以此类推,当容器没有找到<welcome-file-list/>中的页面时,咱们会看到404 error report,不要被这种页面吓到,很好解决,要么检查你的URL路径是否正确,要么新建一个URL所请求的资源,这里咱们重写一个首页文件就OK了。
Servlet建立
在src文件加上右键->New->Class->输入Class Name->点击Browse->搜索HttpServlet->点击OK->点击Finish,一个HttpServlet类就建立完成了。
使用注解的方式配置Servlet,只须要在咱们建立的类上加上注解@WebServlet(urlPatterns = ("you path"))
便可,直接运行,页面显示405状态码,HttpServlet中doGet方法的默认实现,这就OK了,你也能够重写doGet方法,加入本身的处理逻辑。
使用文件配置的方式,打开web.xml,加入以下配置便可:
<servlet> <servlet-name>exampleOne</servlet-name> <servlet-class>example.ExampleTwoServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>exampleOne</servlet-name> <url-pattern>/example/two</url-pattern> </servlet-mapping>
<servlet>配置Servlet基本信息,<servlet-name>的值能够配置成任何你喜欢的名字,只要和<servlet-mapping>中的name先后对应上就行,<servlet-class>指定Servlet类的彻底限定名。<servlet-mapping>配置Servlet的URL路径,<url-pattern>指定Servlet的URL路径。
这一节的目的是经过Tomcat中的Examples了解相关类的应用。点击Examples出现404,是由于安装Tomcat时没有勾选examples的选项,examples和host-manager同样,是Tomcat本身写的web应用而已,一样部署在Tomcat中,咱们在%CATALINA_HOME%\webapps
下都能找到,一样的也能够从别的途径得到examples代码,放到webapps下面就能够访问了。
Servlets examples、JSP Examples、WebSocket Examples,咱们只过了Servlets examples,例子都很适合初学者阅读。我把源码中从上到下涉及到的全部类及方法总结出来,本身对照着去看代码。
ResourceBundle(可以方便读取.properties配置文件)
方法 | 描述 |
---|---|
ResourceBundle.getBundle(String baseName, Locale locale) | 返回具备给定基本名称和语言环境的ResourceBundle对象 |
String getString(String key) | 今后ResourceBundle资源包中获取给定键的字符串 |
HttpServletRequest(获取请求头和请求参数)
方法 | 描述 |
---|---|
Locale getLocale() | 获取客户端所使用的本地语言 |
String getMethod() | 返回http请求的方法名称 |
String getRequestURI() | a String containing the part of the URL from the protocol name up to the query string () |
String getProtocol() | 返回协议名称和协议版本号 |
String getMethod() | 返回http请求的方法名称 |
String getPathInfo() | 返回请求URL中Servlet路径以后查询字符串以前的额外路径信息。若是没有额外路径信息,返回null |
String getRemoteAddr() | 获取发送请求的客户端的IP地址 |
Object getAttribute(String name) | 获取指定属性的属性值 |
Enumeration<String> getHeaderNames() | 获取全部请求参数的名称 |
String getHeader(String name) | 获取指定请求头的值 |
String getParameter(String name) | 获取请求参数的值 |
Cookie[] getCookies() | 获取本次请求携带地全部cookies |
HttpSession getSession(boolean create) | 返回与当前请求相关联的HttpSession对象,create为true时,若是当前会话无效,则建立,不然返回null |
Servlet 3.1 API文档对String getRequestURI()方法的描述是a String containing the part of the URL from the protocol name up to the query string (),翻译过来是URL中的协议以后到查询字符串以前的部分,但实际上获得是域名以后到查询字符串的部分,理解有出入,故不敢随便翻译。看下它的底层实现return request.getServletContext();
,实际是就是获取了ServletContextPath。
String getPathInfo()方法返回请求URL中Servlet路径以后查询字符串以前的额外路径信息。若是没有额外路径信息,返回null。查询字符串,客户端发起的GET请求若是携带参数,如http://localhost:8088/examples/servlets/servlet/RequestInfoExample?param1=value1¶m2=value2
,的形式?号后面的字符串会被解析为查询字符串,格式通常是?param1=value1¶m2=value2。而String getPathInfo()返回servlet path和查询字符串之间的路径,如http://localhost:8088/examples/servlets/servlet/RequestInfoExample/user/xiaoming?param1=value1¶m2=value2
,咱们配置的servlet path是/servlets/servlet/RequestInfoExample,那么extra path就是/user/xiaoming,不要搞混了。
HttpServletResponse(表明服务器对客户端的响应)
方法 | 描述 |
---|---|
void setContentType(String type) | 设置发送到客户端的响应的内容类型 |
void setCharacterEncoding(String charset) | 设置发送到客户端的响应的字符编码(MIME字符集),例如,设置为UTF-8 |
PrintWriter getWriter() | 获取页面输出流 |
void addCookie(Cookie cookie) | 设置cookie |
Cookie(浏览器端跟踪用户会话状态)
方法 | 描述 |
---|---|
String getName() | 获取cookie的名字 |
String getValue() | 获取cookie的值 |
HttpSession(服务器端跟踪用户会话状态)
方法 | 描述 |
---|---|
long getCreationTime() | 获取session的建立时间 |
long getLastAccessedTime() | 获取上一次访问当前会话session的时间 |
String getId() | 获取当前会话ID |
void setAttribute(String name, Object value) | 设置session范围内属性 |
Enumeration<String>getAttributeNames() | 获取全部属性的名字 |
Object getAttribute(String name) | 获取指定属性名的value |
Session和cookie区别(摘自网络):
In Template pattern, an abstract class exposes defined way(s)/template(s) to execute its methods. Its subclasses can override the method implementation as per need but the invocation is to be in the same way as defined by an abstract class. This pattern comes under behavior pattern category.[在模板模式中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类能够按须要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。]
在Servlet中,模板方法由service()方法担任,子类按需重写do method 方法,不过须要注意一点,更多时候咱们会将Template Method定义为final方法,防止子类重写父类模板。