Spring框架内容

来自小能手俱乐部
Gaozehao讨论 | 贡献2021年8月31日 (二) 06:46的版本 →‎HandlerMapping
跳到导航 跳到搜索

SpringMVC简介

Spring Web MVC是什么

Spring Web MVC 是一种基于 Java 的实现了 Web MVC 设计模式的请求驱动类型的轻量级 Web 框架,即使用了 MVC 架构模式的思想,将 web 层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC 也是要简化我们日常 Web 开发的。在 传统的 Jsp/Servlet 技术体系中,如果要开发接口,一个接口对应一个 Servlet,会导致我们开发出许多 Servlet,使用 SpringMVC 可以有效的简化这一步骤。
Spring Web MVC 也是服务到工作者模式的实现,但进行可优化。前端控制器是 DispatcherServlet;应用控制器可以拆为处理器映射器(Handler Mapping)进行处理器管理和视图解析器(View Resolver)进行视图管理;页面控制器/动作/处理器为 Controller 接口(仅包含 ModelAndView handleRequest(request, response) 方法,也有人称作 Handler)的实现(也可以是任何的 POJO 类);支持本地化(Locale)解析、主题(Theme)解析及文件上传等;提供了非常灵活的数据验证、格式化和数据绑定机制;提供了强大的约定大于配置(惯例优先原则)的契约式编程支持。

Spring Web MVC能帮我们做什么

  • 让我们能非常简单的设计出干净的 Web 层和薄薄的 Web 层;
  • 进行更简洁的 Web 层的开发;
  • 天生与 Spring 框架集成(如 IoC 容器、AOP 等);
  • 提供强大的约定大于配置的契约式编程支持;
  • 能简单的进行 Web 层的单元测试;
  • 支持灵活的 URL 到页面控制器的映射;
  • 非常容易与其他视图技术集成,如 Velocity、FreeMarker 等等,因为模型数据不放在特定的 API 里,而是放在一个 Model 里(Map 数据结构实现,因此很容易被其他框架使用);
  • 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的 API;
  • 提供一套强大的 JSP 标签库,简化 JSP 开发;
  • 支持灵活的本地化、主题等解析;
  • 更加简单的异常处理;
  • 对静态资源的支持;
  • 支持 RESTful 风格

HelloWorld

通过一个简单的例子来感受一下SpringMVC。

  1. 利用Maven创建一个web工程,在pom.xml文件中,添加spring-webmvc的依赖。
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.3</version>
        </dependency>
    </dependencies>
    
    添加了 spring-webmvc 依赖之后,其他的 spring-web、spring-aop、spring-context 等等就全部都加入进来了。
  2. 准备一个 Controller,即一个处理浏览器请求的接口。
    public class MyController implements Controller {
        /**
         * 这就是一个请求处理接口
         * @param req 这就是前端发送来的请求
         * @param resp 这就是服务端给前端的响应
         * @return 返回值是一个 ModelAndView,Model 相当于是我们的数据模型,View 是我们的视图
         * @throws Exception
         */
        public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
            ModelAndView mv = new ModelAndView("hello");
            mv.addObject("name", "javaboy");
            return mv;
        }
    }
    
    这里我们我们创建出来的 Controller 就是前端请求处理接口。
  3. 创建视图,这里我们就采用 jsp 作为视图,在 webapp 目录下创建 hello.jsp 文件,内容如下:
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <h1>hello ${name}!</h1>
    </body>
    </html>
    
  4. 在 resources 目录下,创建一个名为 spring-servlet.xml 的 springmvc 的配置文件,这里,我们先写一个简单的 demo ,因此可以先不用添加 spring 的配置。
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="org.javaboy.helloworld.MyController" name="/hello"/>
        <!--这个是处理器映射器,这种方式,请求地址其实就是一个 Bean 的名字,然后根据这个 bean 的名字查找对应的处理器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping">
            <property name="beanName" value="/hello"/>
        </bean>
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" id="handlerAdapter"/>
        
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
            <property name="prefix" value="/jsp/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    </beans>
    
  5. 加载 springmvc 配置文件,在 web 项目启动时,加载 springmvc 配置文件,这个配置是在 web.xml 中完成的。
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <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-servlet.xml</param-value>
            </init-param>
        </servlet>
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    
    所有请求都将自动拦截下来,拦截下来后,请求交给 DispatcherServlet 去处理,在加载 DispatcherServlet 时,还需要指定配置文件路径。这里有一个默认的规则,如果配置文件放在 webapp/WEB-INF/ 目录下,并且配置文件的名字等于 DispatcherServlet 的名字+ -servlet(即这里的配置文件路径是 webapp/WEB-INF/springmvc-servlet.xml),如果是这样的话,可以不用添加 init-param 参数,即不用手动配置 springmvc 的配置文件,框架会自动加载。
  6. 配置并启动项目。
  7. 项目启动成功后,浏览器输入 http://localhost:8080/hello 就可以看到如下页面:

