DispatcherServlet 的继承体系
SpringMvc 是一个具有 Spring 容器(ApplicationContext)的 Servlet。其中,HttpServlet 属于 JDK 的内容,从 HttpServletBean 开始,便属于 Spring 体系中的内容。
- HttpServletBean:XXXBean 是 Spring 框架和其它框架整合时使用,这里将 JDK 中的 Servlet 作为一个框架。
- Aware 接口:由 Spring 的 BeanPostProcessor 进行调用,通过
setXXX()
为 Spring 容器中的 Bean 组件提供功能。例如,当组件希望能够获得整个 Spring 容器时,可以实现 ApplicationContextAware 接口。- ApplicationContextAware:告诉 Spring,需要 ApplicationContext 容器
- EnvironmentAware:告诉 Spring,需要 Environment(包括配置文件和环境变量)
- Capable 接口:告诉 Spring,可以提供什么东西。如果 Spring 需要这个东西,会通过
getXXX()
来获取。- EnvironmentCapable:告诉 Spring,可以提供 Environment 配置信息
HttpServletBean 源码解析
重写 GenericServlet 的 init()
方法,并提供 initServletBean()
方法作为新的扩展点,以取代 GenericServlet 提供的 init()
扩展点。
注意:init()
方法使用了 final 关键字进行修饰,因此子类无法重载 init()
方法。这样便从 Servlet 的规范向 Spring 的规范靠近。
init 流程
public abstract class HttpServletBean
extends HttpServlet
implements EnvironmentCapable, EnvironmentAware {
// 重写GenericServlet中的无参init()方法
@Override
public final void init() throws ServletException {
// 从ServletConfig到PropertyValues
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(),
this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// this实际会代表DispatcherServlet
// BeanWrapper用来操作DispatcherServlet中的属性
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader =
new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class,
new ResourceEditor(resourceLoader,
getEnvironment()));
initBeanWrapper(bw);
// 将PropertyValues设置到DispatchServlet中
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 提供给子类的扩展点,子类可以接着已有的逻辑进行扩展,
// 而不用去重写init()方法,然后把这段内容拷贝过去
initServletBean();
}
protected void initServletBean() throws ServletException {
}
}
内部类 ServletConfigPropertyValues
键值对(key-value)在不同的框架中由不同的对象进行封装,在 Servlet 中保存在 ServletConfig 的 initParameter 中,在 Spring 中封装成对象 PropertyValue。
ServletConfigPropertyValues 这个类的作用便是将保存在 ServletConfig 中的配置项信息添加到 PropertyValues 中,并检查这些 initParameter 是否满足 requiredProperties 集合的全部要求,如果不满足则抛出异常。
private static class ServletConfigPropertyValues extends MutablePropertyValues {
public ServletConfigPropertyValues(ServletConfig config,
Set<String> requiredProperties) throws ServletException {
Set<String> missingProps = CollectionUtils.isEmpty(requiredProperties) ?
null : new HashSet<>(requiredProperties);
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.
if (!CollectionUtils.isEmpty(missingProps)) {
// throw ...
}
}
BeanWrapper 是什么?怎么用?
BeanWrapper 是 Spring 提供的用来操作 JavaBean 属性的工具,使用 BeanWrapper 可以直接修改一个对象的属性,例如
public class BeanWrapperDemo {
public static void main(String[] args) {
User user = new User(1, "root", 18);
System.out.println("user = " + user);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(user);
// 获取 JavaBean 中的属性值,借助 Spring 的类型系统进行类型强转?
Integer userId = (Integer) bw.getPropertyValue("userId");
System.out.println("userId = " + userId);
String nickname = (String) bw.getPropertyValue("nickname");
System.out.println("nickname = " + nickname);
HashMap<String, Object> map = new HashMap<>();
map.put("userId", 10);
map.put("nickname", "admin");
// 故意添加一个不匹配的字段
map.put("name", "error");
PropertyValues propertyValues = new MutablePropertyValues(map);
// User对象并没有name属性,如果不添加第二个参数,那么会报错
bw.setPropertyValues(propertyValues, true);
System.out.println("user = " + user);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class User {
private Integer userId;
private String nickname;
private Integer age;
}
FrameworkServlet 源码解析
从父类 HttpServletBean 的 init()
可以看出,子类的 init()
无法重写,因此方法扩展点就是 HttpServletBean 中的 initServletBean()
。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
private static final String INIT_PARAM_DELIMITERS = ",; \t\n";
@Override
protected final void initServletBean() throws ServletException {
// 核心,初始化SpringMvc容器
this.webApplicationContext = initWebApplicationContext();
// 留给子类的扩展点
initFrameworkServlet();
}
protected WebApplicationContext initWebApplicationContext() {
// 获取Spring根容器
// 在前面某个时间点,会将WebApplicationContext对象设置到ServletContext的属性中(attributes)
// 以 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 为key,所以这里从 ServletContext 中取值即可
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) {
wac = findWebApplicationContext();
}
if (wac == null) {
// 一般情况下会进入到这里来创建
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 当ContextRefreshedEvent事件没有触发时触发onRefresh方法,
// 上面对wac赋值的三种情况中,只有第二种情况会进入到该方法中
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// 将ApplicationContext保存到ServletContext中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
//要调用重载的其它方法,这里只能强转(重载是编译时多态)
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
// throw ...
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
}
WebApplicationContext 接口
public interface WebApplicationContext extends ApplicationContext {
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
String SCOPE_REQUEST = "request";
String SCOPE_SESSION = "session";
String SCOPE_APPLICATION = "application";
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
@Nullable
ServletContext getServletContext();
}
ConfigurableWebApplicationContext 接口
- ServletContext
- ServletConfig
- Namespace
- ConfigLocations
public interface ConfigurableWebApplicationContext
extends WebApplicationContext,
ConfigurableApplicationContext {
String APPLICATION_CONTEXT_ID_PREFIX = WebApplicationContext.class.getName() + ":";
String SERVLET_CONFIG_BEAN_NAME = "servletConfig";
// WebApplicationContext接口中有getServletContext()方法
void setServletContext(@Nullable ServletContext servletContext);
void setServletConfig(@Nullable ServletConfig servletConfig);
ServletConfig getServletConfig();
void setNamespace(@Nullable String namespace);
String getNamespace();
void setConfigLocation(String configLocation);
void setConfigLocations(String... configLocations);
String[] getConfigLocations();
}
DispatcherServlet 源码解析
按照和前面相同的分析,DispatcherServlet 应该使用 initFrameworkServlet()
来进行初始化方法的扩展,但是实际却并没有使用,使用了 onRefresh()
进行扩展。(为什么这么设计?)
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);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
}