Spring MVC

初识SpringMVC

大部分 Java 应用都是 Web 应用,展现层是Web 应用不可忽略的重要环节。Spring 为展现层提供了一个优秀的Web框架——Spring MVC。和众多其他的Web 框架一样,它基于 MVC 的设计理念。此外,它采用了松散耦合、可插拔的组件结构,比其他的MVC框架更具扩展性和灵活性。Spring MVC通过一套MVC注解,让POJO成为处理请求的控制器,无须实现任何接口。同时,Spring MVC还支持 RESTful风格的 URL 请求∶注解驱动及REST风格的 Spring MVC是 Spring的出色功能之一。此外,Spring MVC在数据绑定、视图解析、本地化处理及静态资源处理上都有许多不俗的表现。

SpringMVC简介

Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。
Spring MVC 是结构最清晰的 Servlet+JSP+JavaBean 的实现,是一个典型的教科书式的 MVC 构架,不像 Struts 等其它框架都是变种或者不是完全基于 MVC 系统的框架。
Spring MVC 角色划分清晰,分工明细,并且和 Spring 框架无缝结合。Spring MVC 是当今业界最主流的 Web 开发框架,以及最热门的开发技能。
在 Spring MVC 框架中,Controller 替换 Servlet 来担负控制器的职责,用于接收请求,调用相应的 Model 进行处理,处理器完成业务处理后返回处理结果。Controller 调用相应的 View 并对处理结果进行视图渲染,最终客户端得到响应信息。
Spring MVC 框架采用松耦合可插拔的组件结构,具有高度可配置性,比起其它 MVC 框架更具有扩展性和灵活性。
此外,Spring MVC 的注解驱动和对 REST 风格的支持,也是它最具特色的功能。无论是在框架设计,还是扩展性、灵活性等方面都全面超越了 Struts2 等 MVC 框架。并且由于 Spring MVC 本身就是 Spring 框架的一部分,所以可以说与 Spring 框架是无缝集成,性能方面具有先天的优越性,对于开发者来说,开发效率也高于其它的 Web 框架,在企业中的应用越来越广泛,成为主流的 MVC 框架。

DispatcherServlet

DispatcherServlet是 Spring MVC的”灵魂”和”心脏”,它负责接收HTTP 请求并协调 Spring MVC 的各个组件完成请求处理的工作。和任何 Servlet 一样,用户必须在web.xml 中配置好 DispatcherServlet。这里进一步分析其具体的配置。
要了解 Spring MVC框架的工作机理,必须回答以下3个问题。


  1. DispatcherServlet 框架如何截获特定的HTTP请求并交由 Spring MVC框架处理?

  2. 位于 Web层的 Spring 容器(WebApplicationContext)如何与位于业务层的 Spring容器(ApplicationContext)建立关联,以使Web层的Bean可以调用业务层的Bean?

  3. 如何初始化 Spring MVC的各个组件,并将它们装配到 DispatcherServlet 中?





配置DispatcherServlet

在Servlet的学习中可以知道,通常会在web.xml中配置Servlet,并通过<servlet-mapping>指定其能处理的URL,在Servlet 3.0中还可以采用编程式的配置方法。因此本小节将就这两种方式进行讲解。

web.xml中配置DispatcherServlet

首先新建web项目并添加如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
<!-- spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web </artifactId>
<version>5.3.9</version>
</dependency>
<!-- spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>

在添加依赖后,需要在resource目录下新建SpringMVC的配置文件,因为在框架整合过程中SpringMVC有自身的配置,Spring也需要一些其他的配置,因此将SpringMVC的配置文件和Spring的配置文件进行分离。

Spring-mvc.xml配置文件如下:

1
2
3
4
5
6
7
8
<?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">
<!--扫描controller包实例化控制器-->
<context:component-scan base-package="cn.bytecollege.controller"/>
</beans>

在SpringMVC的配置文件中只是进行了简单的配置,配置用于扫描controller包,从而交由Spring容器实例化控制器。

spring.xml配置文件如下:

1
2
3
4
5
<?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">
</beans>

上述配置完成后,接下来要需要在web.xml中进行配置Dispatcher。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--加载SpringMVC的配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<!--将所有请求都映射到dispatcherServlet-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--加载Spring配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
</web-app>

