Servlet是J2EE标准的一部分,是Java Web开发的标准。Servlet制定了Java中处理Web请求的标准,但是标准本身只是提供了处理请求的标准规范,真正处理请求需要实现了Servlet标准的容器,我们常用的Servlet容器有Tomcat等,Servlet容器真正实现了对接收到的数据进行处理并生成要返回给客户端的结果。一般情况下,Servlet容器还包括了请求连接的建立和Socket通道内容的解析封装等功能。
关于Servlet接口和GenericServlet、HttpServlet类的具体内容,请参考《Servlet相关内容》。
Tomcat容器是Java Web开发中最常用的Servlet容器之一。Tomcat容器结构体系:
Tomcat整体结构图
Spring MVC的本质其实就是一个Servlet。Spring MVC的核心继承结构如下:
通过上述的结构图,我们可以知道,Spring MVC的实现是从实现Servlet规范的HttpServlet开始的。然后,通过HttpServletBean、FrameworkServlet和DispatcherServlet三个抽象类,组成了处理Http请求的核心逻辑。
Spring MVC核心类DispatcherServlet间接的实现了ApplicationContextAware、EnvironmentAware、EnvironmentCapable三个接口。这样DispatcherServlet类就具备了使用ApplicationContext、Environment的能力,同时具备了对外提供获取Environment的能力。
1、在spring设计理念中,XXXAware表示对XXX可以感知。如果实现了该接口,Spring初始化的时候,就会调用唯一SetXXX方法,把对应的XXX设置到该类中,然后该类就可以使用XXX。
2、EnvironmentCapable接口主要是让实现该接口的类,对外提供获取Environment的能力。
抽象类HttpServletBean主要参与了Servlet类的创建工作,并没有涉及请求的处理。这里主要实现了Servlet(实际上是GenericServlet类)的init()方法,该方法主要是供Servlet容器来初始化Servlet类的实例,详细内容请参考《Servlet工作原理和过程》。
HttpServletBean定义如下:
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
//省略其他内容
}
在抽象类HttpServletBean的定义中,我们可以看到,它实现了EnvironmentCapable, EnvironmentAware两个接口,通过实现这两个接口,使HttpServletBean具备了使用Environment和对外提供Environment的能力。同时,抽象类HttpServletBean还继承了servlet中的HttpServlet类,这是Spring MVC实现Servlet的核心逻辑,我们下面详细分析。
init()初始化方法
@Override
public final void init() throws ServletException {
// 首先将容器初始化时的参数ServletConfig,转换成PropertyValues(ServletConfigPropertyValues是HttpServletBean定义的PropertyValues内部类实现类),其中requiredProperties表示必须的属性,如果ServletConfig没有这些属性,将会抛出异常。
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//BeanWrapper是Spring提供的一个用来操作JavaBean属性的工具,使用它可以直接修改一个对象的属性
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//获取ServletContext类加载器,即它将路径解析为ServletContext资源
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册属性编辑器
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//模板方法,可以在子类实现一些初始化的工作
initBeanWrapper(bw);
//设置初始配置的参数,比如在web.xml中的相关配置等
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 模板方法,子类通过实现该空方法,可以自定义一些初始化的逻辑
initServletBean();
}
抽象类FrameworkServlet参与了Servlet类的初始化工作,并实现了请求的处理逻辑。实现Servlet类的初始化工作,是通过实现父类HttpServletBean的initServletBean()方法来实现。
5.1、initServletBean()方法:
@Override
protected final void initServletBean() throws ServletException {
//省略……
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
//省略……
}
在initServletBean()方法中,最核心的代码如上所示。其中,initWebApplicationContext()用于初始化WebApplicationContext;initFrameworkServlet()用来初始化FrameworkServlet,该方法是模板方法,一般由子类实现。
initWebApplicationContext()方法:
protected WebApplicationContext initWebApplicationContext() {
//获取rootContext
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果已经通过构造函数设置了webApplicationContext
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
//设置rootContext
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
//设置并刷新webApplicationContext
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//当webApplicationContext已经存在ServletContext中时,通过配置在Servlet中的contextAttribute参数获取
wac = findWebApplicationContext();
}
if (wac == null) {
//如果webApplicationContext还没有创建或被注册,则创建一个默认的webApplicationContext
wac = createWebApplicationContext(rootContext);
}
//当ContextRefreshedEvent事件没有触发时调用onRefresh()方法
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
//模板方法,一般在子类重写
onRefresh(wac);
}
}
//将webApplicationContext保存到ServletContext中
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
根据上述代码,我们可以知道,该方法主要做了以下几件事:
获取rootContext
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
//ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
//即,ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE =“org.springframework.web.context.WebApplicationContext.ROOT”
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
Object attr = sc.getAttribute(attrName);
//省略……
return (WebApplicationContext) attr;
}
根据上述的代码,我们可以知道,获取rootContext的核心逻辑就是:
ServletContext#getAttribute(“org.springframework.web.context.WebApplicationContext.ROOT”)
配置或创建webApplicationContext
首先,如果webApplicationContext不为空,说明是通过构造函数进行了注入,这是只需要设置父级容器rootContext,并调用configureAndRefreshWebApplicationContext(cwac)方法进行配置和刷新容器即可。
如果webApplicationContext为空,调用findWebApplicationContext()方法,判断ServletContext中是否已经注册了webApplicationContext,如果注册了则返回webApplicationContext。
@Nullable
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
//如果不存在该属性,直接返回空
if (attrName == null) {
return null;
}
//如果存在该属性,但是获取的WebApplicationContext为空,则抛出异常
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
最后,如果webApplicationContext还是为空,则通过createWebApplicationContext(rootContext)方法创建一个webApplicationContext。代码如下:
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//获取contextClass属性
Class<?> contextClass = getContextClass();
//判断contextClass属性是否合法,即是否是ConfigurableWebApplicationContext的实现类
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//根据contextClass实例化webApplicationContext
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//设置webApplicationContext相关参数
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
//判断是否有自定义的configLocation
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
//设置并刷新webApplicationContext
configureAndRefreshWebApplicationContext(wac);
return wac;
}
在第一种和第三种创建webApplicationContext的方法中,都调用了configureAndRefreshWebApplicationContext(wac)方法。
configureAndRefreshWebApplicationContext(wac)方法
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//设置容器Id,使用默认ID或自定义ID。
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
//设置相关参数
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
//添加监听器SourceFilteringListener。实际监听的是ContextRefreshListener所监听的事件。ContextRefreshListener是FrameworkServlet的内部类,监听ContextRefreshedEvent事件,当接收到消息时调用FrameworkServlet的onApplicationEvent方法,在onApplicationEvent中会调用一次onRefresh方法,并将refreshEventReceived标志设置为true,表示已经refresh过。
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
//初始化环境参数
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
//后置处理器,模板方法。该方法如果被调用,会自动调用refresh()方法
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//刷新
wac.refresh();
}
调用onRefresh()方法
因为在第一种和第三种创建webApplicationContext的方法中,都调用了configureAndRefreshWebApplicationContext(wac)方法,而该方法中又调用了刷新方法,所以,实际上onRefresh()方法只有在第二种创建webApplicationContext的方法中才会被调用。该方法在子类DispatcherServlet中实现。
将webApplicationContext保存到ServletContext中
根据publishContext变量判断是否将创建出来的webApplicationContext设置到ServletContext的属性中,publishContext变量可以在配置Servlet时通过init-param参数进行设置,HttpServletBean初始化时会将其设置到publishContext参数中。
5.2、请求处理方法
首先根据前面《Servlet相关内容》,我们知道从Servlet接口的service()方法开始,然后在HttpServlet的service方法中根据请求的类型不同将请求路由到了doGet、doHead、doPost、doPut、doDelete、doOptions和doTrace七个方法进行请求的处理。在FrameworkServlet抽象类中重写了service、doGet、doPost、doPut、doDelete、doOptions、doTrace方法(除了doHead的所有处理请求的方法),并且增加了PATCH类型的处理。
在前面Servlet相关内容的学习中,我们知道Http请求,首先是到了service()方法进行处理,在FrameworkServlet抽象类中,重写了该方法,代码如下:
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
通过上面的代码,我们知道:FrameworkServlet在处理http请求时,PATCH类型的请求,通过调用processRequest(request, response)方法实现,而其他类型则直接调用了父类(HttpServlet)的service()方法,我们知道在父类的service()方法中,实际上更加请求类型的不同,又调用了对应的doXXX()方法,在FrameworkServlet中又重写了这些方法,所以实际上还是调用了FrameworkServlet类中的doXXX()方法,我们以doGet()方法为例,进行分析:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
结果,我们发现,在FrameworkServlet类中,所有的请求类型(doOptions和doTrace方法除外)都又重新汇聚到了processRequest()方法中进行统一处理了。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取LocaleContextHolder原来保存的localeContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//获取当前请求的localeContext
LocaleContext localeContext = buildLocaleContext(request);
//获取RequestContextHolder原来保存的requestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//获取当前请求的requestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
//异步请求处理
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//将当前请求的LocaleContext和ServletRequestAttributes设置到LocaleContextHolder和RequestContextHolder
initContextHolders(request, localeContext, requestAttributes);
try {
//抽象方法,由子类DispatcherServlet实现,请求的实际处理逻辑
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
//恢复原来的LocaleContext和ServletRequestAttributes
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
//打印日志
logResult(request, response, failureCause, asyncManager);
//发布ServletRequestHandledEvent消息
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
5.3、请求处理的细节
在5.2节中,我们分析了Servlet容器初始化和Http请求处理的核心逻辑,涉及到的localeContext、requestAttributes和ServletRequestHandledEvent消息发布的相关内容还没有详细分析,由于篇幅原因,放到下一篇博客中,和FrameworkServlet的子类DispatcherServlet一起分析。