Java打卡:第98天
javaWeb — Servlet
Java EE
Servlet
昨天已经分享了欢迎页面和一般Servlet的书写,GenericServlet;使用模板方法模式和适配器模式
其实系统已经定义好一个之前的实现的GenericServlet;可以直接使用import javax.servlet.GenericServlet;
可以看一下源码
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { private static final long serialVersionUID = 1L; private transient ServletConfig config; public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } /** * A convenience method which can be overridden so that there's no need to * call <code>super.init(config)</code>. * <p> * Instead of overriding {@link #init(ServletConfig)}, simply override this * method and it will be called by * <code>GenericServlet.init(ServletConfig config)</code>. The * <code>ServletConfig</code> object can still be retrieved via * {@link #getServletConfig}. * * @exception ServletException * if an exception occurs that interrupts the servlet's * normal operation */ public void init() throws ServletException { // NOOP by default }
实现的思路和之前写的是相同的,使用了模板方法模式
这里假如有一个需求: 验证请求方式;表单提交的方式为POST;如果直接通过地址栏访问则拦截
分析:要想拦截,那就要获得请求提交的方式method;这里的service方法中有Request类型的,这里的req变量没有getMethod方法;==但是Request下面的子接口HTTPrequest有;
所以这里使用下转型可以获得对象【java其实不支持真正的下转型,只有超类对象是由子类对象上转型得到,才可以进行下转型】 因为通信支持的是Http协议,所以这里就可以想到method应该与Http相关。
package cfeng; import java.io.IOException; import javax.servlet.*; import javax.servlet.http.*; @SuppressWarnings("serial") public class HttpServlet extends GenericServlet { @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { //这里使用下转型 HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; //获取提交的method String method = request.getMethod(); //操作method 使用equals方法一般将常量放在前面,这里可以避免空指针异常 if("POST".equals(method)) { doPost(); }else if("GET".equals(method)) { doGET(); } } public void doGET() System.out.println("非法操作,不允许访问"); } public void doPost() { System.out.println("合法操作,允许访问"); } } 12月 19, 2021 5:30:52 下午 org.apache.catalina.loader.WebappClassLoaderBase checkThreadLocalsForLeaks 警告: 在Java 9上运行时,需要在JVM命令行参数中添加“-add opens=Java.base/Java.lang=ALL-UNNAMED”,以启用线程本地内存泄漏检测。或者,可以通过禁用ThreadLocal内存泄漏检测来抑制此警告。 12月 19, 2021 5:30:52 下午 org.apache.catalina.core.StandardContext reload 信息: 已完成重新加载名为/proj1的上下文 合法操作,允许访问
这样我们就实现了对于请求的处理,可以识别是正常的请求或者非法的请求
那么就有疑问了,操作请求是很常见的一种操作,会比较频繁使用,系统是否实现了这个类呢?就像GenericServlet一样,答案是肯定的;
当需要对请求进行操作的时候,可以不继承GenericServlet,可以直接继承HttpServlet
//这里可以看一下HttpServlet的源码 public abstract class HttpServlet extends GenericServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String msg = lStrings.getString("http.method_get_not_supported"); sendMethodNotAllowed(req, resp, msg); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String msg = lStrings.getString("http.method_post_not_supported"); sendMethodNotAllowed(req, resp, msg); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); //获取提交method if (method.equals(METHOD_GET)) { //判断method 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); } @Override //这里重写自GenericServlet的方法 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(lStrings.getString("http.non_http")); } service(request, response); } }
可以看到这里的实现方法和上面的思路基本是一致的,倒时候需要使用的时候就继承该类;并且重写doGet和doPost方法
所以之后实现的时候就不要再继承GenericServlet,而是直接继承HttpServlet;操作就是重写两个方法
package cfeng; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.*; public class loginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); System.out.println("合法操作"); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); doPost(req,resp); //既支持POST,也支持GET } }
这样当表单POST提交的时候就会执行该方法
经过前面的分析,以后创建Servlet的时候,就不用再继承Servlet接口或者GenericServlet抽象类了,可以直接继承HttpServlet类
并且可以使用Eclipse快捷操作,直接new的时候就创建Servlet;而不是创建Class;这样的好处就是不用手动配置
Ctrl + N 新建输入Servlet;输入名称,next;选择url-pattern;next选择方法就可快捷创建Servlet
这样就可以直接创建一个Servlet类
package cfeng; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class Some */ @WebServlet("/Some") public class Some extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public Some() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub response.getWriter().append("Served at: ").append(request.getContextPath()); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }
像这样之后就可以访问了
package cfeng; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(description = "this is just a test class", urlPatterns = { "/TestServlet" }) public class TestServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("hello"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
注意:这里的Servlet配置没有再web.xml中配置,而是直接使用注解websocket配置,推荐使用这种方式,更加方便动态
@WebServlet(description = “this is just a test class”, urlPatterns = { “/TestServlet” })
在Servlet类的前面加上这个,和在web.xml中配置的效果相同
HttpServletRequest是SUN指定的Servlet规范,表示请求,父接口是ServletRequest;HTTP请求协议的完整的内容都要封装request对象中;Tomcat对于该接口进行了实现;之前使用抓包工具抓取的请求内容就是封装在HttpServletRequest中;
这里比如
用HttpWatch来进行观察,当使用表单POST提交的请求的内容
POST /Test/TestServlet HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://localhost:8080/Test/ Connection: keep-alive Content-Type: application/x-www-form-urlencoded Content-Length: 23 user=%E5%BC%A0%E4%B8%89
这里出现了请求的中文乱码问题,稍后会进行分析;HttpServletRequest就是将上面的内容封装起来,方便直接进行操作。
request = org.apache.catalina.connector.RequestFacade@57a3f54a
这个对象就是Tomcat调用的时候对于HttpServletRequest接口的实现使用的就是该实现类的对象
当请求到达服务器的时候Tomcat服务器就会解析这个请求,按照HTTP协议解析之后就会封装成一个HttpServletRequest对象,这样就初始化完成。请求对象是由服务器创建。
当服务器向客户端响应结束后,HttpServletRequest实例对象由服务器销毁
一次请求对应一个请求对象
,也就是对应一个Request对象;另外的对应另外一个对象,与之前的请求对象没有任何关系,所以Request对象的生命周期很短暂
HTTP是无状态【数据】协议,不同的请求之间是没有关系的
HttpServletRequest的父接口ServletRequest中就有关于请求参数的方法比如
String
, or null
if the parameter does not exist.这里的请求参数是指的是提交表单的时候提交的参数,比如表单中的name就是,value就是参数的值
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("hello"); System.out.println("request = " + request); String user = request.getParameter("user"); System.out.println("user = " + user); } 已完成重新加载名为/Test的上下文 hello request = org.apache.catalina.connector.RequestFacade@6ed310ae user = hh
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("hello"); System.out.println("request = " + request); Enumeration<String> parameterNames = request.getParameterNames(); //遍历枚举类型 while(parameterNames.hasMoreElements()) { String name = parameterNames.nextElement(); System.out.println(request.getParameter(name)); } } 还是可以输出,和上面的结果相同的
HttpServletRequest对于请求所携带的参数是以Map的形式接收的,并且这个Map的key是String,value是String[]类型的,为什么是数组类型,而不是String类型?
表单中的复选框的类型比如checkbox,这里的value有多个值
<form action="/login" method="get"> 爱好: <input type="checkbox" name="hobby" value="soccer"/>足球 <input type="checkbox" name="hobby" value="basketball"/>篮球 <br> </form>
这里的复选框就有多个值,那么获取的时候如果是一个String类型,是不能正常执行的,这里是一个Map类型的
user=zhangsan&hobby=soccer&hobby=basketball&hobby=tennis //上面是请求的请求报文 hello request = org.apache.catalina.connector.RequestFacade@6ed310ae user=zhangsan hobby=soccer
这里看到普通的根据name获取的参数值就是hobby=soccer;就是使用getParameter输出的只是一个value
getParameter本质上等同于getParameterValues(“name”)[0];
所以这里要将所有的value给取出来就要使用Map;之后使用getParameterValues;如果没有复选框,就使用getParameter就可以了
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("hello"); System.out.println("request = " + request); Map<String, String[]> map = request.getParameterMap(); for (String key : map.keySet()) {//keySet就是Map的键的集合 String[] values = request.getParameterValues(key); //获取map的value集合 System.out.print(key + " = "); for(String value : values) { System.out.print(value + " "); } System.out.print("\n"); } } user = zhangsan hobby = soccer basketball tennis
这样子就可以获取复选框这种有多个值的name
dispatcher 调度程序,分配器 wrapper 包装
之前分析config的时候就提到了初始化参数和域属性;那里的域属性是所有的Servlet共享,就是一个应用程序对应一个应用程序;complex
在HttpRequest中也有域属性空间,用来存放有名称的数据,该数据只是在当前的request的请求中可以进行访问;只要请求还存在,就可以访问域属性空间的数据; 所以域属性的作用就是用来传递数据的
和之前的complex相同,直接get,set就可
ServletRequest的一个方法是getRequestDispatch方法 就是获得一个请求的分配器;就是正常来说一个请求提交给一个Servlet,可能完成不了任务;这个时候就要通过dispatcher转发给其他的servlet处理
在其中一个请求没有结束的时候,被分配的Servlet就可以 访问操作域属性;和Complex是相同的;比如remove,set和get
package cfeng; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(description = "just for testing", urlPatterns = { "/some" }) public class someServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println(this.getServletName()); String author = (String)request.getAttribute("author"); System.out.println("author = " + author); System.out.println("addree = " + request.getAttribute("address")); }
这里就是被分配的类,配置的信息就是在webServlet注释中,不需要再xml中配置
package cfeng; import java.io.IOException; import java.util.Enumeration; import java.util.Map; 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; @WebServlet(description = "this is just a test class", urlPatterns = { "/TestServlet" }) public class TestServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute("author", "Cfeng"); request.setAttribute("address", "Peking"); Map<String, String[]> map = request.getParameterMap(); request.getRequestDispatcher("/some").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response);//这里POST提交的时候也会执行GET中的方法 } }
这样就和之前讲解的域属性是类似的,就是Servlet之间实现属性共享,可以设置,访问和删除;这是这里的域属性必须是请求有效区间。♉️