Servlet详解
servlet是Server+Applet的缩写,表示服务器应用。Servlet其实就是一套规范,按照servlet规范编写的程序可以直接运行在支持servlet规范的java服务器上。Servlet3.1的Servlet规范的结构图如下:
Servlet接口
Servlet是一套规范,那么里面的Servlet结构自然很重要了。Servlet接口定义如下:
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在容器启动时会被容器调用(load-on-startup设置为负数或者不设置时会在Servlet第一次被调用时才被调用),只会调用一次;getServletConfig用户获取ServletConfig;service方法用于处理一个具体的请求;getServletInfo方法用于获取Servlet相关信息,这个方法要自己实现,默认返回空字符串。destory主要用于Servlet销毁时释放资源,一般是关闭服务器时,也只会调用一次。
init方法被调用时会接收到一个ServletConfig参数,通过容器传入。ServletConfig是指Servlet的配置,我们在web.xml中定义Servlet时通过init-param标签配置的参数就是通过ServletConfig保存的,我们配置SpringMvc的Dispatcher时指定的ContextConfiguration就是保存在ServletConfig中。
<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
tomcat中的Servlet的init方法是在org.apache.catalina.core.SrandardWrapperd的initServlet方法中调用的。ServletConfig传入的是StandardWrapper(封装着Servlet)的门面类ServletWrapperFacade。Servlet是解析xml时配置的,这样StandardWrapper就包含了配置项。
ServletConfig接口定义如下:
public interface ServletConfig {
public String getServletName();
public ServletContext getServletContext();
public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
}
getServletName用来获取servlet的名字,就是我们在web.xml中定义的servlet-name。getInitparameter用来获取init-param配置的参数;getInitParameterNames用来获取配置参数的集合。getServletContext类的返回值代表我们这个应用的本身,ServletContext是Tomcat的context门面类ApplicationContextFacade。SerlvetContext代表应用本身,那么在ServletContext里面的参数就可以被所有的Servlet共享了。我们项目设置参数有些保存在session中,有时候保存在Application中,后者大多就是保存在ServletContext中。
servletConfig是Servlet级的配置,ServletContext是应用级的,两者都可以配置参数,但是ServletContext还有更多功能。我们知道tomcat是可以同时运行多个应用的,那么有没有更高级别的操作可以访问到其他的应用呢?ServletContext接口中有个方法ServletContext getContext(String uripath),可以根据路径获取到同一站点下别的应用的ServletContext。当然为了安全这个方法默认返回null,如果要使用需要进行一些配置。
ServletConfig和ServletContext最常见的使用时传递参数。以SpringMvc的配置为例:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-*.xml</param-value>
</context-param>
<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
通过context-param配置的参数在ServletContext中,通过Servlet的init-param配置的contextConfigLocation配置到了ServletConfig中。可以在Servlet中通过getInitparameter获取:
String contextLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation");
String servletConfigLocation = getServletConfig().getInitParameter("contextConfigLocation");
在GenericServlet中也有getInitParameter方法,内部返回返回getServletConfig().getInitParameter的返回值,因为我们可以直接调用getInitParameter获取ServletConfig的参数。
ServletContext经常用来保存Application级的属性,可以使用setAttribute来进行:
getServletContext().setAttribute(String key, String value);
ServletConfig和ServletContext是两套数据,相互间不干扰,servletConfig不能设置属性。
GenericServlet
GenericServlet是Servlet的默认实现,主要有三个功能:
- 实现ServletConfig接口
- 提供无参的init方法
- 提供log方法
GenericServlet提供了getServletContext接口,内部调用getServletConfig方法,不用我们再去getServletConfig().getServletContext()接口了。
GenericServlet实现了Servlet的init接口,将config赋值给了内部的config变量,然后调用无参的init方法,我们可以通过继承他来覆盖init方法。
public void init(ServletConfig config) throws ServletException {
this.config = config;
init();
}
这样我们就可以直接在ServletConfig方法中直接调用config来操作,而且在写Servlet时就不用关心config,只用处理自己的初始化逻辑即可,即覆盖无参的init方法即可。如果要覆盖带参数的init方法,需要调用super.init(config)。
GenericServlet提供了两个log方法。一个记录日志,一个记录异常,通过传递给ServletContext实现的。
public void log(String msg) {
getServletContext().log(getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
HttpServlet
HttpServlet是用http实现的Servlet的基类,写Servlet时直接继承即可。SpringMvc中的Dispatcher即是继承的HttpServlet。httpServlet是与协议相关的,其主要关注点在于如何处理请求。所以HttpServlet主要重写了Servlet接口中的service方法。在service方法中,将ServletRequest和ServletResponse转化为了HttpServletRequest和httpServletResponse,再根据不同的请求类型将请求路由到不同的方法。
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("non-HTTP request or response");
}
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) {
// 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 / 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);
}
}
具体处理方法都是doXXX的形式,如doGet(),doPost()等常用方法。doGet,doPost,doPut,doDelete这些都是模板方法,如果子类没有实现将会出现异常,在调用doGet前会对是否过期做检查,如果没过期直接返回304状态码使用缓存。doHead调用doGet返回空body的response。doOptions主要用来进行调试,返回支持处理类型的集合。doTrace用于远程诊断服务器,将收到的header原样返回,这种做法容易被黑客利用,最好禁用。doOptions和doTrace功能固定,所以httpServlet提供了默认实现。