03-Spring mvc: 简介 + DispatcherServlet

基于Spring mvc官方文档
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html

Spring mvc-简介 + DispatcherServlet

从学习Spring mvc开始, 应用前面的RPC框架, 来开发一个web网站.
GitHub>>



1.简介
Spring web MVC 是一个基于Servlet API构建的新颖的web框架, 并且在最开始就被包含在了Spring Framework项目内. 相较于正式名称 “Spring Web MVC“, “Spring MVC“这个名字更为人们所熟知.



2.DispatcherServlet
就像其他的web框架一样, Spring MVC也是围绕着一个核心的Servlet (DispatcherServlet)而设计的. DispatcherServlet 为那些可配置的组件在处理实际工作时提供了一系列的共享的算法. 这种模型非常灵活, 支持各种各样的工作方式.

和所有的servlet一样, DispatcherServlet 需要通过Java代码或者web.xml 来配置. 随后DispatcherServlet 将使用Spring来扫描发现组件, 以便使用这些组件来处理实际的事务逻辑, 比如请求映射, 视图, 异常的处理等等.

下面是使用java代码来注册及初始化DispatcherServlet的例子:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

接下来是通过web.xml配置的例子(推荐):

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

记录下我遇到的一个问题:
上述web.xml文件中, <servlet-mapping>下的<url-pattern>选项/, /*的区别:
/*会将controller返回的结果再传递给DispatcherServlet, 如果找不到对应的controller就会产生404的错误
/会将controller返回的结果直接当做视图来处理


2.1 多层 context

DispatcherServlet 需要一个由 ApplicationContext 拓展而来的WebApplicationContext来配置自己. WebApplicationContext 有指向和自身相关联的ServletContextServlet 的链接, 同时也绑定到了ServletContext, 以便应用程序能够使用RequestContextUtils 中的静态方法来获取WebApplicationContext.

对很多应用程序来说, 简单地拥有一个单独的WebApplicationContext 已经足够了, 但是构造一个多层次的context 也是可能的. 在这种情况下, root WebApplicationContext 被多个DispatcherServlet (或者其他的Servlet) 实例所共享, 而每个实例拥有自己的child WebApplicationContext 配置.

典型的root WebApplicationContext 包含着像数据仓库和各种服务这些需要在各个Servlet 实例之间共享的Java bean. 而典型的child WebApplicationContext 只包含其Servlet 本地的beans.
这里写图片描述

下面是一个多层 context Java 代码配置的例子:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { App1Config.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/app1/*" };
    }
}

web.xml配置方法

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/app1-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>

2.2 特殊的bean类型
DispatcherServlet 委托给一些特殊的beans来完成请求的处理以及生成相应的响应. 特殊的beans指的是那些由Spring 管理的, 实现了WebFlux框架协议的Object实例. 这些beans通常来自于协议内部定义, 但是你可以自定义它们的属性, 继承甚至是替换掉它们.

一下是这些特殊beans的列表:

Bean Type 说明
HandlerMapping 通过一系列的预处理或后处理拦截器来讲请求映射给处理程序. 这种映射取决于HandlerMapping 实现的具体细节, 不同的实现可能有不同的结果.
两个主要的HandlerMapping 的实现之一是RequestMappingHandlerMapping, 该类支持@RequestMapping 注解标注的方法. 另一个是SimpleURLHandlerMapping, 主要负责将明确注册了的URI路径请求传递给处理程序
HandlerAdapter 帮助DispatcherServlet 来调用对应请求的处理程序, 而无需在意处理程序的具体实现细节. 例如, 调用一个由注解标注的controller 需要处理注解. HandlerAdapter 主要就是负责向DispatcherServlet封装掉这样细节.
HandlerExceptionResolver 按一定的策略将异常传递给处理程序, 或者返回一个出错提示的HTML页面
ViewResolver 将由处理程序返回的基于字符串的视图名称映射到实际的视图, 以便生成响应
LocaleResolver,
LocaleContextResolver
处理客户端正在使用的地区信息以及时区等, 以便能够提供国际化了的视图
ThemeResolver 处理web应用能够使用的主题, 例如提供个性化的布局.
MultipartResolver 在某些多段解析类库的帮助下来解析多段类型请求(比如浏览器的文件上传)
FlashMapManager 存取”input”和”output”的FlashMap, FlashMap 能够在个请求之间传递属性值

2.3 Web MVC 配置
应用程序可以根据需求来声明上表中的特殊bean, 以便实现请求的处理. DispatcherServlet 将在 WebApplicationContext 中查找这些特殊的beans. 如果没有找到匹配的bean类型, 那么DispatcherServlet 将会使用在DispatcherServlet.properties 中定义的默认类型.

在很多情况下, MVC 配置是很好的起点. 它既可以在java代码中配置, 也可以在xml配置文件中声明, 同时还提供了一些用于自定义的高级回调函数API.


2.4 Servlet 配置
Servlet 3.0 以上的环境中, 既可以用代码也可以在web.xml中来配置Servlet 选项. 以下是一个注册DispatcherServlet 的例子:

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

WebApplicationInitializer 是一个由Spring MVC提供的接口, 以便保证你的实现能够被发现并且自动的用于初始化任何Servlet 3 容器.

一个实现了WebApplicationInitializer 抽象基类是AbstractDispatcherServletInitializer, 使得配置更加简单. 我们只需要简单地重写那些指定servlet映射和DispatcherServlet配置位置的方法, 就可以实现DispatcherServlet 的注册.

这种方法推荐那些基于Java代码配置的Spring应用:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

如果是基于XMl配置文件的应用, 那么就应该直接继承AbstractDispatcherServletInitializer:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    // AbstractDispatcherServletInitializer也提供了一个方便的方法来添加过滤器
    Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
            new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }
}

每个过滤器凭以它的实际类型为基础的名字来添加并自动映射到DispatcherServlet.

AbstractDispatcherServletInitializerisAsyncSupportedprotected 方法提供了一个用来确定DispatcherServlet 以及他的所有过滤器的对异步的支持的方法. 默认情况下, 这个标志位被设为true.

最后, 如果想更加深入的自定义DispatcherServlet, 你可以重写createDispatcherServlet 方法.


2.5 处理(Processing)
DispatcherServlet 按如下的方式来处理请求:

  • WebApplicationContext 会被搜索并被添加到请求中, 以便controller 和其他的组件能够使用它. 默认被存储的keyDispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
  • Locale Resolver也被添加到请求中, 以便处理组件能够使用这些地区信息来处理请求(例如, 渲染视图, 准备数据等等). 如果你不需要国际化, 那么就可能不需要它.
  • Theme resolver被添加到请求中, 以便视图能够决定使用哪个主题. 如果不使用主题, 就可以忽略它.
  • 如果制定了一个多段文件resolver, 辉县检查请求是否分为了多段. 如果请求分段, 那么它将被包装为MultipartHttpServletRequest, 以方便未来对请求的处理.
  • 搜索一个适当的请求处理器, 如果找到了合适的处理器, 那么与该处理器相关联的执行链(预处理, 后处理, controller)将得到按序执行, 以准备或者渲染Model. 或者对于基于注解的controller来说, 可能会直接生成一个响应, 和不是返回一个view.
  • 如果反返回了一个model, 视图将得到渲染. 如果没有返回model(可能由于预处理器或者后处理器因为安全原因拦截了这个请求), 就不会渲染view, 因为此时请求已经得到满足了.

WebApplicationContext 中声名的HandlerExceptionResolver 会在请求处理过程中处理抛出的异常.

Spring的DispatcherServlet 也支持返回有Servlet API指定的last-modification-date. 决定特定请求的last-modification-date的过程是很明确的: DispatcherServlet搜索一个合适的handler, 然后判断这个handler是否实现了LastModified 接口. 如果是, 则通过LastModified接口中的long getLastModified(request) 方法得到的值将被返回给客户端.

可以向web.xml中的Servlet 的初始化参数中添加参数来自定义DispatcherServlet. 如下是可用的参数列表:

参数 说明
contextClass 指定一个实现了WebApplicationContext 的类, 该类将被Servlet 用作context. 默认情况下, 该类为XMLWebApplicationContext.
contextConfigLocation 传递给上述contextClass实例的字符串, 用来指示哪里可以找到contexts. 这个选项可以由以逗号分隔的多个字符串构成, 来支持多个context. 为了防止某些context 位置被多次定义, 后定义的位置优先
namespace WebApplicationContext 的命名空间, 默认为[servlet-name]-servlet
throwExceptionIfNoHandlerFound 指示是否在某个请求找不到适当的handler时抛出NoHandlerFoundException, 该异常可以被HandlerExceptionResolver 捕获.
默认情况下, 该选项被设置为false, 在这种情况下, DispatcherServlet 将把响应的状态码设置为404(NOT_FOUND) 而不抛出异常.
值得注意的是, 如果设置了默认的handler, 那么请求将被传递给默认的哈handler而不会发出404请求

2.6 拦截(Interception)
所有的HandlerMapping 都支持handler 拦截器, 这种特性对于为某些请求执行特殊的处理功能的时候有很大的帮助. 拦截器必须继承org.springframework.web.servlet 包的HandlerInterceptor接口. 该接口定义了三个方法:

  • preHandle(...) - 在handler执行之前调用
  • postHandle(...) - 在handler执行之后调用
  • afterCompletion(...) - 在请求执行完成之后调用

preHandle(...) 返回一个布尔值. 可以通过这个返回值来控制执行链是否继续. 如果该方法返回true, handler 执行链将继续执行. 否则, DispatcherServlet 将会认为该拦截器已经完成了对请求的妥善处理, 执行链后序的拦截器以及handler都不会得到执行.

需要注意的是postHandle(...) 对于@ResponseBody@ResponseEntity 注解标注的方法来说用处不大. 因为这种情况下, 在postHandle(...)调用之前, HandlerAdapter 已经完成了对response的处理. 换句话说就是在postHandle(...)被调用的时候想再修改response已经太晚了.


2.7 异常(Exceptions)
如果在请求映射时或者@Controller这样的handler处理过程中抛出了异常, DispatcherServlet 将会把这个异常传递给一个由HandlerExceptionResolver beans组成的处理链来处理这个异常, 并提供一个处理结果, 典型的就是一个异常响应.

下表是可用的HandlerExceptionResolver:

HandlerExceptionResolver 说明
SimpleMappingExceptionResolver 异常类名和异常视图之间的映射. 对于浏览器程序渲染出错页面是很有用的
DefaultHandlerExceptionResolver 处理由Spring MVC抛出的异常, 并将它们映射到HTTP状态码.
ResponseStatusExceptionResolver 处理由@ResponseStatus 注解标注的异常, 并把他们映射到HTTP状态码.
ExceptionHandlerExceptionResolver 通过调用@Controller 或者@ControllerAdvice 中标注的@ExceptionHandler 方法来处理异常.

*处理链 (Chain of Resolvers)
在配置中声明多个HandlerExceptionResolver beans即可组成一个异常处理链. 可以通过order属性来决定的它们的顺序, order值越大, 位置就越靠后.

HandlerExceptionResolver 能够返回:

  • ModelAndView - 指向出错页面
  • 空的ModelAndView - 如果Resolver 妥善处理了这个异常
  • null - 如果异常未能得到处理, 交给接下来的Resolver来尝试. 如果直到链的最后异常仍未被处理, 他将被传递给Servlet 容器.

MVC Config 会自动地声明一些内建的resolvers, 这些resolvers 能够处理默认的Spring MVC异常, @ResponseStatus标出的异常, 以及@ExceptionHandler 能够处理的异常. 这些resolvers 可以被自定义或者直接替换掉.

*容器错误页面 (Container error page)
如果没有HandlerExceptionResolver 能够处理异常而冒泡给了Servlet 容器, 或者响应的状态码被设为出错状态(例如, 4xx, 5xx), Servlet 容器可能会渲染一个默认的出错页面. 可以在web.xml 中声明出错页面的映射来自定义这个页面.

<error-page>
    <location>/error</location>
</error-page>

在上面这种情况下, 当一个异常冒泡到Servlet 容器, 或者响应的状态码为出错状态, Servlet 容器将会调度到上述配置的URL. 随后交给DispatcherServlet 来处理. DispatcherServlet 可能会将其交给一个@Controller 来处理, 然后@Controller 来决定是渲染一个出错页面还是返回一个JSON信息. 下面是一个@Controller的例子:

@RestController
public class ErrorController {

    @RequestMapping(path = "/error")
    public Map<String, Object> handle(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));
        return map;
    }
}

2.8 视图(View Resolution)
Spring MVC定义了ViewResolverView 接口来使你在不必掌握具体的view技术的情况下渲染浏览器中显示的model. ViewResolver 提供了在实际view和view名称之间的映射. 而View则负责在向具体的view技术传递数据之前的准备工作.

下表是一些可用的ViewResolver:

ViewResolver 说明
AbstractCachingViewResolver 该抽象类的子类将会对他们处理的视图实例进行缓存. 缓存会提升某些view技术的表现. 也可以关闭缓存功能, 只需要把cache 属性设为false 即可. 除此之外, 在如果必须要刷新某个view, 可以使用removeFromCache(String viewName, Locale loc) 方法.
XMLViewResolver ViewResolver 的实现类接受一个XML的配置文件, 该配置文件与Spring的XML文件有相同的文档类型定义(DTD). 默认的配置文件是/WEB-INF/views.xml .
ResourceBundleViewResolver 实现了ViewResolver 接口, ResourceBundleViewResolver 使用定义在ResourceBundle 中定义bean的方式. 当要处理view的时候, 使用[viewname].(class) 属性值作为view类, [viewname].url 属性值作为view的url.
UrlBasedViewResolver ViewResolver 的一种简单实现, 将逻辑view名直接映射为URL. 如果你的逻辑名称直接匹配view资源的时候非常有用.
InternalResourceViewResolver UrlBasedViewResolver 的子类, 支持InternalResourceView, 以及子类JstViewTitlesView. 可以使用setViewClass(...) 来指定view类.
FreeMarkerViewResolver UrlBasedViewResolver 的子类, 支持FreeMarkerView 以及它的所有子类.
ContentNegotiatingViewResolver 实现了ViewResolver 接口. 负责处理基于请求文件名或者Accept 头的view.

*处理 (handling)
与异常Resolvers类似, 也可以通过声明多个Resolver 来构造一个处理链, 同时需要指定order 属性. order值越大, 该resolver 的位置就越靠后.

ViewResolver 可以返回null 来表明无法找到该view. 但是对于JSP和InternalResourceViewResolver 来说, 唯一可以得知JSP是否存在的方式就是通过RequestDispatcher 来执行一次调度(dispatch). 因此, InternalResourceViewResolver 必须声明在处理链的最后.

*重定向 (redirecting)
在view名称前加上特殊的redirect: 前缀即可执行重定向. UrlBasedViewResolver(以及它的子类)会把这个前缀看做是一条重定向指令, 余下的view名称就是重定向的URL.

结果就像controller返回了一个RedirectView 一样. 像redirect:/myapp/some/resource 这样的view名称将被看作是相对于当前Servlet context的相对地址, 而redirect:http://myhost.com/some/arbitrary/path 这样的地址将作为绝对URL.

需要注意的是, 如果一个controller方法是被@ResponseStatus 标注的, 注解的值将优先于RedirectView的值.

*转发 (fowarding)
特殊的前缀forward:将被UrlBasedViewResolver和它的子类识别, 然后创建一个InternalResourceView 然后执行RequestDispatcher.forward() 来进行转发. 也正是因此, forward: 前缀对于InternalResourceViewResolverInternalResourceView是没用的.


2.9 地区(Locale)

猜你喜欢

转载自blog.csdn.net/cfmy_ban/article/details/81810002