6、 体系结构
通过上面的学习,我们知道要想编写一个Servlet就必须要实现Servlet接口,重写接口中的5个方法,虽然已经能完成要求,但是编写起来还是比较麻烦的,因为我们更关注的其实只有service方法,那有没有更简单方式来创建Servlet呢?
要想解决上面的问题,我们需要先对Servlet的体系结构进行下了解:
因为我们将来开发B/S架构的web项目,都是针对HTTP协议,所以我们自定义Servlet,会通过继承HttpServlet
具体的编写格式如下:
@WebServlet("/demo4") public class ServletDemo4 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //TODO GET 请求方式处理逻辑 System.out.println("get..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //TODO Post 请求方式处理逻辑 System.out.println("post..."); } }
要想发送一个GET请求,请求该Servlet,只需要通过浏览器发送http://localhost:8080/web-demo/demo4,就能看到doGet方法被执行了
要想发送一个POST请求,请求该Servlet,单单通过浏览器是无法实现的,这个时候就需要编写一个form表单来发送请求,在webapp下创建一个a.html页面,内容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/web-demo/demo4" method="post"> <input name="username"/><input type="submit"/> </form> </body> </html>
启动测试,即可看到doPost方法被执行了。
Servlet的简化编写就介绍完了,接着需要思考两个问题:
HttpServlet中为什么要根据请求方式的不同,调用不同的方法?
如何调用?
针对问题一,我们需要回顾之前的知识点前端发送GET和POST请求的时候,参数的位置不一致,GET请求参数在请求行中,POST请求参数在请求体中,为了能处理不同的请求方式,我们得在service方法中进行判断,然后写不同的业务处理,这样能实现,但是每个Servlet类中都将有相似的代码,针对这个问题,有什么可以优化的策略么?
package com.itheima.web; import javax.servlet.*; 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("/demo5") public class ServletDemo5 implements Servlet { public void init(ServletConfig config) throws ServletException { } public ServletConfig getServletConfig() { return null; } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { //如何调用? //获取请求方式,根据不同的请求方式进行不同的业务处理 HttpServletRequest request = (HttpServletRequest)req; //1. 获取请求方式 String method = request.getMethod(); //2. 判断 if("GET".equals(method)){ // get方式的处理逻辑 }else if("POST".equals(method)){ // post方式的处理逻辑 } } public String getServletInfo() { return null; } public void destroy() { } }
要解决上述问题,我们可以对Servlet接口进行继承封装,来简化代码开发。
package com.itheima.web; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class MyHttpServlet implements Servlet { public void init(ServletConfig config) throws ServletException { } public ServletConfig getServletConfig() { return null; } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest)req; //1. 获取请求方式 String method = request.getMethod(); //2. 判断 if("GET".equals(method)){ // get方式的处理逻辑 doGet(req,res); }else if("POST".equals(method)){ // post方式的处理逻辑 doPost(req,res); } } protected void doPost(ServletRequest req, ServletResponse res) { } protected void doGet(ServletRequest req, ServletResponse res) { } public String getServletInfo() { return null; } public void destroy() { } }
有了MyHttpServlet这个类,以后我们再编写Servlet类的时候,只需要继承MyHttpServlet,重写父类中的doGet和doPost方法,就可以用来处理GET和POST请求的业务逻辑。接下来,可以把ServletDemo5代码进行改造
@WebServlet("/demo5") public class ServletDemo5 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { System.out.println("post..."); } }
将来页面发送的是GET请求,则会进入到doGet方法中进行执行,如果是POST请求,则进入到doPost方法。这样代码在编写的时候就相对来说更加简单快捷。
类似MyHttpServlet这样的类Servlet中已经为我们提供好了,就是HttpServlet,翻开源码,大家可以搜索service()方法,你会发现HttpServlet做的事更多,不仅可以处理GET和POST还可以处理其他五种请求方式。
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 = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // 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); } }
小结
通过这一节的学习,要掌握:
HttpServlet的使用步骤
继承HttpServlet
重写doGet和doPost方法
HttpServlet原理
获取请求方式,并根据不同的请求方式,调用不同的doXxx方法
7、 urlPattern配置
Servlet类编写好后,要想被访问到,就需要配置其访问路径(urlPattern)
一个Servlet,可以配置多个urlPattern
package com.sun.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * urlPattern: 一个Servlet可以配置多个访问路径 */ @WebServlet(urlPatterns = {"/demo7","/demo8"}) public class ServletDemo7 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo7 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
在浏览器上输入http://localhost:8080/web-demo/demo7,http://localhost:8080/web-demo/demo8这两个地址都能访问到ServletDemo7的doGet方法。
urlPattern配置规则
精确匹配
/** * UrlPattern: * * 精确匹配 */ @WebServlet(urlPatterns = "/user/select") public class ServletDemo8 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo8 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径http://localhost:8080/web-demo/user/select
目录匹配
package com.sun.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * UrlPattern: * * 目录匹配: /user/* */ @WebServlet(urlPatterns = "/user/*") public class ServletDemo9 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo9 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径http://localhost:8080/web-demo/user/任意
思考:
访问路径http://localhost:8080/web-demo/user是否能访问到demo9的doGet方法?
访问路径http://localhost:8080/web-demo/user/a/b是否能访问到demo9的doGet方法?
访问路径http://localhost:8080/web-demo/user/select是否能访问到demo9还是demo8的doGet方法?
答案是: 能、能、demo8,进而我们可以得到的结论是/user/*中的/*代表的是零或多个层级访问目录同时精确匹配优先级要高于目录匹配。
扩展名匹配
package com.sun.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * UrlPattern: * * 扩展名匹配: *.do */ @WebServlet(urlPatterns = "*.do") public class ServletDemo10 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo10 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径http://localhost:8080/web-demo/任意.do
注意:
如果路径配置的不是扩展名,那么在路径的前面就必须要加/否则会报错
如果路径配置的是*.do,那么在*.do的前面不能加/,否则会报错
任意匹配
package com.sun.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * UrlPattern: * * 任意匹配: / */ @WebServlet(urlPatterns = "/") public class ServletDemo11 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo11 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径http://localhost:8080/demo-web/任意
package com.sun.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * UrlPattern: * * 任意匹配: /* */ @WebServlet(urlPatterns = "/*") public class ServletDemo12 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo12 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径`http://localhost:8080/demo-web/任意
注意:/和/*的区别?
当我们的项目中的Servlet配置了 “/”,会覆盖掉tomcat中的DefaultServlet,当其他的url-pattern都匹配不上时都会走这个Servlet
当我们的项目中配置了"/*",意味着匹配任意访问路径
DefaultServlet是用来处理静态资源,如果配置了"/"会把默认的覆盖掉,就会引发请求静态资源的时候没有走默认的而是走了自定义的Servlet类,最终导致静态资源不能被访问
小结
urlPattern总共有四种配置方式,分别是精确匹配、目录匹配、扩展名匹配、任意匹配
五种配置的优先级为 精确匹配 > 目录匹配> 扩展名匹配 > /* > / ,无需记,以最终运行结果为准。
8、 XML配置
前面对应Servlet的配置,我们都使用的是@WebServlet,这个是Servlet从3.0版本后开始支持注解配置,3.0版本前只支持XML配置文件的配置方法。
对于XML的配置步骤有两步:
编写Servlet类
package com.sun.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; public class ServletDemo13 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo13 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
在web.xml中配置该Servlet <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- Servlet 全类名 --> <servlet> <!-- servlet的名称,名字任意--> <servlet-name>demo13</servlet-name> <!--servlet的类全名--> <servlet-class>com.sun.web.ServletDemo13</servlet-class> </servlet> <!-- Servlet 访问路径 --> <servlet-mapping> <!-- servlet的名称,要和上面的名称一致--> <servlet-name>demo13</servlet-name> <!-- servlet的访问路径--> <url-pattern>/demo13</url-pattern> </servlet-mapping> </web-app>
这种配置方式和注解比起来,确认麻烦很多,所以建议大家使用注解来开发。但是大家要认识上面这种配置方式,因为并不是所有的项目都是基于注解开发的。