文章目录
Servlet
定义
Servlet 是 Serve + Applet 的缩写,表示一个服务器应用。Servlet 规范结构如图所示:
Servlet 接口
Servlet 接口定义了5个方法:
-
void init(ServletConfig config)
在容器启动时被调用(当 load-on-startup 设置为负数或者不设置时会在 Servlet 第一次用到时才被调用),只会调用一次。 -
ServletConfig getServletConfig()
获取 ServletConfig。 -
void service(ServletRequest req, ServletResponse res)
具体处理一个请求。 -
String getServletInfo()
获取与 Servlet 相关的信息,需要自己实现,默认返回空字符串。 -
void destroy()
在 Servlet 销毁时释放资源,只会调用一次。
ServletConfig 接口
ServletConfig 接口定义了4个方法:
-
String getServletName()
获取 Servlet 的名字,即在 web.xml 中定义的<servlet-name>
。 -
ServletContext getServletContext()
获取 ServletContext。 -
String getInitParameter(String name)
获取<init-param>
配置下的<param-value>
参数集合。 -
Enumeration<String> getInitParameterNames()
获取<init-param>
配置下的<param-name>
参数集合。
ServletContext 应用
容器启动时,自动为 Web 应用程序创建 ServletContext 对象,它代表当前 Web 应用,由所有 Servlet 共享,因此 Servlet 对象之间可以通过 ServletContext 对象来实现通讯。
// Servlet1:将数据 data 存储到 ServletContext 对象中
String data = "Save ServletContext !";
ServletContext context = this.getServletContext();
context.setAttribute("data", data);
// Servlet2:从 ServletContext 对象中取出数据,实现数据共享
ServletContext context = this.getServletContext();
String data = (String) context.getAttribute("data");
response.getWriter().print("data="+data);
GenericServlet
GenericServlet 是 Servlet 的默认实现,主要做了三件事:①实现 ServletConfig 接口;②提供了无参的 init 方法;③提供了 log 方法。
1. 实现 ServletConfig 接口
通过源码可知,getServletContext 方法内部先调用了 getServletConfig,因此可以直接调用 ServletConfig 中的方法。
// javax.servlet.GenericServlet
public ServletContext getServletContext() {
ServletConfig sc = getServletConfig();
if (sc == null) {
throw new IllegalStateException(
lStrings.getString("err.servlet_config_not_initialized"));
}
return sc.getServletContext();
}
2. 提供了无参的 init 方法
GenericServlet 实现了 Servlet 的 init(ServletConfig config)
方法,在里面将 config 设置给了内部变量 config,然后调用了无参的 init()
方法,这个方法是个模板方法,在子类中可以通过覆写来完成自定义的初始化工作。
// javax.servlet.GenericServlet
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
三个作用
- 将参数 config 设置给内部属性 config,方便在 ServletConfig 的接口方法中直接调用 config 的方法来执行
- 自定义实现初始化逻辑,不需要关心 config
- 重写 init 方法时不需要调用
super.init(config)
。如果自定义的 Servlet 中重写了带参数的 init 方法,那么一定要调用super.init(config)
,否则 config 属性接收不到值,相应的 ServletConfig 接口方法便无法执行。
3. 提供了 log 方法
2个 log 方法,一个记录日志,一个记录异常。具体实现是通过传给 ServletContext 的日志框架实现的。
// javax.servlet.GenericServlet
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 的基类,继承 GenericServlet,因此包含了 GenericServlet 相关的特性。
HttpServlet 主要重写了 Servlet 接口的 service 方法,将 ServletRequest 和 ServletResponse 转换为 HttpServletRequest 和 HttpServletResponse ,然后根据 HTTP 请求的类型不同将请求路由到不同的处理方法。具体处理方法 doXXX 都是模板方法,需要子类覆写实现,否则抛出异常。
// javax.servlet.HttpServlet
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);
}
}
使用
Servlet 配置
<servlet>
用于注册 Servlet ,它包含有两个主要的子元素: <servlet-name>
和 <servlet-class>
,分别用于设置 Servlet 的注册名称和完整类名。
<servlet-mapping>
用于映射一个已注册的 Servlet 的一个对外访问路径,它包含有两个子元素:<servlet-name>
和 <url-pattern>
,分别用于指定 Servlet 的注册名称和对外访问路径。
映射 URL 中可以使用*
通配符,但是只能有两种固定的格式:一是"*.扩展名
",另一种是以"/*
"。如果 Servlet 的 URL 映射路径仅仅为一个"/
",那么这个 Servlet 就成为当前 Web 应用程序的缺省 Servlet 。凡是在 web.xml 文件中找不到匹配的 <servlet-mapping>
的 URL ,它们的访问请求都将交给缺省 Servlet 处理。
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.chen.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
应用
web.xml 配置
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:application-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
通过 <context-param>
指定 Spring 的配置文件位置 contextConfigLocation 参数,并保存在 ServletContext 中;通过 <init-param>
指定 Spring MVC 的配置文件位置 contextConfigLocation 参数,并保存在 ServletConfig 中,其中 <load-on-startup>
设置为1(数字越小优先级别越高),表示该 Servlet 在容器一启动时就立即加载,并调用接收了 ServletConfig 参数的 init 方法。