Hello.png

SpringMVC工作流程

生成缩略图出错:无法找到文件
  1. 用户发送请求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter处理器适配器。
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器),Controller执行完成返回ModelAndView,HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
  6. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  7. ViewReslover解析后返回具体View,这个view不是完整的,仅仅是一个页面(视图)名字,且没有后缀名。
  8. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中),响应用户。

SpringMVC中的组件

  • DispatcherServlet:前端控制器
 用户请求到达前端控制器,它就相当于 mvc 模式中的c,DispatcherServlet 是整个流程控制的中心,相当于是 SpringMVC 的大脑,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。
  • HandlerMapping:处理器映射器
 HandlerMapping 负责根据用户请求找到 Handler 即处理器(也就是我们所说的 Controller),SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等,在实际开发中,我们常用的方式是注解方式。
  • Handler:处理器
 Handler 是继 DispatcherServlet 前端控制器的后端控制器,在DispatcherServlet 的控制下 Handler 对具体的用户请求进行处理。由于 Handler 涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发 Handler。(这里所说的 Handler 就是指我们的 Controller)
  • HandlAdapter:处理器适配器
 通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
  • ViewResolver:视图解析器
 ViewResolver 负责将处理结果生成 View 视图,ViewResolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。 SpringMVC 框架提供了很多的 View 视图类型,包括:jstlView、freemarkerView、pdfView 等。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

DispatcherServlet

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配置详解

<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-servlet.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。

Spring配置

之前的案例中,只有 SpringMVC,没有 Spring,Web 项目也是可以运行的。在实际开发中,Spring 和 SpringMVC 是分开配置的,所以我们对上面的项目继续进行完善,添加 Spring 相关配置。

首先,项目添加一个 service 包,提供一个 HelloService 类,如下:

@Service
public class HelloService {
    public String hello(String name) {
        return "hello " + name;
    }
}

现在,假设我需要将 HelloService 注入到 Spring 容器中并使用它,这个是属于 Spring 层的 Bean,所以我们一般将除了 Controller 之外的所有 Bean 注册到 Spring 容器中,而将 Controller 注册到 SpringMVC 容器中,现在,在 resources 目录下添加 applicationContext.xml 作为 spring 的配置:

<?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="org.javaboy" use-default-filters="true">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

但是,这个配置文件,默认情况下,并不会被自动加载,所以,需要我们在 web.xml 中对其进行配置:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

首先通过 context-param 指定 Spring 配置文件的位置,这个配置文件也有一些默认规则,它的配置文件名默认就叫 applicationContext.xml ,并且,如果你将这个配置文件放在 WEB-INF 目录下,那么这里就可以不用指定配置文件位置了,只需要指定监听器就可以了。这段配置是 Spring 集成 Web 环境的通用配置;一般用于加载除 Web 层的 Bean(如DAO、Service 等),以便于与其他任何Web框架集成。

  • contextConfigLocation:表示用于加载 Bean 的配置文件;
  • contextClass:表示用于加载 Bean 的 ApplicationContext 实现类,默认 WebApplicationContext。