在web.xml中首先需要配置org.springframework.web.servlet.DispatcherServlet,配置方式和前面所学的Servlet配置一致,需要注意的是在这里需要在标签中配置contextConfigLocation加载spring-mvc的配置文件。

除此以外,需要配置org.springframework.web.context.ContextLoaderListener监听器,该监听器会在监听容器启动时,加载Spring配置文件,从而创建Spring IOC容器。

接着,新建cn.bytecollege.controller包中添加测试控制器,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.bytecollege.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {
@ResponseBody
@RequestMapping(method = RequestMethod.GET,value = "/index")
public String index(String name){
return "Hello "+name;
}
}

在上述代码中可以看出类中包含了@Controller,@ResponseBody,@RequestMapping注解,这些注解会在后续内容中进行讲解。

启动web服务后,在浏览器地址栏输入:http://localhost/index?name=admin。结果如下图:

在上例中已经成功将Spring和Spring MVC进行了整合。

编程式配置DispatcherServlet

Spring 4开始已经全面支持Servlet 3.0,因此,在Servlet 3.0及以上的环境中,也可以使用编程的方式来配置Servlet。下面的diam可以达到上述代码中和web.xml中配置一样的效果。需要注意的是如果使用这种方式配置,需要在项目中添加Servlet的依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package cn.bytecollege.init;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class MyApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//创建容器
XmlWebApplicationContext applicationContext = new XmlWebApplicationContext();
//设置配置文件地址
applicationContext.setConfigLocation("classpath:spring-mvc.xml");
//注册DispatcherServlet
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet",new DispatcherServlet(applicationContext));
registration.setLoadOnStartup(1);
//配置映射路径
registration.addMapping("/");
}
}

在 Servlet 3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer 的类,如果发现已有实现类,就会调用它来配置 Servlet 容器。在 Spring 中,org.springframework.web.SpringServletContainer Initializer 类实现了该接口,同时这个类又会查找实现 org.springframework.web. WebApplicationInitializer 接口的类,并将配置任务交给这些实现类去完成。另外,Spring 提供了一个便利的抽象类 AbstractAnnotationConfigDispatcherServletInitializer 来实现这个接口,使得它在注册 DispatcherServlet 时只需简单地指定它的 Servlet 映射即可。在上述示例中,当应用部署到 Servlet3.0容器中时,容器启动时会自动发现它,并使用它来配置 Servlet 上下文。

详解Dispatcher

在上面的示例中Spring MVC应用已经成功运行了,那么前端控制器DispatcherServlet截获请求后做了什么工作呢?DispatcherServlet又是如何路由请求的呢?

分析DispatcherServlet核心源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected void initStrategies(ApplicationContext context) {
//初始化上传文件解析器
initMultipartResolver(context);
//初始化本地化解析器
initLocaleResolver(context);
//初始化主题解析器
initThemeResolver(context);
//初始化处理器映射器,将请求映射到处理器
initHandlerMappings(context);
//初始化处理器适配器
initHandlerAdapters(context);
//初始化处理器异常解析器,如果执行过程中遇到异常将异常交个HandlerExceptionResolver处理
initHandlerExceptionResolvers(context);
//初始化请求到视图名称解析器
initRequestToViewNameTranslator(context);
initViewResolvers(context);
//初始化flash映射管理器
initFlashMapManager(context);
}

initStrategies()方法将在 WebApplicationContext 初始化后自动执行,此时 Spring 上下文中的 Bean 已经初始化完毕。该方法的工作原理是∶通过反射机制查找并装配 Spring 容器中用户显式自定义的组件 Bean,如果找不到,则装配默认的组件实例。
Spring MVC定义了一套默认的组件实现类,也就是说,即使在 Spring容器中没有显式定义组件Bean,DispatcherServlet也会装配好一套可用的默认组件。在 spring-webmvc-4.x.jar 包的 org/springframework/web/servlet 类路径下拥有一个 DispatcherServlet. properties 配置文件,该文件指定了DispatcherServlet 所使用的默认组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
##本地化解析器
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
##主题解析器
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
##处理器映射(共2个)
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
##处理器适配器(3个)
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter

##异常处理器(3个)
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
##视图名称翻译器
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
##视图解析器
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

