![cover](https://img-blog.csdnimg.cn/20200914181950657.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmcwOTA3,size_16,color_FFFFFF,t_70#pic_center)
springmvc 的Servlet WebApplicationContext
1:初始化入口在哪里?1.1:熟悉的DispatcherServletorg.springframework.web,.servlet.DispatcherServlet的springmvc的入口servlet,继承了javax.http.HttpServlet,其主要的继承关系如下图:我们知道,按照servlet规范,web容器加载servlet时会调用其init方法进行,而org.spring
1:初始化入口在哪里?
1.1:熟悉的DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
是springmvc的入口servlet,继承了javax.http.HttpServlet
,继承关系如下图:
我们知道,按照servlet规范,web容器加载servlet时会调用其init(ServletConfig)方法进行,而
org.springframework.web.servlet.DispatcherServlet
的init(ServletConfig)方法是在其父类javax.servlet.GenericServlet
类中,源码如下:
javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
public void init(ServletConfig config) throws ServletException {
this.config = config;
// <1>
this.init();
}
<1>
处会执行到org.springframework.web.servlet.HttpServletBean#init
,源码如下:
org.springframework.web.servlet.HttpServletBean#init
public final void init() throws ServletException {
// 2023-04-03 13:03:38
initServletBean();
}
2023-04-03 13:03:38
处就比较关键了,负责创建springMVC的相关bean,并创建springMVC的IOC容器 web applicationcontext,方法initServletBean();
是一个模板方法,其逻辑实现是在org.springframework.web.servlet.FrameworkServlet
中,源码如下:
@Override
protected finalvoid initServletBean() throws ServletException {
...
try {
// 初始化WebApplicationContext
this.webApplicationContext = initWebApplicationContext();
// 模板方法,子类使用
initFrameworkServlet();
}
...
}
其中代码this.webApplicationContext = initWebApplicationContext();
就是负责初始化Servlet WebApplicationContext
的,也就是我们要找的入口程序
了。接下来就是org.springframework.web.servlet.DispatcherServlet
,主要负责初始化springmvc的九大组件
。
1.2:入口init(ServletConfig)
web容器在初始化servlet时会调用方法init(ServletConfig),在javax.servlet.HttpServlet中提供具体实现,如下:
javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
public void init(ServletConfig config) throws ServletException {
this.config = config;
// 2023-04-03 13:12:54
this.init();
}
接着2023-04-03 13:12:54
调用的初始化方式init
是在org.springframework.web.servlet.HttpServletBean
中,源码如下:
org.springframework.web.servlet.HttpServletBean#init
public final void init() throws ServletException {
// <1>
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// <2>
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// <3>
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
}
}
// <4>
initServletBean();
}
<1>
处代码是将ServletConfig信息封装到ServletConfigPropertyValues
中,这是一个静态内部类,<2>
处代码是将org.springframework.web.servlet.DispatcherServlet
封装为org.springframework.beans.BeanWrapper
,是spring提供的操作java bean的一个工具类,接口定义如下:
public interface BeanWrapper extends ConfigurablePropertyAccessor {
void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
int getAutoGrowCollectionLimit();
Object getWrappedInstance();
Class<?> getWrappedClass();
PropertyDescriptor[] getPropertyDescriptors();
PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
}
其唯一的实现类是org.springframework.beans.BeanWrapperImpl
。<3>
处是注册属性编辑器。<4>
处是一个模板方法,调用子类,这里的具体实现在子类org.springframework.web.servlet.FrameworkServlet
类中。下面再简单介绍下<1>
处源码,相关注释已经直接在源码中添加:
private static class ServletConfigPropertyValues extends MutablePropertyValues {
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
// requiredProperties为强制通过init-param配置的参数,通过
// org.springframework.web.servlet.HttpServletBean#addRequiredProperty进行添加
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
// 如果是存在强制缺失属性,则将当前属性从强制缺失集合中移除,代表已经不缺失了,这里不用管是否包含,直接移除即可
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
// 如果仍然有缺失属性则抛出javax.servlet.ServletException
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
}
目前我没有找到这个requiredProperties
有什么使用场景,如果是哪位朋友,在工作中用到了,欢迎给我留言告知,不胜感激。
2:Spring Servlet WebApplicationContext初始化
2.1: initServletBean()
org.springframework.web.servlet.FrameworkServlet
实现了在org.springframework.web.servlet.HttpServletBean
中定义的模板方法initServletBean
,因此加载org.springframework.web.servlet.DispatcherServlet
时会调用到该方法,该方法如下:
protected final void initServletBean() throws ServletException {
try {
// <1>
this.webApplicationContext = initWebApplicationContext();
// <2>
initFrameworkServlet();
}
}
<1>
处代码就是用来初始化Spring Servlet WebApplicationContext
容器对象的。<2>
处是一个模板方法,定义如下:
protected void initFrameworkServlet() throws ServletException {
}
但是目前并没有子类重写该方法,可以先忽略之。接下来详细讲解<1>
处代码。
2.2: initWebApplicationContext()
该方法负责对Spring Servlet WebApplicationContext
进行初始化,并设置到web容器的ServletContext上下文对象中,源码如下:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// <1>
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);
}
}
}
// <2>
if (wac == null) {
wac = findWebApplicationContext();
}
// <3>
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
// <4>
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
// <5>
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
<1>
处代码当使用的是org.springframework.web.servlet.FrameworkServlet
的带WebApplicationContext参数的构造函数的时候,源码如下:
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
或者是当自己是spring容器中的bean时,因为实现了org.springframwork.context.ApplicationContextAware
接口,因此会回调setApplicationContext(ApplicationContext applicationContext)
方法进行设置,但是一般不会存在这种情况。这两种情况都不是核心流程,可以忽略,所以<1>
处代码暂时不用深究。<2>
处是源码如下:
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
getContextAttribute()
就是获取名称为contextAttribute
的init-param
参数,配置方式如下:
<init-param>
<param-name>contextAttribute</param-name>
<param-value>contextAttributeXXXXX</param-value>
</init-param>
配置后以值为key去ServletContext中获取对应的WebApplicationContext,如果没有的话,则会抛出类似如下的错误:
java.lang.IllegalStateException: No WebApplicationContext found: initializer not registered?
at org.springframework.web.servlet.FrameworkServlet.findWebApplicationContext(FrameworkServlet.java:679)
at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:620)
...
<3>
处代码就是本文分析的核心,是负责创建Spring Servlet WebApplicationContext
容器对象的,单起一部分讲解。<4>
处代码onRefresh
也是一个模板方法,具体实现在org.springframework.web.servlet.DispatcherServlet
中,是用来初始化spring mvc的九大组件的,源码如下:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
暂时不做深究。 <5>
处代码通过publishContext
变量判断是否需要将创建的Spring Servlet WebApplicationContext设置到web容器的ServletContext的上下文中,默认为true
,也可以通过init-param
在web.xml
中设置,如下:
<init-param>
<param-name>publishContext</param-name>
<param-value>false</param-value>
</init-param>
一般不会进行设置,除非有特别的需求,目前俺还没有遇到过需要设置的,如果哪位同学遇到过这种场景,跪求留言分享
。接下来详细分析下<3>
处代码。
2.3: createWebApplicationContext(rootContext)
源码如下:
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
最终调用参数为ApplicationContext
类型的重载方法:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// <1>
Class<?> contextClass = getContextClass();
// <2>
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");
}
// <3>
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// <4>
wac.setParent(parent);
// <5>
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// <6>
configureAndRefreshWebApplicationContext(wac);
return wac;
}
<1>
处代码获取要创建ApplicationContext的类型,这里默认是org.springframework.web.context.support.XmlWebApplicationContext.class
类型,一般不会修改。<2>
处判断contextClass
是否为org.springframework.web.context.ConfigurableApplicationContext
类型,如果不是的话则抛出org.springframework.context.ApplicationContextException
异常。<3>
处通过Class对象创建实例对象。<4>
处设置父容器为Root WebApplicationContext
,关于ROOT WebApplicationContext
可以参考这里。<5>
处获取在web.xml
中配置的contextConfigLocation
,可能如下配置:
<servlet>
<servlet-name>letsGO</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/letsGO-servlet.xml</param-value>
</init-param>
</servlet>
则这里获取的值就是WEB-INF/letsGO-servlet.xml
,即springMVC的配置文件。<6>
处代码是对Spring Servlet WebApplicationContext
进行配置和初始化,接下来对这一部分进行详细说明。
2.4: configureAndRefreshWebApplicationContext(wac)
源码如下:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// <1>
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());
}
}
// <2>
wac.setServletContext(getServletContext());
// <3>
wac.setServletConfig(getServletConfig());
// <4>
wac.setNamespace(getNamespace());
// <5>
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
// <6>
postProcessWebApplicationContext(wac);
applyInitializers(wac);
// <7>
wac.refresh();
}
<1>
处是设置容器id,如果是默认策略生成的id则分为两种情况,第一种是用户配置了contextId
,配置方式为在web.xml
给ServletContext配置参数即context-param
,可能为如下配置:
<context-param>
<param-name>contextId</param-name>
<param-value>dongsir-root-web-application-context-id</param-value>
</context-param>
则此处获取的值就是dongsir-root-web-application-context-id
,若没有配置则生成一个id并设置。<2>
处设置web容器的ServletContext对象。<3>
处设置ServletConfig对象,<4>
处设置命名空间,<5>
通过注册SourceFilteringListener
完成当<7>
处代码执行完毕后对ContextRefreshListener
的代理调用,SourceFilterListener
代码如下:
public class SourceFilteringListener implements GenericApplicationListener, SmartApplicationListener {
private final Object source;
@Nullable
private GenericApplicationListener delegate;
protected void onApplicationEventInternal(ApplicationEvent event) {
if (this.delegate == null) {
throw new IllegalStateException(
"Must specify a delegate object or override the onApplicationEventInternal method");
}
this.delegate.onApplicationEvent(event);
}
}
其中private GenericApplicationListener delegate
就是被代理的ContextRefreshListener
,最终调用代码onApplicationEventInternal
完成被代理监听器的调用。<6>
处是创建Spring Servlet WebApplicationContext
完毕后的后置处理模板方法,目前没有子类实现改后置处理方法。<7>
处是刷新容器。
更多推荐
所有评论(0)