前面我们编写Servlet是通过实现Servlet接口来编写的,但是,使用这种方法,则必须要实现Servlet接口中定义的所有的方法,但是其中我们主要使用的还是service方法,剩余的四种方法中没有任何东西也要去实现,并且还需要自己手动的维护ServletConfig这个对象的引用。因此,这样去实现Servlet是比较麻烦的。
GenericServlet抽象类的出现很好的解决了这个问题。本着尽可能使代码简洁的原则,GenericServlet实现了Servlet和ServletConfig接口,下面是GenericServlet抽象类的具体代码:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable { private static final String LSTRING_FILE = "javax.servlet.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings"); private transient ServletConfig config; public GenericServlet() { } public void destroy() { } public String getInitParameter(String name) { ServletConfig sc = this.getServletConfig(); if (sc == null) { throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized")); } else { return sc.getInitParameter(name); } } public Enumeration<String> getInitParameterNames() { ServletConfig sc = this.getServletConfig(); if (sc == null) { throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized")); } else { return sc.getInitParameterNames(); } } public ServletConfig getServletConfig() { return this.config; } public ServletContext getServletContext() { ServletConfig sc = this.getServletConfig(); if (sc == null) { throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized")); } else { return sc.getServletContext(); } } public String getServletInfo() { return ""; } public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException { } public void log(String msg) { this.getServletContext().log(this.getServletName() + ": " + msg); } public void log(String message, Throwable t) { this.getServletContext().log(this.getServletName() + ": " + message, t); } public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; public String getServletName() { ServletConfig sc = this.getServletConfig(); if (sc == null) { throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized")); } else { return sc.getServletName(); } } }GenericServlet抽象类相比于直接实现Servlet接口,有以下几个好处: 1.为Servlet接口中的所有方法提供了默认的实现,则程序员需要什么就直接改什么,不再需要把所有的方法都自己实现了。 2.提供方法,包围ServletConfig对象中的方法。 3.将init( )方法中的ServletConfig参数赋给了一个内部的ServletConfig引用从而来保存ServletConfig对象,不需要程序员自己去维护ServletConfig了。
虽然GenricServlet是对Servlet一个很好的加强,但是也不经常用,因为他不像HttpServlet那么高级。HttpServlet才是主角,在现实的应用程序中被广泛使用。 之所以所HttpServlet要比GenericServlet强大,其实也是有道理的。HttpServlet是由GenericServlet抽象类扩展而来的,HttpServlet抽象类的声明如下所示:
public abstract class HttpServlet extends GenericServlet implements SerializableHttpServlet抽象类是继承于GenericServlet抽象类而来的。
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 var6) { throw new ServletException("non-HTTP request or response"); } this.service(request, response); }HttpServlet中的service方法把接收到的ServletRequsest类型的对象转换成了HttpServletRequest类型的对象,把ServletResponse类型的对象转换成了HttpServletResponse类型的对象。之所以能够这样强制的转换,是因为在调用Servlet的Service方法时,Servlet容器总会传入一个HttpServletRequest对象和HttpServletResponse对象,预备使用HTTP。因此,转换类型当然不会出错了。转换之后,service方法把两个转换后的对象传入了另一个service方法,那么我们再来看看这个方法是如何实现的:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long lastModified; if (method.equals("GET")) { lastModified = this.getLastModified(req); if (lastModified == -1L) { this.doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader("If-Modified-Since"); if (ifModifiedSince < lastModified) { this.maybeSetLastModified(resp, lastModified); this.doGet(req, resp); } else { resp.setStatus(304); } } } else if (method.equals("HEAD")) { lastModified = this.getLastModified(req); this.maybeSetLastModified(resp, lastModified); this.doHead(req, resp); } else if (method.equals("POST")) { this.doPost(req, resp); } else if (method.equals("PUT")) { this.doPut(req, resp); } else if (method.equals("DELETE")) { this.doDelete(req, resp); } else if (method.equals("OPTIONS")) { this.doOptions(req, resp); } else if (method.equals("TRACE")) { this.doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } }这个service方法的参数是HttpServletRequest对象和HttpServletResponse对象,刚好接收了上一个service方法传过来的两个对象。 接下来我们再看看service方法是如何工作的,我们会发现在service方法中还是没有任何的服务逻辑,但是却在解析HttpServletRequest中的方法参数,并调用以下方法之一:doGet,doPost,doHead,doPut,doTrace,doOptions和doDelete。
这7种方法中,每一种方法都表示一个Http方法。doGet和doPost是最常用的。所以,如果我们需要实现具体的服务逻辑,不再需要覆盖service方法了,只需要覆盖doGet或者doPost就好了。
总之,HttpServlet有两个特性是GenericServlet所不具备的:
不用覆盖service方法,而是覆盖doGet或者doPost方法。(在少数情况,还会覆盖其他的5个方法。)使用的是HttpServletRequest和HttpServletResponse对象。(开发中写Servlet最常用的就是继承HttpServlet抽象类,复写doGet或doPost方法)
request对象继承体系结构:
Servlet容器对于接受到的每一个Http请求,都会创建一个ServletRequest对象,并把这个对象传递给Servlet的Sevice( )方法。其中,ServletRequest对象内封装了关于这个请求的许多详细信息。
public interface ServletRequest { int getContentLength();//返回请求主体的字节数 String getContentType();//返回主体的MIME类型 String getParameter(String var1);//返回请求参数的值 ,ServletRequest中最常用的方法,可用于获取查询字符串的值 }HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法我们可以分别获得HTTP请求的请求行,请求头和请求体。
String getMethod():获取请求方式 String getContextPath():获取虚拟目录 String getServletPath():获取Servlet路径 getRequestURL():返回客户端发出请求时的完整URL。 String getProtocol():获取协议及版本 getRequestURI(): 返回请求行中的资源名部分。 getQueryString ():返回请求行中的参数部分。 getPathInfo():方法返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以“/”开头。 getRemoteAddr():方法返回发出请求的客户机的IP地址。 getRemoteHost():方法返回发出请求的客户机的完整主机名。 getRemotePort():方法返回客户机所使用的网络端口号。 getLocalAddr():方法返回WEB服务器的IP地址。 getLocalName():方法返回WEB服务器的主机名。 案例:
package cn.zhukun.web.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/demo3") public class ServletDemo3 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /** * 1.获得客户机信息 */ String requestUrl = request.getRequestURL().toString();//得到请求的URL地址 String requestUri = request.getRequestURI();//得到请求的资源 String queryString = request.getQueryString();//得到请求的URL地址中附带的参数 String remoteAddr = request.getRemoteAddr();//得到来访者的IP地址 String remoteHost = request.getRemoteHost(); int remotePort = request.getRemotePort(); String remoteUser = request.getRemoteUser(); String method = request.getMethod();//得到请求URL地址时使用的方法 String pathInfo = request.getPathInfo(); String localAddr = request.getLocalAddr();//获取WEB服务器的IP地址 String localName = request.getLocalName();//获取WEB服务器的主机名 response.setCharacterEncoding("UTF-8");//设置将字符以"UTF-8"编码输出到客户端浏览器 //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码 response.setHeader("content-type", "text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.write("获取到的客户机信息如下:"); out.write("<hr/>"); out.write("请求的URL地址:"+requestUrl); out.write("<br/>"); out.write("请求的资源:"+requestUri); out.write("<br/>"); out.write("请求的URL地址中附带的参数:"+queryString); out.write("<br/>"); out.write("来访者的IP地址:"+remoteAddr); out.write("<br/>"); out.write("来访者的主机名:"+remoteHost); out.write("<br/>"); out.write("使用的端口号:"+remotePort); out.write("<br/>"); out.write("remoteUser:"+remoteUser); out.write("<br/>"); out.write("请求使用的方法:"+method); out.write("<br/>"); out.write("pathInfo:"+pathInfo); out.write("<br/>"); out.write("localAddr:"+localAddr); out.write("<br/>"); out.write("localName:"+localName); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }getHeader(string name):String getHeaders(String name):Enumeration getHeaderNames():
package cn.zhukun.web.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; @WebServlet("/demo3") public class ServletDemo3 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8");//设置将字符以"UTF-8"编码输出到客户端浏览器 //通过设置响应头控制浏览器以UTF-8的编码显示数据 response.setHeader("content-type", "text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); Enumeration<String> reqHeadInfos = request.getHeaderNames();//获取所有的请求头 out.write("获取到的客户端所有的请求头信息如下:"); out.write("<hr/>"); while (reqHeadInfos.hasMoreElements()) { String headName = (String) reqHeadInfos.nextElement(); String headValue = request.getHeader(headName);//根据请求头的名字获取对应的请求头的值 out.write(headName+":"+headValue); out.write("<br/>"); } out.write("<br/>"); out.write("获取到的客户端Accept-Encoding请求头的值:"); out.write("<hr/>"); String value = request.getHeader("Accept-Encoding");//获取Accept-Encoding请求头对应的值 out.write(value); Enumeration<String> e = request.getHeaders("Accept-Encoding"); while (e.hasMoreElements()) { String string = (String) e.nextElement(); System.out.println(string); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }请求体:只有POST请求方式才有请求体,在请求体中封装了POST请求的请求参数。request对象将请求体的数据封装成流
获取流对象 BufferedReader getReader():获取字符输入流,只能操作字符数据 ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据再从流对象中拿数据案例:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <form action="demo4" method="post"> 用户名(文本框):<input type="text" name="username" value="请输入用户名"><br> 密 码(密码框):<input type="password" name="userpass" value="请输入密码"><br> <input type="submit" value="提交(提交按钮)"> </form> </body> </html> package cn.zhukun.web.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; @WebServlet("/demo4") public class ServletDemo4 extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取请求消息体--请求参数 //1、获取字符流 BufferedReader br = request.getReader(); String line = null; while ((line=br.readLine())!=null) { System.out.println(line); } } }
getParameter(String):根据参数名称获取参数值 ——常用 getParameterValues(String name):根据参数名称获取参数值的数组 ——常用 getParameterNames():获取所有请求的参数名称——不常用 getParameterMap():获取所有参数的map集合——编写框架时常用
案例: get方式
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <form action="demo3" method="get"> 用户名(文本框):<input type="text" name="username" value="请输入用户名"><br> 密 码(密码框):<input type="password" name="userpass" value="请输入密码"><br> <input type="submit" value="提交(提交按钮)"> </form> </body> </html> package cn.zhukun.web.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; @WebServlet("/demo3") public class ServletDemo3 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username");//获取填写的用户名 String userpass = request.getParameter("userpass");//获取填写的密码 response.setContentType("text/html;charset=UTF-8");//设置客户端浏览器以UTF-8编码解析数据 PrintWriter out = response.getWriter(); out.write("用户名:"+username); out.write("<br>"); out.write("密码:"+userpass); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }post方式:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <form action="demo3" method="post"> 用户名(文本框):<input type="text" name="username" value="请输入用户名"><br> 密 码(密码框):<input type="password" name="userpass" value="请输入密码"><br> <input type="submit" value="提交(提交按钮)"> </form> </body> </html> package cn.zhukun.web.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/demo3") public class ServletDemo3 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username");//获取填写的用户名 String userpass = request.getParameter("userpass");//获取填写的密码 response.setContentType("text/html;charset=UTF-8");//设置客户端浏览器以UTF-8编码解析数据 PrintWriter out = response.getWriter(); out.write("用户名:"+username); out.write("<br>"); out.write("密码:"+userpass); } }
解决post提交方式的乱码:
request.setCharacterEncoding("UTF-8");解决get提交的方式的乱码:(tomcat 8 已经将get方式乱码问题解决了)
parameter = newString(parameter.getbytes("iso8859-1"),"utf-8");请求转发:一种在服务器内部的资源跳转方式 步骤:
通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)使用RequestDispatcher对象来进行转发:forward(ServletRequest request, ServletResponse response)案例:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <form action="demo4" method="post"> 用户名(文本框):<input type="text" name="username" value="请输入用户名"><br> 密 码(密码框):<input type="password" name="userpass" value="请输入密码"><br> 爱好:<input type="checkbox" name="hobby" value="game">游戏 <input type="checkbox" name="hobby" value="study">学习 <input type="checkbox" name="hobby" value="running">跑步 <input type="submit" value="提交(提交按钮)"> </form> </body> </html> package cn.zhukun.web.servlet; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/demo4") public class ServletDemo4 extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo4被访问了"); response.setHeader("Content-Type","text/html;charset=UTF-8"); response.setContentType("text/html;charset=UTF-8");//设置客户端浏览器以UTF-8编码解析数据 PrintWriter out = response.getWriter(); //转发资源到demo3 RequestDispatcher requestDispatcher = request.getRequestDispatcher("/demo3"); requestDispatcher.forward(request,response); } } package cn.zhukun.web.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/demo3") public class ServletDemo3 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo3被访问了"); request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username");//获取填写的用户名 String userpass = request.getParameter("userpass");//获取填写的密码 //设置HttpServletResponse使用utf-8编码 response.setCharacterEncoding("utf-8"); //通知浏览器使用utf-8编码 response.setHeader("Content-Type","text/html;charset=UTF-8"); response.setContentType("text/html;charset=UTF-8");//设置客户端浏览器以UTF-8编码解析数据 PrintWriter out = response.getWriter(); out.write("用户名:"+username); out.write("<br>"); out.write("密码:"+userpass); out.write("<br>"); out.write("爱好:"); String[] hobbies = request.getParameterValues("hobby"); for (String hobby: hobbies) { out.write(hobby); out.write("  "); } } }表单提交到demo4,demo4被访问了,demo4将资源跳转到demo3,将request对象和response对象转发给demo3,demo3被访问,但浏览器地址栏路径还是demo4。 特点:
浏览器地址栏路径不发生变化只能转发到当前服务器内部资源中。转发是一次请求域对象:一个有作用范围的对象,可以在范围内共享数据 request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据 方法:
void setAttribute(String name,Object obj):存储数据Object getAttitude(String name):通过键获取值void removeAttribute(String name):通过键移除键值对 package cn.zhukun.web.servlet; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/demo4") public class ServletDemo4 extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo4被访问了"); //存储数据到request域中 request.setAttribute("msg","hello"); //转发资源到demo3 RequestDispatcher requestDispatcher = request.getRequestDispatcher("/demo2"); requestDispatcher.forward(request,response); } } package cn.zhukun.web.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/demo2") public class ServletDemo2 extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo2被访问了"); //获取数据 Object msg = request.getAttribute("msg"); System.out.println(msg); } }demo4中在request域中保存的数据“hello”在资源跳转到demo3后被共享到了demo3
javax.servlet.ServletResponse接口表示一个Servlet响应,在调用Servlet的Service( )方法前,Servlet容器会先创建一个ServletResponse对象,并把它作为第二个参数传给Service( )方法。ServletResponse隐藏了向浏览器发送响应的复杂过程。
public interface ServletResponse { String getCharacterEncoding(); String getContentType(); ServletOutputStream getOutputStream() throws IOException; PrintWriter getWriter() throws IOException; void setCharacterEncoding(String var1); void setContentLength(int var1); void setContentType(String var1); void setBufferSize(int var1); int getBufferSize(); void flushBuffer() throws IOException; void resetBuffer(); boolean isCommitted(); void reset(); void setLocale(Locale var1); Locale getLocale(); }其中的getWriter方法,它返回了一个可以向客户端发送文本的的Java.io.PrintWriter对象。默认情况下,PrintWriter对象使用ISO-8859-1编码(该编码在输入中文时会发生乱码)。 在向客户端发送响应时,大多数都是使用该对象向客户端发送HTML。还有一个方法也可以用来向浏览器发送数据,它就是getOutputStream,从名字就可以看出这是一个二进制流对象,因此这个方法是用来发送二进制数据的。 在发送任何HTML之前,应该先调用setContentType()方法,设置响应的内容类型,并将“text/html”作为一个参数传入,这是在告诉浏览器响应的内容类型为HTML,需要以HTML的方法解释响应内容而不是普通的文本,或者也可以加上“charset=UTF-8”改变响应的编码方式以防止发生中文乱码现象。
HttpServletResponse接口,它继承自ServletResponse接口,专门用来封装HTTP响应消息。 由于HTTP请求消息分为状态行,响应消息头,响应消息体三部分,因此,在HttpServletResponse接口中定义了向客户端发送响应状态码,响应消息头,响应消息体的方法
HttpServletResponse内封装的响应
通过Response设置响应 void addCookie(Cookie var1);//给这个响应添加一个cookie void addHeader(String var1, String var2);//给这个请求添加一个响应头 void sendRedirect(String var1) throws IOException;//发送一条响应码,讲浏览器跳转到指定的位置 void setStatus(int var1);//设置响应行的状态码
PrintWriter getWriter() 获得字符流,通过字符流的write(String s)方法可以将字符串设置到response 缓冲区中,随后Tomcat会将response缓冲区中的内容组装成Http响应返回给浏览器端。 ServletOutputStream getOutputStream() 获得字节流,通过该字节流的write(byte[] bytes)可以向response缓冲区中写入字节,再由Tomcat服务器将字节内容组成Http响应返回给浏览器。
Response的乱码问题: 原因:response缓冲区的默认编码是iso8859-1,此码表中没有中文,所以需要更改response的编码方式
两种方法: 第一种:
//设置HttpServletResponse使用utf-8编码 response.setCharacterEncoding("utf-8"); //通知浏览器使用utf-8编码 response.setHeader("Content-Type","text/html;charset=UTF-8");第二种:
//包含第一种方式的两种功能 response.setContentType("text/html;charset=UTF-8");