Servlet是Server+Applet的缩写,表示一个服务器的应用。
1、Servlet接口
Servlet是一套规范,即就是一个接口,包含五个方法:
<1>Init方法:在容器启动时被容器调用(当在web.xml中load-on-startup设置为负数或者不设置时会在Servlet第一次调用时才被调用)只用调用一次
<2>getServletConfig方法用于获取ServletConfig
<3>service方法用于具体处理一个请求
<4>getServletInfo方法可以获取一些Servlet相关的信息(这个方法需要自己实现,默认返回空字符串)
<5>destory是在Servlet销毁时释放一些资源(只会调用一次)
Init方法被调用时会接受到一个ServletConfig类型的参数,是容器传进来的。Web.xml中定义Servlet时通过init-param标签配置的参数就是通过ServletConfig来保存的,比如:定义Spring MVC的Servlet时指定配置文件的contextConfigLocation参数就保存在ServletConfig中
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/spring-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Tomcat中对的Servlet的init方法是在org.apache.catalina.core.StandardWrapper的initServlet方法中调用的,ServletConfig传入的是StandardWrapper(里面封装着Servlet)吱声的门面类StandardWrapperFacade。Servlet是通过xml文件配置的,在解析xml时就会把配置参数的设置进去,这样StandardWrapper本身就包含了配置项了,当然,不是StandardWrapper的所有内容都是Config相关的,所以使用了Facade类。
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
getInitParameter方法用户获取init-param配置的参数
getInitParameterNames用于获取配置的所有init-param的名字集合
getServletContext返回值ServletContext代表的是我们这个应用本身,ServletContext里面设置的参数是可以被当前应用的所有Servlet共享了。
即ServletConfig是Servlet级的,ServletContext是Context(也就是Application)级的。
除了以上两个级别的可以操作,还有更高级别的。在ServletContext接口中有一个public ServletContext getContext(String uripath),可以根据路径获取到同一个站点下别的应用的ServletContext
ServletConfig和ServletContext最常见的使用方式就是传递初始化参数,
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/spring-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties</param-value>
</context-param>
通过context-param配置log4jConfigLocation到ServletContext中,通过servlet下的init-param配置的contextConfigLocation配置到ServletConfig中
获取方法如下:
String contextConfigLocation = getServletConfig().getInitParameter("contextConfigLocation");
String log4jConfigLocation = getServletConfig().getServletContext().getInitParameter("log4jConfigLocation");
为了方便操作,GenericServlet定义了getInitParameter方法,内部返回getServletConfig().getInitParameter的返回值,因此,需要获取ServletConfig中的参数,可以不再调用getServletConfig(),而是直接调用getInitParameter。
ServletContext中还非常常用的就是保存Application级的属性,如:getServletContext().setAttribute("log4jConfigLocation", "/WEB-INF/spring/log4j.properties");
注意:设置同名Attribute并不会覆盖initParameter中的参数,ServletConfig不可以设置属性。
2、GenericServlet
GenericServlet是Servlet的默认实现,主要做三件事:
(1)实现了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();
}
(2)提供了无参的init方法:实现了Servlet的init(ServletConfig config)方法,在方法中将config设置给了内部变量config,然后调用了无参的init()方法
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {}
以上两种做法的作用有三点:
在ServletConfig的接口方法中直接调用config的相应方法来执行
我们自己在写Servlet时只处理自己的初始化逻辑,不需要再关心config
重写无参init方法无需再调用super.init(config),重写有参的init方法,一定要记得调用super.init(config),否则这里的config属性就接受不到值,相应的ServletConfig接口方法也就不能执行了。
(3)提供了log两个方法:一个记录日志,一个记录异常,具体实现是通过传给ServletContext的日志实现的。
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
3、HttpServlet
HttpServlet是HTTP协议实现的Servlet的基类,我们写Servlet时直接继承它就可以了。
HttpServlet是跟协议有关的,所以HttpServlet主要是重写了service方法。在service方法中将ServletRequest和ServletResponse转换为HttpServletRequest和HttpServletResponse,然后根据Http请求的类型不同将请求路由到把不同的处理方法。
代码如下:
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) {
// 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);
}
}
备注:
源代码来源于javax.servlet-api-3.0.1.jar中。
参考资料:《看透Spring MVC 源代码分析与实践》