配置完成之后,还需要修改 MyController,在 MyController 中注入 HelloSerivce:

@org.springframework.stereotype.Controller("/hello")
public class MyController implements Controller {
    @Autowired
    HelloService helloService;
    /**
     * 这就是一个请求处理接口
     * @param req 这就是前端发送来的请求
     * @param resp 这就是服务端给前端的响应
     * @return 返回值是一个 ModelAndView,Model 相当于是我们的数据模型,View 是我们的视图
     * @throws Exception
     */
    public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        System.out.println(helloService.hello("javaboy"));
        ModelAndView mv = new ModelAndView("hello");
        mv.addObject("name", "javaboy");
        return mv;
    }
}

为了在 SpringMVC 容器中能够扫描到 MyController ,这里给 MyController 添加了 @Controller 注解,同时,由于我们目前采用的 HandlerMapping 是 BeanNameUrlHandlerMapping(意味着请求地址就是处理器 Bean 的名字),所以,还需要手动指定 MyController 的名字。 最后,修改 SpringMVC 的配置文件,将 Bean 配置为扫描形式:

<context:component-scan base-package="org.javaboy.helloworld" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--这个是处理器映射器,这种方式,请求地址其实就是一个 Bean 的名字,然后根据这个 bean 的名字查找对应的处理器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping">
    <property name="beanName" value="/hello"/>
</bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" id="handlerAdapter"/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
    <property name="prefix" value="/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

配置完成后,再次启动项目,Spring 容器也将会被创建。访问 /hello 接口,HelloService 中的 hello 方法就会自动被调用。

两个容器

当 Spring 和 SpringMVC 同时出现,我们的项目中将存在两个容器,一个是 Spring 容器,另一个是 SpringMVC 容器,Spring 容器通过 ContextLoaderListener 来加载,SpringMVC 容器则通过 DispatcherServlet 来加载,这两个容器不一样:

生成缩略图出错:无法找到文件

从图中可以看出:

  • ContextLoaderListener 初始化的上下文加载的 Bean 是对于整个应用程序共享的,不管是使用什么表现层技术,一般如 DAO 层、Service 层 Bean;
  • DispatcherServlet 初始化的上下文加载的 Bean 是只对 Spring Web MVC 有效的 Bean,如 Controller、HandlerMapping、HandlerAdapter 等等,该初始化上下文应该只加载 Web相关组件。

处理器详解

HandlerMapping

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

HandlerMapping ,中文译作处理器映射器,在 SpringMVC 中,系统提供了很多 HandlerMapping:

生成缩略图出错:无法找到文件

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

  • BeanNameUrlHandlerMapping

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

这个 HandlerMapping 配置如下:

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping">
    <property name="beanName" value="/hello"/>
</bean>
  • SimpleUrlHandlerMapping

SimpleUrlHandlerMapping 是 BeanNameUrlHandlerMapping 的增强版本,它可以将 url 和处理器 bean 的 id 进行统一映射配置:

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" id="handlerMapping">
    <property name="mappings">
        <props>
            <prop key="/hello">myController</prop>
            <prop key="/hello2">myController2</prop>
        </props>
    </property>
</bean>

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

HandlerAdapter

HandlerAdapter,中文译作处理器适配器。

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

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

生成缩略图出错:无法找到文件


  • SimpleControllerHandlerAdapter


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

<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
  • HttpRequestHandlerAdapter

HttpRequestHandlerAdapter,http 请求处理器适配器,所有实现了 org.springframework.web.HttpRequestHandler 接口的 Bean 通过此适配器进行适配、执行。

例如存在如下接口:

@Controller
public class MyController2 implements HttpRequestHandler {
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("-----MyController2-----");
    }
}
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" id="handlerMapping">
    <property name="mappings">
        <props>
            <prop key="/hello2">myController2</prop>
        </props>
    </property>
</bean>
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" id="handlerAdapter"/>