如果用户希望采用非默认类型的组件,则只需在 Spring 配置文件中配置自定义的组件 Bean 即可。Spring MVC一旦发现上下文中有用户自定义的组件,就不会使用默认的组件。

以下是DispatcherServlet装配每种组件的细节:

  • 文件上传解析器。只允许一个实例

    • 查找名为 multipartResolver、类型为 MultipartResolver 的Bean作为该类型的组件。
    • 如果用户没有在上下文中显示定义 MultipartResolver 类型的组件,则 DispartcherServlet 将不会加载该类型的组件。
  • 本地化解析器。只允许一个实例

    • 查找名为 localeResolver 、类型为 .LocaleResolver 的 Bean 作为该类型组件。
    • 如果没有找到,则使用默认的实现类 AcceptHeaderLocaleResolver 作为该类型的组件。
  • 主题解析器。只允许一个实例

    • 查找名为 themeResolver 、类型为 ThemeResolver、的 Bean 作为该类型的组件。
    • 如果没有找到,则使用默认的实现类 FixedThemeResolver 作为该类型组件。
  • 处理器映射器。允许多个实例

    • 如果 detectAllHandlerMappings 的属性为 true(默认为true),则根据类型匹配机制查找上下文以及 Spring 容器中所有类型为HandlerMapping 的 Bean,将他们作为该类型组件。
    • 如果 detectAllHandlerMappings 的属性为 false,则查找名为 handlerMapping 、类型为 HandlerMapping 的 Bean 作为该类型组件。
    • 如果通过以上两种方式都没有找到,则使用 BeanNameUrlHandlerMapping 实现类创建该类型的组件。
  • 处理器适配器。允许多个实例

    • 如果 detectAllHandlerAdapters 的属性为 true(默认为true),则根据类型匹配机制查找上下文以及 Spring 容器中所有类型为HandlerAdapter 的 Bean,将他们作为该类型组件。
    • 如果 detectAllHandlerAdapters 的属性为 false,则查找名为 handlerAdapter 、类型为 HandlerAdapter 的Bean 作为该类型的组件。
    • 如果通过以上两种方式都没有找到,则使用 DispatcherServlet.properties配置文件中指定的三个实现类分别创建一个适配器,并将其添加到适配器列表中。
  • 处理器异常解析器。允许多个实例

    • 如果 detectAllHandlerExceptionResolvers 的属性为 true(默认为true),则根据类型匹配机制查找上下文以及 Spring 容器中所有类型为 HandlerExceptionResolver 的 Bean ,将他们作为该类型组件。
    • 如果 detectAllHandlerExceptionResolvers 的属性为 false ,则查找名为 handlerExceptionResolver 、类型为 HandlerExceptionResolver的 Bean 作为该类型组件。
    • 如果通过以上两种方式都没有找到,则查找 DispatcherServlet.properties 配置文件中定义的默认实现类,注意,改文件中没有对应处理器异常解析器的默认实现类,用户可以自定义处理器异常解析器的实现类,将之添加到 DispatcherServlet.properties配置文件中。
  • 视图名称解析器。只允许一个实例

    • 查找名为 viewNameTranslator 、类型为 RequestToViewNameTranslator 的 Bean 作为该类型的组件。
    • 如果没有找到,则使用默认的实现类 DefaultRequestToViewNameTranslator 作为该类型的组件。
  • 视图解析器。允许多个实例

    • 如果 detectAllViewResolvers 的属性为 true(默认为true),则根据类型匹配机制查找上下文以及 Spring 容器中所有类型为ViewResolver 的 Bean ,将它们作为该类型组件。
    • 如果 detectAllViewResolvers 的属性为 false ,则查找名为 viewResolvers 、类型为 ViewResolver 的BEA作为该类型组件。
    • 如果通过以上两种方式都没有找到,则查找 DispatcherServlet.properties 配置文件中定义默认实现类 InternalResourceViewResolver 作为该类型组件。
  • FlashMap 映射管理器。

    • 查找名为 FlashMapManager 、类型为 SessionFlashMapManager 的 Bean 作为该类型组件,用于管理 FlashMap ,即数据默认存储在HttpSession 中。

SpringMVC 请求流程

Spring MVC应用的开发流程实际上是按请求–响应的流程来开发,下面通过一个流程图来介绍请求–响应的完整流程。

