Servlet 生命周期从创建到销毁的过程中会执行如下三个方法:
- init() - 负责初始化 Servlet 对象。在 Servlet 生命周期中只会调用一次。
- service() - 负责响应客户的请求。每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service 方法中两个参数,分别是 ServletRequest 和 ServletResponse,用于传递 http 请求和回写。
- destory() - 负责销毁 Servlet 对象。在 Servlet 生命周期中只会调用一次。
可以看到在Servlet的继承结构中一共有5个类 ,GenericServlet和HttpServlet在java里,流程比较通俗易懂,不再多做叙述 ,剩下的三个类 HttpServletBean
、FrameworkServlet
和DispatcherServlet
是Spring MVC中的, 本章主要讲解这三个类的创建过程。
这三个类直接实现三个接口:
EnvironmentCapable
、EnvironmentAware
和ApplicationContextAware
。 XXXAware在spring里表示对XXX可以感知, 通俗点解释就是:如果在某个 类里面想要使用spring的一些东西,就可以 通过实现XXXAware 接口告诉spring, spring看到后就会给你送过来 ,而接收的方式是通过实现接口唯一的方法 set-XXX。 比如,有一个类 想要使用当前的ApplicationContext
, 那么我们只需要让它实现ApplicationContextAware
接口 ,然后实现接口中唯一的方法void setApplicationContext (ApplicationContext applicationContext)
就可以了,spring会自动调用这个方法将applicationContext
传给我们, 我们只需要接收就可以 了!很方便吧!EnvironmentCapable
, 顾名思义,当然就是具有Environment
的能力,也就是可以提供Environment
, 所以EnvironmentCapable
唯一的方法是EnvironmentgetEnvironment()
, 用千实现EnvirorunentCapable
接口的类,就是告诉spring它可以提供Environment
, 当spring 需要Environment
的时候就会调用其getEnvironment
方法跟它要。
了解了Aware
和Capable
的意思,下面再来看一下ApplicationContext
和Environment
。 前者相信大家都很熟悉了,后者是环境的意思, 具体功能与之前讲过的ServletContext
有点类似。 实际上在HttpServletBean
中Environment
使用的是Standard-Servlet-Environment (在createEnvironment
方法中创建),这里确实封装了ServletContext
, 同时还封装了ServletConfig
、JndiProperty
、 系统环境变量和系统属性, 这些都封装到了其propertySources
属性下。
Spring版本
Spring 5.X
HttpServletBean
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {
// 通过ServletConfig找到web.xml中的init-param参数写入ServletConfigPropertyValues
//如果是java config方式会跳出
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 将 dispatcherServlet 对象包装成 BeanWrapper,可以便于设置属性
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 加载文件资源,这里可以理解成加载<init-param>中指定的xml文件,再交由BeanWrapper进行加载
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;
}
}
//模板方法,子类实现
// 交由子类(FrameworkServlet)来进行其特有的初始化工作
initServletBean();
}
FrameworkServlet
FrameworkServlet
继承自 HttpServletBean
,实现了initServletBean()
方法。FrameworkServlet
在继承体系结构中,在 Servlet 与 SpringMVC 起到了承上启下的作用,它负责初始化 WebApplicationContext,还负责重写了 Servlet 生命周期中另外两个重要方法——service()
和destory()
,并改写了doGet()
、doPost()
等 http 方法,统一调用processHandler()
方法来处理所有 http 请求,子类必须实现doService
来处理请求。
@Override
protected final void initServletBean() throws ServletException {
.......省略..........
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
........省略..........
}
核心代码只有两行,初始化webApplicationContext
与初始化FrameworkServlet
,其中initFrameworkServlet();
又是一个模板方法,交由子类进行自定义实现。
protected WebApplicationContext initWebApplicationContext() {
//获取root Context
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果通过构造方法创建了 webApplicationContext
//java config方式在构造器里就初始化了 webApplicationContext,适用servlet3.0+
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//假如WebApplicationContext已经存在于servletContext,尝试获取
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
//如果WebApplicationContext确实还没被创建,直接创建一个
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// DispatcherSevlet 初始化工作的入口
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
这里要插播一个小知识点,Spring的几个上下文 里简单提到了容器调用web.xml
中配置的ContextLoaderListener
,初始化WebApplicationContext
上下文环境(即IOC容器),WebApplicationContext
被称为根上下文,spring以 WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE
为属性Key,将其存储到ServletContext
中。
注:我们常说的WebApplicationContext
,servletContext
也可以理解为容器
这里我们可以简单看一下代码它是怎么被塞进去的,Spring会通过ContextLoaderListener
调用contextInitialized()
进行初始化:
//ContextLoaderListener
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
//ContextLoader
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
。。。。。省略
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
//创建WebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//这里进行了set操作 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
。。。。。省略
}
}
现在回到FrameworkServlet
的初始化方法中来,我们从源码上可以看出WebApplicationContext
的设置有三种方法:
第一种,通过构造方法已经传递了WebApplicationContext
参数,这种情况我们只需要再做一些设置即可。这种方式得多亏了servlet 3.0新增的ServletContext#addServlet()
方法,我们可以直接调用这个方法创建一个DispatcherServlet
实例,这意味着我们可以通过构造器注入已经准备好的WebApplicationContext
到DispatcherServlet
,即完成Spring MVC容器的注入,再将root WebApplicationContext设置为parent;
第二种,WebApplicationContext
已经在ServletContext
中,这里会通过当前servlet配置的contextAttribute
属性查找一个自定义的WebApplicationContext
,将其作为当前servlet的容器,例如,在ServletContext
中有一个haha的WebApplicationContext
,可以将它配置到Spring MVC中:
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>haha</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
第三种,通过xml配置一般都是走这种方式,直接创建一个WebApplicationContext
,
//FrameworkServlet
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
//FrameworkServlet
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//获取创建类型
Class<?> contextClass = getContextClass();
//检查创建类型
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");
}
//具体创建
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
//将root WebApplicationContext设置为parent
wac.setParent(parent);
// 获取当前servlet配置的contextConfigLocation,在web.xml中配置的
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 读取当前WebApplicationContext配置的Spring相关的bean,并进行初始化
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
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());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
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());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
//提前将 ServletContext 和 ServletConfig 提前注入到 Environment 变量中
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
// 后置处理,子类可以覆盖进行一些自定义操作。在 Spring MVC 未使用到,是个空方法。
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//接口方法,实现类重写,下文再叙
wac.refresh();
}
wac设置了监听器,SourceFilteringListener
可以根据入参来选择,所以实际监听的是ContextRefreshListener
所监听的事件,当接收到消息时调用FrameworkServlet.this.onApplicationEvent(event)
方法,将refreshEventReceived
设置为true,表示已经refresh过。
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
onRefresh(event.getApplicationContext());
}
}
在FrameworkServlet#initWebApplicationContext
方法的末尾我们可以看到会根据refreshEventReceived来判断是否需要refresh,当使用第一种第三种,都调用了FrameworkServlet#configureAndRefreshWebApplicationContext()
方法,所以已经fresh过,只有第二种才需要在这里调用onRefresh方法,不过通过哪种方式,onRefresh只会被调用一次,DispatcherServlet就是通过重写这个模板方法实现初始化。
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
DispatcherServlet
onRefresh()
是DispatcherServlet
的入口方法,initStrategies()
用于初始化9个组件
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
//初始化文件上传处理类
initMultipartResolver(context);
//初始化本地化Resolver
initLocaleResolver(context);
//初始化主题Resolver
initThemeResolver(context);
//初始化请求映射关系。
initHandlerMappings(context);
//根据Handler的类型定义不同的处理规则。
initHandlerAdapters(context);
//初始化异常处理的handler
initHandlerExceptionResolvers(context);
//将指定的ViewName按照定义的RequestToViewNameTranslators替换成想要的格式
initRequestToViewNameTranslator(context);
//用于将View解析成页面
initViewResolvers(context);
//生成FlashMap管理器
initFlashMapManager(context);
}
我们以initLocaleResolver(context);
为例,可以发现,先从容器中取名为localeResolver
或者LocaleResolver.class
类型的bean,如果没有则走默认策略。
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.localeResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
}
}
}
getDefaultStrategy
方法调用了getDefaultStrategies()
@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
真正执行创建的方法是ClassUtils.forName()
,通过代码我们可以发现是通过defaultStrategies
这个配置文件取得相关的类名进行创建bean,这个变量可以追溯到静态块中初始化的内容,我们可以发现原来是取跟它同包下的一个DispatcherServlet.properties
文件
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
DispatcherServlet.properties
文件:
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
我们可以看到除了multipartResolver
其他都有相应的默认值,因为不是所有的项目都需要上传,或者不一定使用multipart的方式,也可能用appache的包进行上传。
至此DispatcherServlet
的初始化就完成了。
小结
- Spring MVC中Servlet分为三个层次,
HttpServletBean
,FrameworkServlet
,DispatcherServlet
。HttpServletBean
继承自Java的HttpServlet
,作用是将Servlet中的配置设置到相应的属性中;FrameworkServlet
初始化了Spring MVC的容器,即WebApplicationContext
;DispatcherServlet
初始化了自身的9大组件。 - Spring容器也称IOC容器称为
Root WebApplicationContext
,是Spring MVCWebApplicationContext
的父容器,Root WebApplicationContext
是ServletContext
的一个属性,通过ContextLoaderListener
初始化并设置。
参照
《看透springMvc源代码分析与实践》