【SpringMVC】Servlet

Servlet是一套规范,其实就是一组接口,容器会回调接口的方法。我们按照这个规范写的代码就可以直接在Java的服务器上运行,等待容器的调用。
层次结构
【SpringMVC】Servlet
文章图片
HttpServlet.png Servlet接口
public interface Servlet { //init方法在容器启动的时候被容器调用(当load-on-startup设置为负数或者不设置时会在Servlet第一次用到时才被调用),只会调用一次 void init(ServletConfig var1) throws ServletException; //getServletConfig方法用于获取ServletConfig ServletConfig getServletConfig(); //service方法用于具体处理一个请求 void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; //获取servlet相关的信息 String getServletInfo(); //主要用在Servlet销毁(一般指关闭服务器)时释放一些资源,也只会调用一次 void destroy(); }

  • Init方法被容器调用的时候会传入一个ServletConfig接口类型的参数。ServletConfig顾名思义指的是Servlet的配置。我们在web.xml中定义Servlet时通过init-prams标签配置的参数就是通过ServletConfig来保存的。比如,定义SpringMVC的Servlet时指定配置文件位置的contextConfigLocation参数就保存在ServletConfig中。
    例如下面的配置:
contextConfig contextConfig org.springframework.web.servlet.DispatcherServlet【【SpringMVC】Servlet】contextConfigLocationclasspath*:/xml/all.xml 1

ServletConfig
public interface ServletConfig { //用于获取Servlet的名字,也就是我们在web.xml中定义的servlet-name String getServletName(); //ServletContext 代表这个应用本身 ServletContext getServletContext(); //获取init-param配置的参数 String getInitParameter(String var1); //获取配置的所有的init-params的名字集合 Enumeration getInitParameterNames(); }

  • ServletConfig是Servlet级的,而ServletContext是Context(也就是Application级的)。
  • ServletConfig和ServletContext最常见的应用之一就是传递初始化参数
    如下:
contextConfigLocationclasspath*:/xml/all.xml contextConfig contextConfig org.springframework.web.servlet.DispatcherServletcontextConfigLocationclasspath*:/xml/all.xml 1

上面通过context-param配置的contextConfigLocation配置到了ServletContext中,而通过servlet下的init-params配置的contextConfigLocation配置到了ServletConfig中。
在Servlet中可以分别通过他们的getInitParamter方法进行获取,比如:
String contextConfigLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation"); String servletLocation = getServletConfig().getInitParameter("contextConfigLocation");

GenericServlet
  • 实现Servlet接口:GenericServlet是Servlet的一个默认实现,并没有实现service方法即它不能实际处理请求。
    如下:init(ServletConfig config)方法中用一个ServletConfig类型的成员变量保存容器传入的ServletConfig,然后调用了一个无参数的init()方法。无参的init()方法是一个模版方法,子类重写该方法做一些初始化的工作,一般情况我们不要重写有参的init方法,因为GenericServlet中的其他方法会访问ServletConfig这个成员变量。
public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException { } public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

  • 实现ServletConfig接口:方便的通过GenericServlet直接访问ServletConfig中保存的信息。
    如下:ServletConfig接口的getInitParameterNames在GenericServlet中的实现,实际上还是调用了ServletConfig.getInitParameterNames。
public Enumeration getInitParameterNames() { ServletConfig sc = this.getServletConfig(); if (sc == null) { throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized")); } else { return sc.getInitParameterNames(); } }

  • 新增了log(String msg)
public void log(String message, Throwable t) { this.getServletContext().log(this.getServletName() + ": " + message, t); }

HttpServlet
HttpServlet是GenericServlet的子类,主要实现了父类的抽象方法service(ServletRequest var1, ServletResponse var2)
  • 在service(ServletRequest var1, ServletResponse var2) 中判断 ServletRequest 是否是HttpServletRequest 类型,如果是则处理该请求。换句话说HttpServlet 只能处理Http请求。
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; this.service(request, response); } else { throw new ServletException("non-HTTP request or response"); } }

  • service(HttpServletRequest req, HttpServletResponse resp)中首先获取了请求方法的类型,然后根据请求方法类型分发到不同的方法进行处理。
    GET方法还会使用缓存机制,getLastModified默认返回-1L即不支持缓存,我们可以通过重写getLastModified()方法获取资源修改时间如文件实际修改的时间来实现自己的缓存策略。
  • 缓存比较流程:
    如果浏览器的If-Modified-Since大于等于服务器的Last-Modified时间,说明浏览器的文件没有修改。那么返回HTTP状态码304(不返回文件内容),客户端接到之后,就直接把本地缓存文件显示到浏览器中。
    如果浏览器的If-Modified-Since小于该资源的Last-Modified时间,说明文件有修改。就返回HTTP状态码200、Last-Modified以及新的文件内容,客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示到浏览器中,同时将If-Modified-Since设置为Last-Modified,下次请求的时候附带上If-Modified-Since。
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); }}

  • GET、HEAD、POST、PUT、DELETE、OPTIONS、TRACE的默认实现。通过下面的代码可以知道默认所有的方法都是返回400或者405的,也就是说HttpServlet的子类应该要根据自己需要重写其中部分方法来处理请求,不然就会默认返回400或者405 。
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); } }

    推荐阅读