SpringMVC 的执行流程如下。

  1. 浏览器发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);
  2. 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。
  3. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
  4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
  5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
  6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
  7. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
  8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
  9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
  10. 视图负责将结果显示到浏览器(客户端)。

Spring MVC接口

Spring MVC 涉及到的组件有 DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)。下面对各个组件的功能说明如下。

.DispatcherServlet

DispatcherServlet 是前端控制器,从上图可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。

2.HandlerMapping

HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。

3.HandlerAdapter

HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。

4.Handler

Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。

5.View Resolver

View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。

6.View

View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。
以上组件中,需要开发人员进行开发的是处理器(Handler,常称Controller)和视图(View)。通俗的说,要开发处理该请求的具体代码逻辑,以及最终展示给用户的界面。

Spring MVC常用注解

Spring在2.5以前使用XML方式配置JavaBean,但是由于配置文件的不便以及臃肿等问题,Spring提供了@Controller、@RequestMapping、@ResponseBody等注解,不但减轻了配置,也使得开发更加的便利。在本章内将详细讲解SpringMVC中常用的注解

控制器注解

@Controller注解

在 POJO类定义处标注@Controller,再通过<context∶component-scan/>扫描相应的类包,即可使 POJO 成为一个能处理HTTP请求的控制器。


用户可以创建数量不限的控制器,分别处理不同的业务请求,如 LoginController、UserController、ForumController 等。每个控制器可拥有多个处理请求的方法,每个方法负责不同的请求操作。如何将请求映射到对应的控制器方法中是 Spring MVC框架的重要任务之一,这项任务由@RequestMapping 承担。


在控制器的类定义及方法定义处都可以标注@RequestMapping,类定义处的@RequestMapping提供初步的请求映射信息,方法定义处的@RequestMapping 提供进一步的细分映射信息。DispatcherServlet 截获请求后,就通过控制器上@RequestMapping 提供的映射信息确定请求所对应的处理方法。


将请求映射到控制器处理方法的工作包含一系列映射规则,这些规则是根据请求中的各种信息制定的,具体包括请求URL、请求参数、请求方法、请求头这4个方面的信息项。


首先查看@Controller源码:

1
2
3
4
5
6
7
8
9
10
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}

从源码可以看出@Controller注解只有一个value属性,这个属性用于配置JavaBean在IOC容器中实例的名称,如果不配置则默认是类名首字母小写。下面的示例将演示这个注解的用法。

1
2
3
4
5
6
7
8
@Controller
public class IndexController {
@ResponseBody
@RequestMapping(method = RequestMethod.GET,value = "/index")
public String index(String name){
return "Hello "+name;
}
}

@RequestMapping注解

在上一小节已经简单介绍了@RequestMapping注解,@RequestMapping的作用是将HTTP请求正确的映射到对应的类中的方法。从而让对应的方法处理请求,并给出做出响应。首先查询@RequestMapping的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
//给映射地址指定一个别名
String name() default "";
//用于将指定请求的地址映射到方法
@AliasFor("path")
String[] value() default {};
//同value
@AliasFor("value")
String[] path() default {};
//映射指定请求的方法,包括GET、POST、HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE
RequestMethod[] method() default {};
//指定request中必须包含某些参数值时,才让该方法处理请求
String[] params() default {};
//指定request中必须包含某些指定的header值,才能让该方法处理请求
String[] headers() default {};
//指定处理请求的提交内容(Content-Type),例如:application/json
String[] consumes() default {};
//指定返回的内容类型,返回的内容类型必须是request请求头中所包含的类型
String[] produces() default {};

}

@RequestMapping注解支持的常用属性示例如下。

属性 类型 是否必要 说明
value String[] 用于将指定请求的地址映射到方法
name String 给映射地址指定一个别名
method RequestMethod[] 映射指定请求的方法,包括GET、POST、HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE
consumes String[] 指定处理请求的提交内容(Content-Type),例如:application/json
produces String[] 指定返回的内容类型,返回的内容类型必须是request请求头中所包含的类型
params String[] 指定request中必须包含某些参数值时,才让该方法处理请求
headers String[] 指定request中必须包含某些指定的header值,才能让该方法处理请求
path String[] 用于将指定请求的地址映射到方法

