Java中的Servlet你了解吗?
Servlet详解
Servlet
是Server + Applet
的缩写,表示一个服务器应用。
其实,Servlet
就是一套规范,我们按照这套规范写出来的代码就可以直接在Java服务器上运行。
Servlet3.1
中的Servlet
结构如下图所示:
1. Servlet接口
说到规范,那当然还是接口最重要了,我们看一下接口的定义:
package javax.servlet;
import java.io.IOException;
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletInfo();
public void destroy(); }
|
简单介绍一下几个方法的作用:
- **
init
**:init方法在容器启动时被容器调用(当load-on-startup
设置位负数或者不设置时会在Servlet
第一次用到时才会被调用),只会调用一次;
- **
getServletConfig
**:用于获取ServletConfig
,即Servlet
的配置;
- **
service
**:用于具体处理一个请求;
- **
getServletInfo
**:获取一些Servlet
相关的信息,如作者、版权等,默认返回空串;
- **
destroy
**:用于在Servlet
销毁时释放资源,也只会调用一次;
(1) ServletConfig
ServletConfig
指的是Servlet
的配置,也就是我们在web.xml
中定义的Servlet
时通过init-param
标签配置的参数就是通过ServletConfig
来保存的。
比如定义Spring MVC
的Servlet
时指定配置文件位置的contextConfigLocation
参数就保存在ServletConfig
中,如下:
<servlet> <servlet-name>demoDispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>demo-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
|
下面我们看一下ServletConfig
接口的定义:
package javax.servlet;
import java.util.Enumeration;
public interface ServletConfig {
public String getServletName();
public ServletContext getServletContext();
public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames(); }
|
简单介绍一下几个方法的作用:
getServletName
:用于获取Servlet
的名字,也就是我们在web.xml
中定义的servlet-name
;
getServletContext
:获取ServletContext
,表示我们这个应用本身;
getInitParameter
:获取init-param
配置的参数;
getInitParameterNames
:获取配置的所有init-param
的名字集合;
(2) ServletContext
ServletContext
非常重要,代表的其实就是我们应用本身。
那么自然,ServletContext
中设置的参数可以被我们当前应用的所有Servlet
共享,也就是我们在写功能时,把一些参数保存在Application
中,其实就是保存在了ServletContext
中了。
这里只是简单了解一下ServletContext
。
(3) 使用
ServletConfig
和 ServletContext
最常见的使用之一是传递初始化参数。
<?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_3_1.xsd" version="3.1" metadata-complete="true"> <display-name>initParam Demo</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>application-context.xml</param-value> </context-param> <servlet> <servlet-name>DemoServlet</servlet-name> <servlet-class>com.excelib.DemoServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>demo-servlet.xml</param-value> </init-param> </servlet> ... </web-app>
|
- 通过
context-param
配置的contextConfigLocation
配置到了servletContext
中
servlet
下面的init-param
配置的contextConfigLocation
配置到了ServletConfig
中
在Servlet中可以分别通过它们的getInitParameter
方法进行获取,比如:
String contextLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation"); String servletLocation = getServletConfig().getInitParameter("contextConfigLocation");
|
另外**ServletContext
中非常常用的用法就是保存Application
级别的属性**,比如:
getServletContext().setAttribute("contextConfigLocation", "new path");
|
需要注意的是,这里设置的同名的Attribute
并不会覆盖initParameter
中的参数值,它们是两套数据,互不干扰。
ServletConfig
不可以设置属性。
2.GenericServlet
GenericServlet
是Servlet
的默认实现,主要做了三件事情:
- 实现了
ServletConfig
接口,我们可以直接调用该接口里面的方法;
- 提供了无参的
init
方法;
- 提供了
log
方法;
实现了ServletConfig接口
这样我们在需要调用ServletConfig中方法的时候可以直接调用,而不需要先获取ServletConfig了。其实是在底层帮我们内部调用了,源码如下:
public ServletContext getServletContext() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); }
return sc.getServletContext(); }
|
提供了无参的init方法
GenericServlet
实现了Servlet的init(ServletConfig config)
方法,在里面将config设置给了内部变量config,然后调用无参的init方法,该方法是一个模板方法,在子类中可以通过覆盖它来完成自己的初始化工作,如下:
public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); }
public void init() throws ServletException {
}
|
这样做有三个好处:
- 将参数
config
设置给内部属性config
,这样就可以在ServletConfig
的接口方法中直接调用config
的相应方法来执行;
- 之后写
Servlet
时只需要处理自己的初始逻辑,不需要再关注config
;
- 重写
init
方法时也不需要再调用super.init(config)
;
提供了log方法
具体实现是通过传给ServletContext的日志实现的。
public void log(String msg) { getServletContext().log(getServletName() + ": "+ msg); }
public void log(String message, Throwable t) { getServletContext().log(getServletName() + ": " + message, t); }
|
完整源码如下:
package javax.servlet;
import java.io.IOException; import java.util.Enumeration; import java.util.ResourceBundle;
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { private static final String LSTRING_FILE = "javax.servlet.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
private transient ServletConfig config;
public GenericServlet() { }
public void destroy() { }
public String getInitParameter(String name) { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); }
return sc.getInitParameter(name); }
public Enumeration<String> getInitParameterNames() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); }
return sc.getInitParameterNames(); }
public ServletConfig getServletConfig() { return config; }
public ServletContext getServletContext() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); }
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) { getServletContext().log(getServletName() + ": "+ msg); }
public void log(String message, Throwable t) { getServletContext().log(getServletName() + ": " + message, t); }
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletName() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); }
return sc.getServletName(); } }
|
3. HttpServlet
HttpServlet
是用HTTP协议实现的Servlet
的基类,写Servlet
时直接继承它就可以了,不需要再从头实现Servlet
接口。
HttpServle
t是跟协议有关的,所以我们就关注一下如何处理请求。
HttpServlet
的重点主要在service
中,在service
方法中首先将ServletRequest
和ServletResponse
转换为HttpServletRequest
和HttpServletResponse
,然后根据Http请求的类型不同将请求路由到了不同的处理方法:
@Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException("non-HTTP request or response"); } request = (HttpServletRequest) req; response = (HttpServletResponse) res; service(request, response); }
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) { doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { 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 { 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); } }
|