精尽 Spring MVC 源码分析 —— 容器的初始化(二)之 Servlet WebApplicationContext 容器
Servlet WebApplicationContext 容器的初始化,是在 DispatcherServlet 初始化的过程中执行。
DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
DispatcherServlet:负责初始化Springmvc的各个组件,以及处理客户端的请求
FrameworkServlet:负责初始化Spring Servlet WebApplicationContext容器
HttpServletBean:负责将ServletConfig设置到当前Servlet对象中
1、HttpServletBean
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {}
1.1、因为实现类EnvironmentAware接口,所以会自动注入environment属性
1.2、
//必须配置的属性的集合
private final Set<String> requiredProperties = new HashSet<>(4);
//可通过下列方法
protected final void addRequiredProperty(String property) {
this.requiredProperties.add(property);
}
1.3、init方法
负责将ServletConfig设置到当前Servlet对象中
// HttpServletBean.java
public final void init() throws ServletException {
//解析 <init-param /> 标签,封装到 PropertyValues pvs 中 见1.3.1
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中
//BeanWrapper是Spring提供的一个用来操作java bean属性的工具,使用它可以直接修改一个对象的属性
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//空实现,留给子类覆盖
initBeanWrapper(bw);
//以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中
bw.setPropertyValues(pvs, true);
} catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
//子类来实现,实现自定义的初始化逻辑。目前,有具体的代码实现,在frameworkServlet中。见2.3
initServletBean();
}
1.3.1 ServletConfigPropertyValues是HttpServletBean的内部类
private static class ServletConfigPropertyValues extends MutablePropertyValues {
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
// 获得必需的属性的集合
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
//遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中,并从 missingProps 移除
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
// 添加到 ServletConfigPropertyValues 中
addPropertyValue(new PropertyValue(property, value));
// 从 missingProps 中移除
if (missingProps != null) {
missingProps.remove(property);
}
}
//因为以上步骤,每获取到一个值,若是必需的就会删除,所以若不为空,则代表有必需的值未获取到,得报错
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
}
2、FrameworkServlet
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {}
负责初始化Spring WebApplicationContext容器
2.1、关键对象
//创建的 WebApplicationContext 类型
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
//配置文件的地址
private String contextConfigLocation;
//WebApplicationContext 对象
private WebApplicationContext webApplicationContext;
//默认的WebApplicationContext类型
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
2.2、Servlet WebApplicationContext容器,它有四种方式进行创建
方式一:
通过构造方法
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
方式二:因为实现了ApplicationContextAware接口
通过其中的set方法注入
方式三:findWebApplicationContext() 方法
方式四:createWebApplicationContext(WebApplicationContext parent) 方法
2.3、initServletBean方法
进一步初始化当前Servlet对象,当然,重心是在初始化Servlet WebApplicationContext容器,即initWebApplicationContext()方法
protected final void initServletBean() throws ServletException {
// 打日志
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
// 记录开始时间
long startTime = System.currentTimeMillis();
try {
// 初始化 WebApplicationContext 对象,见2.4
this.webApplicationContext = initWebApplicationContext();
// 空实现。子类有需要,可以实现该方法,实现自定义逻辑
initFrameworkServlet();
} catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
// 打日志
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
// 打日志
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
2.4、initWebApplicationContext()方法
protected WebApplicationContext initWebApplicationContext() {
//获得根 WebApplicationContext 对象,根据WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE获取root WebApplicationContext容器
//上节在创建root WebApplicationContext的时候使用过
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
//获得 WebApplicationContext wac 变量
WebApplicationContext wac = null;
// 第一种情况,如果构造方法已经传入 webApplicationContext 属性,则直接使用
if (this.webApplicationContext != null) {
// 赋值给 wac 变量
wac = this.webApplicationContext;
// 如果是 ConfigurableWebApplicationContext 类型,并且未激活,则进行初始化
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) { // 未激活
// 设置 wac 的父 context 为 rootContext 对象
if (cwac.getParent() == null) {
cwac.setParent();
}
// 配置和初始化 wac 见2.7
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象
if (wac == null) {
wac = findWebApplicationContext();
}
// 第三种,创建一个 WebApplicationContext 对象
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
//如果未触发刷新事件,则主动触发刷新事件
if (!this.refreshEventReceived) {
//实现在DispathcerServlet中
onRefresh(wac);
}
//将 context 设置到 ServletContext 中
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
2.5、findWebApplicationContext()方法
protected WebApplicationContext findWebApplicationContext() {
//获取属性名
String attrName = this.getContextAttribute();
if (attrName == null) {
return null;
} else {
//根据属性名从ServletContext中获取
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
} else {
return wac;
}
}
}
//一般也不会去设置这个属性
public String getContextAttribute() {
return this.contextAttribute;
}
2.6、createWebApplicationContext()方法
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//获取ContextClass,在构造方法中有将默认的XmlApplicationContext.class赋予它
Class<?> contextClass = this.getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + this.getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "', using parent context [" + parent + "]");
}
//如果ConfigurableWebApplicationContext不是contextClass的父类,则抛异常
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
} else {
//初始化contextClass,并设置相应的属性
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(this.getEnvironment());
wac.setParent(parent);
String configLocation = this.getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
//见2.7
this.configureAndRefreshWebApplicationContext(wac);
return wac;
}
}
2.7、configureAndRefreshWebApplicationContext方法
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// 如果 wac 使用了默认编号,则重新设置 id 属性
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 情况一,使用 contextId 属性
if (this.contextId != null) {
wac.setId(this.contextId);
// 情况二,自动生成
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
//设置 wac 的 servletContext、servletConfig、namespace 属性
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
//添加监听器 SourceFilteringListener 到 wac 中
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
//执行处理完 WebApplicationContext 后的逻辑。目前是个空方法,暂无任何实现
postProcessWebApplicationContext(wac);
//执行自定义初始化 context
applyInitializers(wac);
//刷新 wac ,从而初始化 wac
wac.refresh();
}