1.value属性:@RequestMapping 是一个用来处理请求地址映射的注解,可以使用 @RequestMapping 注释一个方法或类。一个采用@RequestMapping注释的方法将成为一个请求处理方法, 例如:

1
2
3
4
@RequestMapping(value = "/index")
public String index(String name){
return "Hello "+name;
}

2.method属性,该属性用来指示该方法仅处理哪些HTTP请求方式。

1
@RequestMapping(method = RequestMethod.GET,value = "/index")

以上代码method=RequestMethod.GET表示该方法支持GET请求。也可以同时支持多个HTTP请求方式,如:

1
@RequestMapping(method = {RequestMethod.GET,RequestMethod.POST},value = "/index")

Spring 4.3 之后,新增了@GetMapping、@PostMapping、 @PutMapping、@DeleteMapping、@PatchMapping 注解,这几个注解可 以指定的属性和@RequestMapping注解类似,区别在于@GetMapping注 解只支持GET方式的请求;@PostMapping注解只支持POST方式的请求,@PutMapping@DeleteMapping、@PatchMapping分别对应PUT请 求、DELETE请求和PATCH请求,使用比较少。

3.consumes属性 该属性指定处理请求的提交内容类型(Content-Type)。

1
@RequestMapping(consumes = "application/json",value = "/index")

表示方法仅处理request Content-Type为“application/json”类型的请求。

4.produces属性,该属性指定返回的内容类型,返回的内容类型必须是 request 请求

头(Accept)中所包含的类型。

1
@RequestMapping(produces = "application/json",value = "/index")

方法仅处理request请求中Accept头中包含了"application/json"的请 求,同时指明了返回的内容类型为application/json。

5.params属性 该属性指定request中必须包含某些参数值时,才让该方法处理。

1
@RequestMapping(params = "param=value",value = "/index")

方法仅处理其中名为“param、值为“value”的请求。

@RequestMapping 不但支持标准的 URL,还支持 Ant 风格(?、*和**字符)的和带{xxx}占位符的URL。以下 URL 都是合法的。


  • /user/*/createUser∶ 匹配/user/aa/createUser、/user/bbb/createUser等 URL。
  • /user/**/createUser∶匹配/user/createUser、/user/aaa/bbb/createUser 等URL。
  • /user/createUser??∶匹配/user/createUseraa、/user/createUserbb等 URL。
  • /user/{userld}∶匹配 user/123、user/456 等URL。

  • /user/**/{userId}∶匹配 user/aa/bbb/123、user/aaa/456等 URL。

  • company/{companyld}/user/{userId}/detail∶匹配 company/123/user/456/detail 等
URL。

请求处理方法参数

Spring MVC 对控制器处理方法签名的限制是很宽松的,用户几乎可以按自己喜欢的方式进行方法签名。在必要时对方法及方法入参标注相应的注解(如@PathVariable、@RequestParam、@RequestHeader 等)即可,Spring MVC 会优雅地完成剩下的工作∶将 HTTP请求的信息绑定到相应的方法入参中,并根据方法返回值类型做出相应的后续处理。

@RequestParam

在Servlet中我们知道,如果需要获取请求参数,需要使用HttpServletRequest对象的getParameter()方法,在SpringMVC中,只需要将方法参数列表中参数名称和请求参数名称保持一致,SpringMVC会自动将同名请求参数绑定到方法参数中,下面将示例这种用法:

新建页面,在页面中放置表单

1
2
3
4
5
6
<body>
<form action="${pageContext.request.contextPath}/index" method="get">
<input name="name" value="">
<input type="submit" value="提交">
</form>
</body>

定义Controller,并将页面提交的数据

1
2
3
4
5
6
7
8
@Controller
public class IndexController {
@GetMapping("/index")
public String index(String name){
System.out.println(name);
return null;
}
}

需要注意的是页面中参数的name要和方法的参数名称保持一致,否则参数无法映射。如果出现页面中提交的参数名称不一致的情况,也可以使用@RequestParam注解将请求参数和方法参数进行绑定。以上例为例,假设页面中标签的name属性为username,那么在controller的方法参数上可以做如下修改:

1
2
3
4
5
6
7
8
@Controller
public class IndexController {
@GetMapping("/index")
public String index(@RequestParam("username") String name){
System.out.println(name);
return null;
}
}

