SpringMVC的简单实用和配置请查看我的一篇博文 SpringMVC配置实例,今天主要分析一下SpringMVC中的Servlet源码,Serlvet的生命周期和使用方法请查看我的另一篇博文 Servlet学习笔记
1.首先来看一下Servlet接口的源码:
import java.io.IOException;
public interface Servlet {
/**
* 被servlet引擎调用用于初始化该Servlet,只有在该方法完成之后,才能响应请求
* 如果init方法抛出了异常,则一定不能响应请求。init方法在构造方法之后执行。
* Servlet引擎会把ServletConfig对象传进来
*/
public void init(ServletConfig config) throws ServletException;
/**
* 返回ServletConfig对象,其中包含着初始化信息和初始化参数
* @see #init
*/
public ServletConfig getServletConfig();
/**
* 响应请求,必须在init(Servletconfig config)成功后才能执行
*/
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
/**返回基本信息*/
public String getServletInfo();
/**
* 被Servlet引擎调用,用来关闭资源,或者做一些清楚工作
*/
public void destroy();
}
- 所有servlet都要实现的接口
- Servlet是供Servlet引擎调用的Java类,它相当于Servlet引擎的组件
- 在Servlet接口中定义了三个方法,也被称为Servlet的声明周期:
- void init(ServletConfig config):Servlet被初始化时被Servlet引擎调用,只执行一次,且后于构造方法
- void service(ServletRequet,ServletResponse):响应请求,每当有一个请求,Servlet引擎就调用该方法
- void destroy():Servlet被销毁时被Servlet引擎调用,用于关闭资源等服务
- 以上三个方法都是被Servlet引擎(容器)调用
- ServletConfig getServletConfig:一个ServletConfig对象,在init方法被传递进来,包含了Servlet的初始化信息,我们配置的Springmvc的Servlet时指定配置文件位置的contextConfigLocation的参数就是保存在ServletConfig中。
- String getServletInfo():返回该Servlet的基本信息
2.GenericServlet源码分析
GenericServlet是Servlet的默认实现,主要完成三件事:
* 实现了ServletConfig接口,我们可以直接调用ServletConfig
* 提供了无参init方法
* 提供了log方法
GenericServlet源码:
package javax.servlet;
import java.io.IOException;
import java.util.Enumeration;
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
//销毁方法
@Override
public void destroy() {
// NOOP by default
}
//获取指定参数名的初始化参数,不需要自己调用getServletConfig(),已经默认帮我们调用了,直接用就可以。
@Override
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
//获取所有初始化参数,不需要自己调用getServletConfig(),已经默认帮我们调用了,直接用就可以。
@Override
public Enumeration<String> getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
//获取ServletConfig对象
@Override
public ServletConfig getServletConfig() {
return config;
}
//获取ServletContext对象
@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
//获取Servlet相关信息,默认为空,我们可以重写
@Override
public String getServletInfo() {
return "";
}
//Servelt初始化的时候调用,并且已经把ServletContext对象传入,我们不需要自己获取ServletContext对象在传入,并且提供了无参的init方法供调用者重写。
@Override
public void init(ServletContext config) throws ServletException {
this.config = config;
this.init();
}
//无参的init方法供调用者重写
public void init() throws ServletException {
// NOOP by default
}
//输出log
public void log(String msg) {
getServletContext().log(getServletName() + ": " + msg);
}
//输出log
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
//处理Http请求
@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
//获取Servlet的名称
@Override
public String getServletName() {
return config.getServletName();
}
3.HttpServlet源码分析:
HttpServlet是用HTTP协议实现的Servlet的基类并继承了GenericServlet,HttpServlet处理了Http请求,我们就分析一下HttpServlet处理请求的过程,其他的方法和GenericServlet是一样的。
HttpServlet 源码:
public abstract class HttpServlet extends GenericServlet {
private static final long serialVersionUID = 1L;
//HTTP请求方式
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
//最后一次修改
private static final String HEADER_LASTMOD = "Last-Modified";
、
.........省略部分代码...........
//get请求方式会调用此方法来处理
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
.........
}
/*1. -1:直接响应doget
*2. 小于返回值:说明请求端的页面已经过期,直接响应,且在响应头当中设置LastModified值
*3.大于返回值:说明请求端的页面已经是最新,客户端直接使用本地缓存页面,不需要服务端重新生成返回。向响应头当中写入状态码304
*/
protected long getLastModified(HttpServletRequest req) {
return -1;
}
//Head请求方式会调用此方法
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
........
}
//post请求方式调用此方法处理
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
..............
}
//put请求方式调用此方法处理
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
............
}
//Delete请求方式调用此方法处理
protected void doDelete(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
..............
}
//Option请求方式会调用此方法来处理
protected void doOptions(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
..........
}
//Trace请求方式会调用此方法来处理
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
............
}
//主要负责处理请求的方法
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
// -1直接响应doget
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)) {
//说明请求端的页面已经过期,直接响应,且在响应头当中设置LastModified值
maybeSetLastModified(resp, lastModified); //设置新的LastModified值
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) { //Head请求方式
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp); //处理Head请求
} else if (method.equals(METHOD_POST)) { //Post请求方式
doPost(req, resp); //处理POST请求
} else if (method.equals(METHOD_PUT)) { //Put请求方式
doPut(req, resp); //处理PUT请求
} else if (method.equals(METHOD_DELETE)) { //Delete请求方式
doDelete(req, resp); //处理DELETE请求
} else if (method.equals(METHOD_OPTIONS)) { //Option请求方式
doOptions(req,resp); //处理OPTIONS请求
} else if (method.equals(METHOD_TRACE)) { //Trace请求方式
doTrace(req,resp); //处理TRACE请求
} else {
//不支持的请求方式显示错误
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
//把lastModified设置到请求头
private void maybeSetLastModified(HttpServletResponse resp,
long lastModified) {
if (resp.containsHeader(HEADER_LASTMOD))
return;
if (lastModified >= 0)
resp.setDateHeader(HEADER_LASTMOD, lastModified);
}
//把request和response转成HttpServletRequest,HttpServletResponse,并调用上面的Service方法
@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);
}
}
由于篇幅的关系每种请求方式具体怎么处理的请求就没有列出来,可以自己分析,我们在使用Servlet的时候切记不要自己在service中根据请求方式调用doGet或doPost方法,我们通过源码发现service会自动根据请求的方式调用相应的处理方法,我们只需要重写doPost,doGet等等方法就可以。
3.HttpServletBean源码分析
HttpServletBean直接实现了EnviromentCapable和EnvironmentAware接口,
xxxAware在Spring中表示对xxx可以感知,换句话说就是如果在某个类里面想要Spring的一些东西就可以通过实现xxxAware告诉Spring,Spring感知后会吧需要的东西送过来。EnviromentCapable就是提供Environment的能力。
HttpServletBean源码:
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
....省略部分源码.........
//初始化的时候调用
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
//把Servlet中配置的参数封装到pvs中,requiredProperties为必须参数,如果没有配置会报异常。
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//BeanWrapper 是Spring提供用来操作JavaBean属性的一个工具,可以使用它直接修改一个对象的属性。这里操作的javaBean是DispatcherServlet
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//模板方法在子类中实现,做一些初始化操作
initBeanWrapper(bw);
//将配置初始化
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 模板方法在FrameworkSerlvet中实现
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
//空实现
protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
}
// 模板方法在FrameworkSerlvet中实现
protected void initServletBean() throws ServletException {
}
}
通过源码发现HttpServletBean的主要作用就是将Servlet中配置的参数通过BeanWrapper设置到DispatcherServlet的相关属性。然后调用initServletBean方法,子类通过这个方法初始化(比如FrameWorkServlet)。
4.FrameWorkServlet源码分析
FrameWorkServlet继承HttpServletBean调用initServletBean()方法进行初始化。
FrameWorkServlet源码:
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
@Override
protected final void initServletBean() throws ServletException {
long startTime = System.currentTimeMillis();
try {
//获得WebApplicationContext对象
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();//初始化FrameworkServlet
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
//初始化WebApplicationContext对象
protected WebApplicationContext initWebApplicationContext() {
//获取Spring的根容器rootContext
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//如果WebApplicationContext对象存在则去查找,通过Servlet的contextAttribute参数获取
wac = findWebApplicationContext();
}
if (wac == null) {
//如果WebApplicationContext对象不存在则去创建一个,createWebApplicationContext方法会将设置的contextConfigLocation参数传给wac,默认为web-INFO/[servletName]-Servlet.xml
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
onRefresh(wac);
}
if (this.publishContext) {
//把WebApplicationContext设置到ServletContext中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
}
return wac;
}
}
关于ServletContext和WebApplicationContext的区别和关系:
首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取。
DispatcherServlet源码分析:
DispatcherServlet继承FrameWorkServlet,onRefresh方法是他的入口方法,onRefresh()方法调用了initStrategies方法初始化SpringMVC9大组件。
DispatcherServlet源码:
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);//初始化文件上传解析器
initLocaleResolver(context);//本地化解析器
initThemeResolver(context);//主题解析器
initHandlerMappings(context);//HandlerMapping
initHandlerAdapters(context);//HandlerAdapter
initHandlerExceptionResolvers(context);//HandlerExceptionResolver异常解析器
initRequestToViewNameTranslator(context);//RequestToViewNameTranslator可以在处理器返回的View为空时使用它根据Request获取viewName。RequestToViewNameTranslator提供的实现类只有一个DefaultRequestToViewNameTranslator,提供了getViewName抽象方法,其实就是根据request请求获取来组装视图名称。
initViewResolvers(context);//初始化视图解析器
initFlashMapManager(context);//初始化FlashMapManager,flashMap用于redirect重定向传递数据
}
}
DispatcherServlet的转发请求的过程源码在后续的SpringMVC请求处理流程中分析SpringMVC源码分析(二)。