Spring Web框架架构的主要部分是DispatcherServlet。也就是本文中重点介绍的对象。
在本文的第一部分中,我们将看到基于Spring的DispatcherServlet的主要概念:前端控制器模式。第二部分将专门介绍Spring应用程序中的执行链。接下来是DispatcherServlet类的解释。在最后一部分,我们将尝试开发一个自定义的dispatcher servlet。
请注意,本文分析的DispatcherServlet来自Spring的5.0.0.RC3版本。如果使用不同的版本,则可能需要进行几个调整,其实因为分析的都是比较固定的东西,很少有改的。
什么是前端控制器模式?
在进入DispatcherServlet之前,我们需要了解一些关于它的概念基础。DispatcherServlet所隐含的关键概念其实就是前端控制器模式。
此模式为Web应用程序提供了一个中心入口点。该集中入口点将系统组件的共同特征进行重新组合。我们可以在那里找到安全资源,语言切换,会话管理,缓存或输入过滤的处理程序。这样做的一个很大的好处是:这个共同的入口点有助于避免代码重复。
因此,从技术上讲,前端控制器模式由一个捕获所有传入请求的类组成。之后,分析每个请求以知道哪个控制器以及哪个方法应该来处理该请求。
前端控制器模式有助于对以下询问做出最佳响应:
如何集中授权和认证?
如何处理正确的视图渲染?
如何使用URL重写映射将请求发送到适当的控制器?
这个前台控制器模式包含5名参与者:
客户端:发送请求。
控制器:应用程序的中心点,捕获所有请求。
调度员:管理视图的选择,以呈现给客户端。
视图:表示呈现给客户端的内容。
帮助:帮助查看和/或控制器完成请求处理。
什么是DispatcherServlet的执行链?
由标题可以看到,前端控制器模式有自己的执行链。这意味着它有自己的逻辑来处理请求并将视图返回给客户端:
请求由客户端发送。它到达作为Spring的默认前端控制器的DispatcherServlet类。
DispatcherServlet使用请求处理程序映射来发现将分析请求的控制器(controller)。接口org.springframework.web.servlet.HandlerMapping的实现返回一个包含org.springframework.web.servlet.HandlerExecutionChain类的实例。此实例包含可在控制器调用之前或之后调用的处理程序拦截器数组。你可以在Spring中有关于拦截器的文章中了解更多的信息。如果在所有定义的处理程序映射中找不到HandlerExecutionChain,这意味着Spring无法将URL与对应的控制器进行匹配。这样的话会抛出一个错误。
现在系统进行拦截器预处理并调用由映射处理器找到的相应的controller(其实就是在找到的controller之前进行一波拦截处理)。在controller处理请求后,DispatcherServlet开始拦截器的后置处理。在此步骤结束时,它从controller接收ModelAndView实例(整个过程其实就是 request请求->进入interceptors->controller->从interceptors出来->ModelAndView接收)。
DispatcherServlet现在将使用的该视图的名称发送到视图解析器。这个解析器将决定前台的展现内容。接着,它将此视图返回给DispatcherServlet,其实也就是一个“视图生成后可调用”的拦截器。
最后一个操作是视图的渲染并作为对客户端request请求的响应。
什么是DispatcherServlet?
通过上面讲到的前端控制器模式,我们可以很轻易的知道DispatcherServlet是基于Spring的Web应用程序的中心点。它需要传入请求,并在处理程序映射,拦截器,控制器和视图解析器的帮助下,生成对客户端的响应。所以,我们可以分析这个类的细节,并总结出一些核心要点。
下面是处理一个请求时DispatcherServlet执行的步骤:
DispatcherServlet是一个位于org.springframework.web.servlet包中的类,并扩展了同一个包中的抽象类FrameworkServlet。它包含一些解析器的私有静态字段(用于本地化,视图,异常或上传文件),映射处理器:handlerMapping和处理适配器:handlerAdapter(进入这个类的第一眼就能看到的)。DispatcherServlet非常重要的一个核心点就是是初始化策略的方法(protected void initStrategies(ApplicationContext context))。在调用onRefresh方法时调用此方法。最后一次调用是在FrameworkServlet中通过initServletBean和initWebApplicationContext方法进行的(initServletBean方法中调用initWebApplicationContext,后者调用onRefresh(wac))。initServletBean通过所提供的这些策略生成我们所需要的应用程序上下文。其中每个策略都会产生一类在DispatcherServlet中用来处理传入请求的对象。
基于篇幅,有些代码就不给贴示了,请在相应版本的源码中自行对照查找,此处只给一部分源码:
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
*
May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
需要注意的是,如果找的结果不存在,则捕获异常NoSuchBeanDefinitionException(下面两段代码的第一段),并采用默认策略。如果在DispatcherServlet.properties文件中初始定义的默认策略不存在,则抛出BeanInitializationException异常(下面两段代码的第二段)。默认策略如下:
/**
* Initialize the LocaleResolver used by this class.
*
If no bean is defined with the given name in the BeanFactory for this namespace,
* we default to AcceptHeaderLocaleResolver.
*/
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug(“Using LocaleResolver [” + this.localeResolver + “]”);
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug(“Unable to locate LocaleResolver with name ‘” + LOCALE_RESOLVER_BEAN_NAME +”’: using default [” + this.localeResolver + “]”);
}
}
}
抛出异常后调用getDefaultStrategy(因为容器里都是单例的存在,所以只需要判断基于这个接口的默认实现实例size为1即可,两个以上还能叫默认么,都有选择了):
/**
* Return the default strategy object for the given strategy interface.
* The default implementation delegates to {@link #getDefaultStrategies},
* expecting a single object in the list.
* @param context the current WebApplicationContext
* @param strategyInterface the strategy interface
* @return the corresponding strategy object
* @see #getDefaultStrategies
*/
protected T getDefaultStrategy(ApplicationContext context, Class strategyInterface) {
List strategies = getDefaultStrategies(context, strategyInterface);
if (strategies.size() != 1) {
throw new BeanInitializationException(
“DispatcherServlet needs exactly 1 strategy for interface [” + strategyInterface.getName() + “]”);
}
return strategies.get(0);
}
/**
* Create a List of default strategy objects for the given strategy interface.
* The default implementation uses the “DispatcherServlet.properties” file (in the same
* package as the DispatcherServlet class) to determine the class names. It instantiates
* the strategy objects through the context’s BeanFactory.
* @param context the current WebApplicationContext
* @param strategyInterface the strategy interface
* @return the List of corresponding strategy objects
*/
@SuppressWarnings(“unchecked”)
protected List getDefaultStrategies(ApplicationContext context, Class strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
Class