查看@RequestParam注解源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {

@AliasFor("name")
String value() default "";

@AliasFor("value")
String name() default "";

boolean required() default true;


String defaultValue() default ValueConstants.DEFAULT_NONE;

}

各个属性含义如下:

属性 类型 是否必须 说明
name String 指定请求参数绑定的名称
value String name属性的别名
required boolean 指定参数是否必须绑定
defaultValue String 如果没有传递参数则使用默认值

在@RequestParam注解的属性中需要特别注意的是required属性,该属性指参数是否必须绑定,默认是true,也就是说如果不绑定参数值,即没有向服务端传递对应的参数,程序将抛出异常。

多个请求参数的绑定

在上一小节的示例中可以知道通过请求参数和方法参数的绑定,如果请求参数较少的情况下可以使用这种方式,但是如果请求参数较多的情况下这种方式显得不那么方便,此时,可以将请求参数封装到VO类中,例如登录时,通常需要填写用户名和密码,此时就可以将用户名和密码封装到一个VO中,需要注意的是,请求参数的名称必须要和VO对象的实例变量名称相同如下例所示:

1
2
3
4
5
6
7
<body>
<form action="${pageContext.request.contextPath}/login" method="get">
<input name="username" value=""></br>
<input name="password" value=""></br>
<input type="submit" value="提交">
</form>
</body>
1
2
3
4
5
6
7
8
9
@Controller
public class LoginController {
@PostMapping("/login")
public String login(UserVO userVO){
System.out.println(userVO.getUsername());
System.out.println(userVO.getPassword());
return "success.jsp";
}
}

这种方式不但简化了请求参数的接收,也方便数据合法性的验证,关于数据验证的内容将会在下一章节进行讲解。

视图解析器

请求处理方法执行完成后,最终返回一个ModelAndView对象。对于那些返回String、View 或 ModelMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个ModelAndView 对象,该对象包含了视图逻辑名和模型对象的信息。
Spring MVC借助视图解析器(ViewResolver)得到最终的视图对象(View),这可能是我们常见的 JSP 视图,也可能是一个基于FreeMarker、Velocity 模板技术的视图,还可能是PDF、Excel、XML、JSON 等各种形式的视图。


视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。视图对象可以是常见的 JSP,还可以是 Excel或PDF 等形式不一的媒体形式。为了实现视图模型和具体实现技术的解耦,Spring在 org.springframework.web.servlet 包中定义了一个高度抽象的 View接口,该接口中定义了两个方法。
口 String getContentType()∶视图对应的 MIME类型,如 text/html、image/jpeg 等。口 void render(Map model,HttpServletRequest request,HttpServletResponse response)∶将
模型数据以某种MIME 类型渲染出来。
视图对象是一个 Bean,通常情况下,视图对象由视图解析器负责实例化。由于视图 Bean是无状态的,所以它们不会有线程安全的问题。


不同类型的视图实现技术对应不同的View实现类,这些视图实现类都位于org.springframework.web.servlet.view包中,常见的View如下:

视图类型 说明
InternalResourceView 将JSP活其他资源封装成一个视图,这InternalResourceViewResolver默认使用的视图实现类
JstlView 如果JSP文件中使用了JSTL国际化标签的功能,则需要使用该视图
FreeMakerView 使用FreeMaker模板引擎的视图
MappingJackson2JsonView 将模型数据通过Jackson开源框架的ObjectMapper以JSON方式输出

返回视图

在web开发中通常处理完一个请求后,需要跳转到某个页面或者Controller,本小节将会详细介绍如何返回视图。

利用方法返回值

在SpringMVC中通过控制器内的方法来接受请求并处理请求,在定义方法时,可以将方法返回值定义成String类型,当处理请求后,直接返回视图的名称,SpringMVC会自动寻找返回值对应的视图,并进行跳转

1
2
3
4
@GetMapping(value = "/studentList")
public String findAll(){
return "studentList.jsp";
}

使用View

在上面的内容中讲解了集中常见的View类型,如果使用View跳转页面时,可以将方法的返回值定义为View类型,在方法中创建View子类对象,从而进行视图的返回

