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()方法@Overridepublic final void init() throws ServletException {// 从ServletConfig到PropertyValuesPropertyValues 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";@Overrideprotected 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";@NullableServletContext 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 {@Overrideprotected 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);}
}