Servlet其实就是一套规范,我们按照这套规范写的代码就可以直接在java的服务器上运行。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() ;
}
<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>
Tomcat 中Serviet 的init 方法是在org.apache.catalina.core.StandardWrapper 的initServlet方法中调用的, ServletConfig传人的是StandardWrapper (里面封装着Servlet)自身的门面类StandardWrapperFacade 。其实这个也很容易理解, Servlet 是通过xml 文件配置的, 在解析xml 时就会把配置参数给设置进去,这样StandardWrapper 本身就包含配置项了, 当然,并不是StandardWrapper 的所有内容都是Config 相关的, 所以就用了其门面Facade 类。下面是ServletConfig 接口的定义:
import java.util.Enumeration;
public interface ServletConfig {
public String getServletName ();
public ServletContext getServletContext();
public String getlnitParameter (String name );
publiC Enumeration getInitParameterNames ();
}
getServletName 用于获取Servlet 的名字,也就是我们在web.xml 中定义的servlet-name ; getlnitParameter 方法用于获取init-param 配置的参数; getlnitParameterNames 用于获取配置的所有init-param 的名字集合; getServletContext 非常重要,它的返回值Serv ]letContext 代表的是我们这个应用本身,如果你看了前面Tomcat 的分析就会想到, ServletContext 其实就是Tomcat 中Context 的门面类ApplicationContextFacade (具体代码参考StandardContext 的getServ letContext 方法) 。既然ServletContext 代表应用本身,那么ServletContext 里边设置的参数就可以被当前应用的所有Serviet 共享了。我们做项目的时候都知道参数可以保存在Session 中,也可以保存在Application 中,而后者很多时候就是保存在了ServletContext 中。
我们可以这么理解, ServletConfig 是Servlet 级的p 而ServletContext 是Context (也就是Application ) 级的。当然, ServletContext 的功能要强大很多,并不只是保存一下配置参数,否则就叫ServletContextConfig 了。
另外,在Serv etContext 接口中有这么一个方法: public ServletContext getContext(String uripath) ,它可以根据路径获取到同一个站点下的别的应用的ServletContext ! 当然由于安全的原因, 一般会返回null ,如果想使用需要进行一些设置。
ServletConfig 和ServletContext 最常见的使用之一是传递初始化参数。我们就以spring 配置中使用得最多的contextConfigLocation 参数为例来看一下:
<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*:/applicationContext/application_spring_mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
上面通过context-param 配置的contextConfigLocation 配置到了ServletContext 中, 而通过servlet 下的init-param 配置的contextConfigLocation 配置到了ServletConfig 中。在Servlet 中可以分别通过它们的getlnitParameter 方法进行获取,比如:
String contextLocation = getServletConfig().getServletContext().getinitParameter("context ConfigLocation");
String servletLocation = getServletConfig().getinitParameter("contextConfigLocation") ;
为了操作方便, GenericServlet 定义了getinitParameter 方法,内部返回getServIetConfig().getlnitParameter 的返回值。因此,我们如果需要获取ServletConfig 中的参数,以不再调用getServletConfig(),而直接调用getlnitParameter。
另外ServletContext 中非常常用的用法就是保存Application 级的属性,这个可以使用setAttribute 来完成,比如:
getServletContext().setAttribute("contextConfigLocation","new path");
需要注意的是,这里设置的同名Attribute 并不会覆盖initParameter 中的参数值,它们是两套数据,互不干扰。ServletConfig 不可以设置属性。
GenericServlet 是Servlet 的默认实现,主要做了三件事:
GenericServlet 实现了ServletConfig 接口,我们在需要调用ServletConfig 中方法的时候可以直接调用,而不再需要先获取ServletConfig 了,比如,获取ServletContext 的时候可以直接调用getServletContext,而无须调用getServletConfig().getServletContext()了,不过其底层实现其实是在内部调用了。getServletContext 的代码如下:
@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
GenericServlet 实现了Serviet 的init(ServletConfigconfig)方法,在里面将config设置给了内部变量config ,然后调用了无参的init()方法,这个方法是个模板方法,在子类中可以通过覆盖它来完成自己的初始化工作,代码如下:
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
// NOOP by default
}
这种做法有三个作用:首先,将参数config 设置给了内部属性config ,这样就可以在ServletConfig 的接口方法中直接调用config 的相应方法来执行;其次,这么做之后我们在写Servlet 的时候就可以只处理自己的初始化逻辑,而不需要再关心config 了;还有一个作用就是在重写init 方法时也不需要再调用super.init(con fig)了。如果在自己的Servlet 中重写了带参数的init 方法,那么一定要记着调用super.init(config),否则这里的config 属性就接收不到值,相应的ServletConfig 接口方法也就不能执行了。
HttpServlet 是用HTTP 协议实现的Serviet 的基类, 写Servlet 时直接继承它就可以了,不需要再从头实现Serviet 接口,我们要分析的Spring MVC 中的DispatcherServlet 就是继承的H即Servlet。既然HttpServlet 是跟协议相关的,当然主要关心的是如何处理请求了,所以HttpServlet 主要重写了service 方法。在service 方法中首先将ServletRequest 和ServletResponse转换为了HttpServletRequest 和HttpServletResponse ,然后根据Http请求的类型不同将请求路向到了不同的处理方法。代码如下:
@Override
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;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
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 正常不需要使用,主要是用来做一些调试工作, doOptions 返回所有支持的处理类型的集合,正常情况下可以禁用, doTrace 是用来远程诊断服务器的,它会将接收到的header 原封不动地返回,这种做法很可能会被黑客利用,存在安全漏洞,所以如果不是必须使用,最好禁用。由于doOptions 和doTrace 的功能非常固定,所以HtψServlet 做了默认的实现。doGet 代码如下( doPost 、doPut 、do Delete 与之类似):
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(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
这就是HttpServlet ,它主要将不同的请求方式路由到了不同的处理方法。不过SpringMVC中由于处理思路不一样,又将所有请求合并到了统一的一个方法进行处理,在后续的文章中SpringMVC中再详细讲解。