1
2
3
4
5
6
@GetMapping(value = "/studentList")
public View findAll(){
JstlView jstlView = new JstlView();
jstlView.setUrl("studentList.jsp");
return jstlView;
}

在实际开发中,有时候不但需要跳转页面还需要把从数据库获取的数据传递到页面。很明显此时仅仅返回String或者View是不能满足需要的。那么如何解决这个问题呢?

针对这个问题,可以在方法参数中传入Model对象,Model对象用于将数据发送到视图,因为SpringMVC跳转视图默认使用请求转发方式,因此Model通常将数据封装进request对象。当页面跳转是,会自动将数据发送到视图。Model通过如下方法添加数据:

1
Model addAttribute(String attributeName, @Nullable Object attributeValue);

下面的实例将演示如何使用Model传递数据:

1
2
3
4
5
6
@GetMapping(value = "/studentList")
public String findAll(Model model){
List<Student> list = studentService.findAll();
model.addAttribute("list",list);
return "studentList.jsp";
}

使用ModelAndView

控制器处理方法的返回值如果是ModelAndView,则其既包含模型 数据信息,也包含视图信息,这样Spring MVC将使用包含的视图对模型 数据进行渲染。可以简单地将模型数据看成一个Map<String,Object>

对象。在处理方法中可以使用ModelAndView对象的如下方法添加模型数据:

1
addObject(String attributeName,Object attributeValue);

可以通过如下方法设置视图:

1
setViewName(String viewName);

下面示例ModelAndView的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping(value = "/studentList")
public ModelAndView findAll(){
List<Student> list = studentService.findAll();
ModelAndView modelAndView = new ModelAndView();
//封装视图
modelAndView.setViewName("studentList.jsp");
//封装数据
modelAndView.addObject("list",list);
model.addAttribute("list",list);
JstlView view = new JstlView();
view.setUrl("studentList.jsp");
return modelAndView;
}

视图解析

在web开发中,通常会将页面放置在WEB-INF目录下,如果在JSP/Servlet项目中,可以利用过滤器对请求进行转发,在SpringMVC中可以以如下方式配置:

1
2
3
4
<bean id="internalResourceView" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>

其中prefix配置了页面跳转的前缀路径,而suffix通常配置视图的扩展名,这样在跳转视图时,只需要写视图名称即可。例如跳转到/WEB-INF/success.jsp时,只需要填写视图名称success。

静态资源处理

在使用SpringMVC开发应用时,因为所有的请求都交由了DispatcherServlet处理,而DispatcherServlet并不会对静态资源进行处理,这就需要手动配置静态资源的位置。

<mvc∶resources/允许静态资源放置在任何地方,如 WEB-INF 目录下、类路径下等,甚至可以将 JavaScript 等静态文件打包到JAR 包中。通过location 属性指定静态资源的位置,由于location 属性是Resource类型,因此可以使用诸如”classpath∶”等的资源前缀指定资源位置。传统 Web 容器的静态资源只能放在 Web 容器的根路径下,<mvc∶resources/>则完全打破了这个限制。

在接收到静态资源的获取请求时,会检查请求头的Last-Modified值。如果静态资源没有发生变化,则直接返回 303响应状态码,指示客户端使用浏览器缓存的数据,而非将静态资源的内容输出到客户端,以充分节省带宽,提高程序性能。



在SpringMVC配置文件中进行如下配置:

1
<mvc:resources mapping="/static/**" location="/WEB-INF/static"></mvc:resources>

其中mapping属性配置的是访问静态资源时的路径,而location属性则是指静态资源实际存放的位置。

@SessionAttribute和@CookieValue

@SessionAttribute注解允许我们有选择地指定Model中的哪些属性转存到 HttpSession对象当中。 该注解源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionAttribute {

/**
* 指定请求参数绑定的名称
*/
@AliasFor("name")
String value() default "";

/**
* 同name
*/
@AliasFor("value")
String name() default "";

/**
* 指定参数是否必须绑定
*/
boolean required() default true;

}

示例代码如下:

1
2
3
@RequestMapping(value="/arrtibuteTest")
public void arrtibuteTest (@SessionAttribute(value="username")String username){
}

以上代码会自动将session作用域中名为username的属性的值设置到username参数上。 @CookieValue的作用和@SessionAttribute的作用类似,只是作用域由Session变成了Cookie。