Spring MVC 核心组件详解

Posted by 彭楷淳 on 2021-01-15
Estimated Reading Time 8 Minutes
Words 1.9k In Total
Viewed Times

DispatcherServlet 作用


DispatcherServlet 是前端控制器设计模式的实现,提供 Spring Web MVC 的集中访问点,而且负责职责的分派,而且与 Spring IoC 容器无缝集成,从而可以获得 Spring 的所有好处。DispatcherServlet 主要用作职责调度工作,本身主要用于控制流程,主要职责如下:

  1. 文件上传解析,如果请求类型是 multipart 将通过 MultipartResolver 进行文件上传解析;
  2. 通过 HandlerMapping 将请求映射到处理器(返回一个 HandlerExecutionChain,它包括一个处理器、多个 HandlerInterceptor 拦截器);
  3. 通过 HandlerAdapter 支持多种类型的处理器(HandlerExecutionChain 中的处理器);
  4. 通过 ViewResolver 解析逻辑视图名到具体视图实现;
  5. 本地化解析;
  6. 渲染具体的视图等;
  7. 如果执行过程中遇到异常将交给 HandlerExceptionResolver 来解析

DispathcherServlet 配置详解


1
2
3
4
5
6
7
8
9
10
11
12
13
14
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
  • load-on-startup:表示启动容器时初始化该 Servlet;
  • url-pattern:表示哪些请求交给 Spring Web MVC 处理, “/“ 是用来定义默认 servlet 映射的。也可以如 *.html 表示拦截所有以 html 为扩展名的请求;
  • contextConfigLocation:表示 SpringMVC 配置文件的路径。

其他的参数配置:

参数 描述
contextClass 实现 WebApplicationContext 接口的类,当前的 Servlet 用它来创建上下文。如果这个参数没有指定, 默认使用 XmlWebApplicationContext
contextConfigLocation 传给上下文实例(由 contextClass 指定)的字符串,用来指定上下文的位置。这个字符串可以被分成多个字符串(使用逗号作为分隔符) 来支持多个上下文(在多上下文的情况下,如果同一个bean被定义两次,后面一个优先)。
namespace WebApplicationContext 命名空间。默认值是 [server-name]-servlet。

HandlerMapping 处理器映射器


注意,下文所说的处理器即我们平时所见到的 Controller

在 SpringMVC 中提供了很多 HandlerMapping

img

HandlerMapping 是负责根据 request 请求找到对应的 Handler 处理器及 Interceptor 拦截器,将它们封装在 HandlerExecutionChain 对象中返回给前端控制器。

  • BeanNameUrlHandlerMapping:BeanNameUrl 处理器映射器,根据请求的 url 与 Spring 容器中定义的 bean 的 name 进行匹配,从而从 Spring 容器中找到 bean 实例,就是说,请求的 Url 地址就是处理器 Bean 的名字。这个 HandlerMapping 配置如下:

    1
    2
    3
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping">
    <property name="beanName" value="/hello"/>
    </bean>
  • SimpleUrlHandlerMapping:是 BeanNameUrlHandlerMapping 的增强版本,它可以将 url 和处理器 bean 的 id 进行统一映射配置:

    1
    2
    3
    4
    5
    6
    7
    8
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" id="handlerMapping">
    <property name="mappings">
    <props>
    <prop key="/hello">helloController</prop>
    <prop key="/hello2">helloController2</prop>
    </props>
    </property>
    </bean>

    注意,在 props 中,可以配置多个请求路径和处理器实例的映射关系。

HandlerAdapter 处理器适配器


HandlerAdapter 会根据适配器接口对后端控制器进行包装(适配),包装后即可对处理器进行执行,通过扩展处理器适配器可以执行多种类型的处理器,这里使用了适配器设计模式。

在 SpringMVC 中,HandlerAdapter 也有诸多实现类:

img

  • SimpleControllerHandlerAdapter:简单控制器处理器适配器,所有实现了 org.springframework.web.servlet.mvc.Controller 接口的 Bean 通过此适配器进行适配、执行,也就是说,如果我们开发的接口是通过实现 Controller 接口来完成的(不是通过注解开发的接口),那么 HandlerAdapter 必须是 SimpleControllerHandlerAdapter

    1
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
  • HttpRequestHandlerAdapter:Http 请求处理器适配器,所有实现了 org.springframework.web.HttpRequestHandler 接口的 Bean 通过此适配器进行适配、执行。例如存在如下接口:

    1
    2
    3
    4
    5
    6
    7
    @Controller
    public class HelloController2 implements HttpRequestHandler {

    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println("-----HelloController2-----");
    }
    }

    XML 配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" id="handlerMapping">
    <property name="mappings">
    <props>
    <prop key="/hello2">helloController2</prop>
    </props>
    </property>
    </bean>
    <bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" id="handlerAdapter"/>

最佳实践


各种情况都大概了解了,我们看下项目中的具体实践。

组件自动扫描

Web 开发中,我们基本上不再通过 XML 或者 Java 配置来创建一个 Bean 的实例,而是直接通过组件扫描来实现 Bean 的配置,如果要扫描多个包,多个包之间用 , 隔开即可:

1
<context:component-scan base-package="com.antoniopeng.hello.spring.mvc"/>

HandlerMapping

正常情况下,我们在项目中使用的是 RequestMappingHandlerMapping,这个是根据处理器中的注解,来匹配请求(即 @RequestMapping 注解中的 url 属性)。因为在上面我们都是通过实现类来开发接口的,相当于还是一个类一个接口,所以,我们可以通过 RequestMappingHandlerMapping 来做处理器映射器,这样我们可以在一个类中开发出多个接口。

HandlerAdapter

对于上面提到的通过 @RequestMapping 注解所定义出来的接口方法,这些方法的调用都是要通过 RequestMappingHandlerAdapter 这个适配器来实现。

例如我们开发一个接口:

1
2
3
4
5
6
7
8
@Controller
public class HelloController3 {

@RequestMapping("/hello3")
public ModelAndView hello() {
return new ModelAndView("hello3");
}
}

要能够访问到这个接口,我们需要 RequestMappingHandlerMapping 才能定位到需要执行的方法,需要 RequestMappingHandlerAdapter,才能执行定位到的方法,修改 springmvc 的配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.antoniopeng.hello.spring.mvc"/>

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" id="handlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" id="handlerAdapter"/>

<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
<property name="prefix" value="/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

然后,启动项目,访问 /hello3 接口,就可以看到相应的页面了。

继续优化

由于开发中,我们常用的是 RequestMappingHandlerMappingRequestMappingHandlerAdapter,这两个有一个简化的写法,如下:

1
<mvc:annotation-driven>

可以用这一行配置,代替 RequestMappingHandlerMappingRequestMappingHandlerAdapter 的两行配置。优化后完整配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 自动扫描组件 -->
<context:component-scan base-package="com.antoniopeng.hello.spring.mvc"/>

<!-- 处理器 -->
<mvc:annotation-driven/>

<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
<property name="prefix" value="/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

访问效果和上一步的效果一样。这是我们实际开发中,最终配置的形态。

更多干货请移步:https://antoniopeng.com


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !