目录
1.前端控制器 DispatcherServlet(不需要工程师开发,由框架提供)
2.处理器映射器 HandlerMapping(不需要工程师开发,由框架提供)
3.处理器适配器 HandlerAdapter(不需要工程师开发,由框架提供)
5.视图解析器 ViewResolver (不需要工程师开发,由框架提供)
拦截器(Interceptor)和过滤器(Filter)的区别
4.3.2 Controller ----> Controller
4.5.1 Servlet api 中的 HttpServletRequest对象
2、使用@RequestParam注解的Map参数,object>
3、使用@RequestHeader注解的Map参数,object>
4、使用@PathVariable注解的Map参数,object>
一、MVC模式
之前详解了Spring体系结构的两大核心:Spring IOC和Spring AOP,今天主要谈Spring MVC。
Spring MVC 基于 MVC 模式,因此理解 Spring MVC 需要先对 MVC 模式有所了解。
1.1 MVC模式的发展
1.1.1 Model1 模型
Model1 模型是很早以前项目开发的一种常见模型,项目主要由 jsp 和 JavaBean 两部分组成。
它的优点是:
结构简单。开发小型项目时效率高。
它的缺点也同样明显:
- 第一:JSP 的职责兼顾于展示数据和处理数据(也就是干了控制器和视图的事)
- 第二:所有逻辑代码都是写在 JSP 中的,导致代码重用性很低。
- 第三:由于展示数据的代码和部分的业务代码交织在一起,维护非常不便。 所以,结论是此种设计模型已经被淘汰没人使用了。
在Model 1模式下,整个Web应用几乎全部由JSP页面组成,JSP页面接收处理客户端请求,对请求处理后直接做出响应。用少量的JavaBean来处理数据库连接、数据库访问等操作。
1.1.2 Model2 模型
Model2 模型是在 Model1 的基础上进行改良,它是 MVC 模型的一个经典应用。它把处理请求和展示数据进行分离,让每个部分各司其职。 此时的 JSP 已经就是纯粹的展示数据了,而处理请求的事情交由控制器来完成,使每个组件充分独立,提高了代码可重用性和易维护性。所以这个就是最终形态的MVC模型了。下图展示的就是 Model2 模型:
Model 2是基于MVC架构的设计模式。 在Model 2架构中,Servlet作为前端控制器,负责接收客户端发送的请求在Servlet中只包含控制逻辑和简单的前端处理; 后端JavaBean来完成实际的逻辑处理; 最后,转发到相应的JSP页面处理显示逻辑。 Model 2具有组件化的特点,更适用于大规模应用的开发。
1.2 MVC模式简介
MVC模式是软件工程中常见的一种软件架构模式,该模式把软件系统(项目)分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。MVC就是 Model、View、和Controller的缩写。
MVC各部分根据职责进行分离,使程序的结构更为直观,增加了程序的可扩展性、可维护性、可复用性。
可以用如下的图形来表示MVC三者之间的关系:
1.模型(Model)
模型封装了数据及对数据的操作,可以直接对数据库进行访问,不依赖视图和控制器,也就是说模型并不关注数据如何展示,只负责提供数据。GUI 程序模型中数据的变化一般会通过观察者模式通知视图,而在 web 中则不会这样。
2.视图(View)
视图从模型中拉取数据,只负责展示,没有具体的程序逻辑。
3.控制器(Controller)
控制器用于控制程序的流程,将模型中的数据展示到视图中。
二、Spring MVC模型简介
SpringMVC 全名叫 Spring Web MVC,是⼀种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架。它以SpringIOC容器为基础,并利用容器的特性来简化它的配置,所以 SpringMVC 和 Spring 可直接整合使用,是Spring框架的一个模块 。
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。SpringMVC是一种web层的mvc框架,用于替代servlet(处理响应请求,获取表单参数,表单验证等)。所以Spring MVC是Spring体系结构的一部分,如下图所示:
Spring MVC其实就一种基于Servlet的MVC模型:
- 模型:一个或多个JavaBean对象,用于存储数据和业务逻辑。
- 视图:一个和多个JSP页面,想控制器提交数据和为模型提供数据显示,JSP页面主要使用HTML标记和JavaBean标记来显示数据。
- 控制器:一个或多个Servlet对象,根据视图提交的请求进行控制,即将请求转发给业务逻辑的JavaBean,并将处理记过存放到实体模型JavaBean中,输出给视图显示。
Spring MVC 本质可以认为是对servlet的封装,简化了我们serlvet的开发。
Spring容器和Spring MVC容器是父子容器的关系。Spring容器中可以装配Spring MVC容器中的Bean吗?
同一个Bean是可以同时装配到父容器和子容器的(也就是Spring容器和Spring MVC容器可以同时存在这个Bean)
Spring容器不能使用Spring MVC中的Bean,但是Spring MVC可以使用spring容器中的Bean。父容器不能用子容器中的Bean,但是子容器可以用父容器中的Bean。
三、Spring MVC 六大核心组件
3.1 六大组件简介
1.前端控制器 DispatcherServlet(不需要工程师开发,由框架提供)
DispatcherServlet本质上是一个Servlet,相当于一个中转站,所有的访问都会走到这个Servlet中,再根据配置进行中转到相应的Handler中进行处理,获取到数据和视图后,在使用相应视图做出响应。
2.处理器映射器 HandlerMapping(不需要工程师开发,由框架提供)
HandlerMapping本质上就是一段映射关系,将访问路径和对应的Handler存储为映射关系,在需要时供前端控制器查阅(即根据请求的url查找Handler)。
Spring MVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3.处理器适配器 HandlerAdapter(不需要工程师开发,由框架提供)
本质上是一个适配器,可以根据要求(HandlerAdapter要求的规则)找到对应的Handler来运行。
由于 Handler 涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发 Handler。
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4.处理器 Handler(需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler。
Handler叫做处理器,也叫控制器,是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
5.视图解析器 ViewResolver (不需要工程师开发,由框架提供)
本质上也是一种映射关系,可以将视图名称映射到真正的视图地址。前端控制器调用处理器适配完成后得到model和view,将view信息传给视图解析器进行视图解析,来得到真正的view。
6.视图渲染 View (需要工程师开发jsp...)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)。 View对象是通过视图解析器生成的,它的作用就是将handler处理器中返回的model数据嵌入到视图解析器解析后得到的页面中,向客户端做出响应。
3.2 Spring MVC中的handler究竟是什么?
了解过Sping MVC流程的同学一定听说过handler,百度翻译过来是处理者,很多博客中称之为处理器。那就按照大部分人的说法称呼它为控制器,说到控制器,会不会联想到我们平常写业务代码中的各种controller,也是控制器,那他们两个是不是一种东西呢?这里可以大胆猜测一下就是一种东西,现在通过源码进行验证猜测!
如果直接从源码中按照类文件类型直接搜索Handler是找不到的,根据Spring MVC的工作流程开始捋(有很多帖子说过这里不在重述),最早出现handler是在这个地方:
AbstractHandlerMapping.java中getHandler()
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 获取handler,如果获取为空则使用默认handler,如果默认的也没有则返回null
Object handler = getHandlerInternal(request);
// 省略部分代码....
}
测试案例发送实际请求:
@RequestMapping("/test")
@RestController
public class Test {
@GetMapping("/add")
public String add(String a,Personal personal,@RequestParam(name = "genderType") int gender) {
int i=0;
return a;
}
}
对应请求来看一下debug
Object handler = getHandlerInternal(request); 中handler具体是什么内容,截图如下:
从中可以看到handler实际上是一个HandlerMethod类型的对象,里面的属性有请求所在的类信息、请求方法、请求参数等内容。所以从这里可以认为handler相当于是平常业务代码中每个请求对应的的Controller类以及方法信息。上面debug截图对应起来更容易理解!
总结:
Handler是什么?
- Handler是一个Controller的对象和请求方式的组合的一个Object对象。其实可以粗略的认为Handler就是Controller对象(也分情况,有的类型的处理器就可以认为是Controller中的方法),里面是程序员编写的处理请求的逻辑。
- HandleExcutionChains是HandleMapping返回的一个处理执行链,它是对Handler的二次封装,将拦截器关联到一起。然后,在DispatcherServlert中完成了拦截器链对handler的过滤。
- DispatcherServlet要将一个请求交给哪个特定的Controller,它需要咨询一个Bean——这个Bean的名字为“HandlerMapping”。HandlerMapping是把一个URL指定到一个Controller上,(就像应用系统的web.xml文件使用<servlet-mapping>将URL映射到servlet)。
3.3 Spring MVC拦截器
Spring MVC提供了拦截器支持,这也算是Spring MVC的一个组件,是利用AOP实现的。
Servlet提供了过滤器(Filter)和监听器(Listener),Spring MVC提供了拦截器(Interceptor)。
下面简单介绍一下它的使用方法,我们以后会单独对拦截器进行详细讲解。
1、创建自定义的拦截器类,实现HandlerInterceptor接口
true表示放行,false表示拦截。
三种拦截器:
- preHandler: 请求通过DispatcherServlet后,到达后端Handler之前进行拦截;
- postHandler: 后端Handler处理完后,请求返回到DispatcherServlet之前进行拦截;
- afterHandler: 视图解析器View处理完后,请求返回DispatcherServlet之前进行拦截;
2、注册拦截器
XML注册:
在springmvc.xml中进行注册
<!--注册拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!-- 要拦截的路径 -->
<mvc:mapping path="/**"/>
<!-- 排除拦截的路径 -->
<mvc:exclude-mapping path="/login"/>
<!-- 拦截处理类(拦截器类)-->
<bean class="com.jd.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
- bean:mvc:interceptors 标签下的拦截器 bean 将应用到所有的处理器。
- mvc: interceptor:这个标签下的子标签可以指定拦截器应用到哪些请求路径。
- mvc: mapping:指定处理的请求路径。
- mvc: exclude-mapping:指定排除的请求路径。
- bean:指定应用到给定路径的拦截器 bean。
注解注册:
对于注解配置来说,需要将 MappedInterceptor 配置为 Spring 的 bean。
@Configuration
public class MvcConfig {
@Bean
public MappedInterceptor logInterceptor() {
return new MappedInterceptor(null, new LoginInterceptor());
}
@Bean
public MappedInterceptor loginInterceptor() {
return new MappedInterceptor(new String[]{"/**"}, new String[]{"/login"}, new LoginInterceptor());
}
}
API 配置:
拦截器与 Spring MVC 环境紧密结合,并且是作用范围通常是全局性的,因此大多数情况建议使用这种方式配置。
这里在配置类上添加了@EnableWebMvc注解开启了 Spring MVC 中的某些特性,然后就可以实现 WebMvcConfigurer 接口中的 addInterceptors 方法向 Spring MVC 中添加拦截器。如果你使用了 spring-boot-starter-web,不再需要手工添加 @EnableWebMvc 注解。
拦截器(Interceptor)和过滤器(Filter)的区别
- 过滤器是基于函数回调的,而拦截器是基于Java反射的。
- 过滤器依赖于servlet容器(是Servlet提供的组件),而拦截器不依赖与Servlet容器,拦截器依赖 Spring MVC(是Spring MVC提供的组件)。
- 过滤器几乎对所有的请求都可以起作用,而拦截器只能对Spring MVC请求起作用。
- 拦截器可以访问处理方法的上下文,而过滤器不可以。
两者的执行流程图:
- 过滤器是在到达DispatcherServlet之前进行过滤(所以过滤器几乎能对所有的请求起作用);
- 拦截器是经过DispatcherServlet后进行拦截(因为DispatcherServlet是Spring MVC提供的,所以拦截器只能拦截对Spring MVC的请求)。
层次关系图:
四、Spring MVC的使用
4.1 环境搭建
这里我们以xml配置为例来讲解。其实也可以用注解来进行配置,但是用xml配置更能帮助我们理解底层的原作原理。
4.1.1 引入依赖
<dependencies>
<!-- Spring MVC的核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!-- Spring MVC是基于servlet的,所以也要引入servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
<!--这里的scope是一定要加的 不然程序会出问题-->
<scope>provided</scope>
</dependency>
<!-- Spring MVC底层在帮我们进行JSON格式的解析和与Java属性之间的映射,Spring MVC 使用jackson作为默认的json转换器,所以,需要我们引入jackson依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
</dependencies>
4.1.2 配置web.xml
Spring MVC 已经提供了一个 DispatcherServlet 类作为前端控制器,Java Web项目只要是使用servlet,就需要配置web.xml,所以要使用 Spring MVC 必须在web.xml 中配置前端控制器。
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置springmvc的核心servlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<!-- 配置Spring MVC前端控制器 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定 Spring 容器启动加载的配置文件-->
<init-param>
<!--配置Spring MVC配置文件的位置-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- Tomcat 启动初始化 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置前端控制器servlet映射路径 -->
<servlet-mapping>
<!--这里的url配置的是/,说明要拦截所有请求,来将请求转到DispatcherServlet去做处理-->
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 添加 register.jsp 为首页,也就是URL访问项目名称时会默认跳转到这个页面 -->
<welcome-file-list>
<welcome-file>register.jsp</welcome-file>
</welcome-file-list>
</web-app>
其中<param-value>标签中的**.xml 这里可以使用多种写法:
- 什么也不写,使用默认值:/WEB-INF/-servlet.xml
- /WEB-INF/classes/springMVC.xml
- classpath*:springmvc-config.xml
- 多个值用逗号分隔
注意:
load-on-startup 元素是可选的:若值为 0 或者大于 0 时,表示容器在应用启动时就构建 Servlet 并调用其 init 方法做初始化操作(非负数的值越小,启动该 Servlet 的优先级越高);若值为一个负数时或者没有指定时,则在第一次请求该 Servlet 才加载。配置的话,就可以让 Spring MVC 初始化的工作在容器启动的时候完成,而不是丢给用户请求去完成,提高用户访问的体验性。
4.1.2.1 配置映射路径的注意点
配置前端控制器的映射路径一般有以下的三种形式:
- 配置如 .do、.htm 是最传统方式,可以访问静态文件(图片、 JS、 CSS 等),但不支持 RESTful风格。
- 配置成 /,可以支持流行的 RESTful 风格,但会导致静态文件(图片、 JS、 CSS 等)被拦截后不能访问。
- 配置成 /*,是错误的方式,可以请求到 Controller 中,但跳转到调转到 JSP 时被拦截,不能渲染JSP 视图,也会导致静资源访问不了。
4.1.2.1.1 访问静态资源和 JSP 被拦截的原因
Tomcat 容器处理静态资源是交由内置 DefaultServlet 来处理的(拦截路径是 /),处理 JSP 资源是交由内置的 JspServlet 处理的(拦截路径是*.jsp | *.jspx)。
启动项目时,先加载容器的 web.xml,而后加载项目中的 web.xml。当拦截路径在两者文件中配置的一样,后面会覆盖掉前者。
所以前端控制器配置拦截路径是 / 的所有静态资源都会交由前端控制器处理,而拦截路径配置 /*,所有静态资源和 JSP 都会交由前端控制器处理。
4.1.2.1.2 如何解决
1、方式一
在 web.xml 中修改,修改前端控制器的映射路径修改为*.do,但注意,访问控制器(Controller)里的处理方法时,请求路径须携带 .do。
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
2、方式二
在 springmvc.xml中加入一段配置,这个配置会在 Spring MVC 上下文中创建存入一个
DefaultServletHttpRequestHandler 的 bean,它会对进入DispatcherServlet的请求进行筛查,若不是映射的请求,就将该请求交由容器默认的 Servlet处理。
<mvc:default-servlet-handler/>
4.1.3 编写Spring配置文件
在 web.xml 中配置了 DispatchcerServlet,DispatchcerServlet 加载时需要一个 Spring MVC 的配置文件,默认会去 WEB-INF 下查找对应的 [servlet-name]-servlet.xml 文件(所以说Spring的xml配置和Spring MVC的xml配置是两个独立的配置文件),如本例中默认查找的是 springmvc-servlet.xml。
Spring MVC 的配置文件可以放在任何地方,用 servlet 的子元素 init-param 标签描述即可。
<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 1. 开启注解扫描-->
<context:component-scan base-package="com.lin.controller"/>
<!--开启Spring MVC,其实就是将它的组件都作为Bean装配到Spring容器中-->>
<!-- 2. 配置处理器映射器-->
<!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />-->
<!-- 3. 开启处理器适配器-->
<!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />-->
<!-- 上面两段配置可以被下面的一句话所替代(封装) 这个注解里封装了所有种类的处理器映射器和适配器,所以添加这个标签以后就可以不用单独配置处理器映射器和处理器适配器了,所有的实现类都可以使用-->
<mvc:annotation-driven />
<!-- 4. 开启视图解析器-->
<!-- 定义了一个名为 “viewResolver” 的 InternalResourceViewResolver 管理器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--通过 <property> 标签设置视图的前缀和后缀 配置这个Spring MVC 找视图的路径就是:前缀 + 逻辑视图名(处理方法设置或返回视图名)+ 后缀名-->
<!--prefix 属性指定了视图文件的路径前缀-->
<property name="prefix" value="/"/>
<!-- suffix 属性指定了视图文件的后缀名 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
注:在 Spring4.0 之后,如果不配置处理映射器、处理器适配器和视图解析器,会使用默认的。
在 spring-mvc.xml 的配置文件里加上 <mvc:annotation-driven></mvc:annotation-driven> 相当于在 xml 中同时加入了如下配置,当然如果不想要这么多,也可以单独配置想要的 HandlerMapping 等 bean 信息。
<!-- HandlerMapping -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!-- HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- HadnlerExceptionResolvers -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"></bean>
<bean class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver"></bean>
<bean class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"></bean>
这里讲一下视图解析器的配置,一般如果在Controller里面要返回视图视图页面的话,会在Controller方法return一个字符串,这个字符串就是那个视图文件的名字(例如html文件名,注意是不带文件类型后缀的),在Spring MVC 程序运行时,Thymeleaf 视图解析器会将视图的前缀和后缀与Controller返回的逻辑视图名拼接,组成真正的 Thymeleaf 文件路径,生成真正的View类型的对象,然后再通过这个View接口的实现类把 Model 数据渲染到这个 Thymeleaf 中(将model渲染到view的操作是View对象完成的),以达到将视图展示给用户的目的。
也就是说配置中的前缀,就是存放视图文件的路径,后缀就是这个视图文件的类型后缀(例如html、jsp等)。
如果不配置这个前缀和后缀的话,那么在Controller方法中返回视图名称字符串的时候,就需要返回视图文件的全路径,并且要带着视图文件格式后缀,不能只返回视图文件的名称,例如:
@Controller
public class ResponseController {
@RequestMapping("/resp2")
public String resp2(Model model) {
// 往作用域或者模型中存入数据
model.addAttribute("msg", "方法返回类型是 String");
// 返回视图全路径,并且要带着文件类型后缀
return "/WEB-INF/views/resp.jsp";
}
}
4.1.4 编写控制器Controller
@Controller
public class HellowController {
/*
@RequestMapping的修饰范围:可以用在类上和方法上,他的作用如下:
1. 用在方法上可以给当前方法加入指定的请求路径
2. 用在类上可以给类中的所有方法都加入一个统一的请求路径,在这个方法访问之前都必须加上
*/
@RequestMapping("/hello")
public String hello(String username,String password){
System.out.println("hello");
// 返回视图名称
return "index";
}
}
Spring MVC有三种不同的实现方式
- 通过实现Controller接口,控制器有返回值。它的处理器适配器实现是一个叫SimpleControllerHandlerAdapter的类实现的。
- 通过Controller注解。基本流程和接口实现的完全一样,只是有一些细节上的差别,比如处理器适配器是RequestMappingHandlerAdapter实现的,这一点和通过接口是实现是有区别的。
- 实现HttpRequestHandler接口,这个没有返回值。这个方法用的不多。基本流程也是一样的,只有一些不同,比如这个的处理器适配器用的是HttpRequestHandlerAdapter(所以handle方法的实现都不太一样)。
上面三种方法的控制器应该都是一样的,都是DispatcherServlet,都调用了doService方法。
4.2 Spring MVC相关注解简介
4.2.1 @Controller
该注解作用于类上,用来标识这是一个控制器组件类并创建这个Bean,告诉spring我是一个控制器。
4.2.2 @RequestMapping
这个注解可以作用在方法上或者是类上,用来指定请求路径。
4.3 Spring MVC的跳转方式
传统的Servlet开发跳转方式有两种:
- forward(请求转发):forward跳转,是在服务器内部跳转,所以是同一次请求,地址栏不变。跳转时可以携带数据进行传递(使用request作用域进行传递)。
- redirect(重定向):redirect跳转是客户端跳转,所以是多次请求,地址栏会改变,跳转时不可以携带数据传递,不共享之前请求的数据。
在请求转发和重定向的时候,我们一般有两种方式来写请求路径:
- 加/:使用是绝对路径(推荐使用),从项目根路径查找。(/response/test6 ---> "redirect:/hello.html" ---> localhost:/hello.html)
- 不加/:使用是相对路径,相对于当前路径来查找。(/response/test6 ---> "redirect:hello.html" ---> localhost:/response/hello.html)
4.3.1 Controller ----> 前台页面
4.3.1.1 forward
通过测试我们可以发现,Spring MVC默认的就是使用请求转发的方式来进行跳转到前台页面的;
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
// 默认使用请求转发跳转到前台页面 就相当于request.getRequestDispatcher().forward(request,response)
return"index";
}
}
也可以在return的时候加上请求转发关键字,但是加了关键字后,配置的视图解析器就不起作用了。返回视图必须写全路径。
return "forward:/WEB-INF/views/welcome.jsp";
4.3.1.2 redirect
如果我们想使用重定向的方式来进行跳转的话,需要使用Spring MVC提供给我们的关键字——redirect:来完成。
语法:
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
// 相当于 response.sendRedirect()
// 注意:在redirect:后接页面的不是逻辑名,而是全路径名。因为redirect跳转不会经过视图解析器。
return "redirect:/视图全路径名";
}
}
注意:在redirect:后接页面的不是逻辑名,而是全路径名。因为redirect跳转不会经过视图解析器。
4.3.2 Controller ----> Controller
4.3.2.1 forward
如果我们想使用请求转发的方式跳转到相同(不同)Controller的不同方法的时候,我们也需要使用Spring MVC提供的关键字:forward:。
语法:
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
return:"forward: /需要跳转的类上的@RequestMapping的值/需要跳转的方法上的@RequestMapping的值;"
}
}
4.3.2.2 redirect
如果我们想使用重定向的方式跳转到相同(不同)Controller的不同方法的时候,我们也需要使用Spring MVC提供的关键字:redirect:。
语法:
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
return:"redirect: /需要跳转的类上的@RequestMapping的值/需要跳转的方法上的@RequestMapping的值;"
}
}
4.4 Spring MVC的参数接收
前端向后台传递参数的方式:
- 通过 Servlet api 中的 HttpServletRequest对象
- 基本数据类型 + String类型
- Array 数组
- Java Bean 对象
- List 集合
- Map 集合
- JSON 格式
4.4.1 Servlet接收参数的方式
这种方式想必是学习了Java Web之后,最熟悉的一种获取前端页面传递参数的方式。直接在服务器端,使用对HttpServletRequest对象进行操作即可。
在传统的Servlet开发,我们一般都是用这种方式来进行接收请求参数的。
// 接收名字为name的参数
request.getParameter(name)
通过request对象的getParameter()方法直接获取指定的key属性名,即可获取到对应的value属性值,然后赋值给了name变量。
在SpringMVC的Controller中,这种传统方式也是存在的。具体代码如下:
import org.springframework.stereotype.Controller;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("/param")
public class ParamController{
@RequestMapping("/servlet")
// 在Controller参数中写上HttpServletRequest,就可以获取到request对象
public String servlet(HttpServletRequest request){
// 通过request获取参数
String name = request.getParameter("name");
System.out.println("name:" + name);
request.setAttribute("result", "hello " + name);
return "hello";
}
}
这种方式需要注意的点,无非就是在Controller具体的请求处理方法中,需要传入一个HttpServletRequest对象,来作为当前方法的形参,然后就可以在此方法中,使用这个request对象了,这个对象跟之前传统的方法中的request对象是一致的。通过它,可以获取到很多的内容,getSession(),getHeader(),getAttribute()等等。
在前端页面中,访问的时候,只需要指定url的路径,然后利用key=value的形式访问即可。无需书写其他的代码,例如:
http://localhost:8080/param/servlet?name=golden3young
这样,后端接收到name参数的之后,使用System.out.println("name:" + name);即可将值打印到控制台上。
Servlet有几个需要注意的点:
- 参数要求是表单域的name属性。
- getParameter方法用于获取单个值, 返回类型是String。
- getParameterValues方法用于获取一组数据, 返回结果是String[]。
- 冗余代码较多, 使用麻烦, 类型需要自己转换。
Spring MVC使用的是控制器中方法形参列表来接收客户端的请求参数,它可以进行自动类型转换,要求传递参数的key要与对应方法的形参变量名一致才可以完成自动赋值。它的优势很明显:
- 简化参数接收形式(不需要调用任何方法和@RequestParam注解, 需要什么参数, 就在控制器方法中提供什么参数)。
- 参数类型不需要自己转换了。但是日期时间(默认为yyyy/MM/dd)得注意,需要在处理方法的 Date 类型的形参上使用@DateTimeFormat注解声明日期转换时遵循的格式, 否则抛出400异常。
@Controller
public class RequestController {
@RequestMapping("/req5")
// 注意形参的类型为 java.util.Date
public ModelAndView resp5(@DateTimeFormat(pattern="yyyy-MM-dd")Date date) {
System.out.println(date.toLocaleString());
return null;
}
}
如果日期在封装对象的字段,那么我们需要在字段的上贴@DateTimeFormat注解。
public class User {
private Long id;
private String Username;
private String password;
// 增加下面这个字段,并贴注解
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date date;
// 省略 setter getter toString
}
@Controller
public class RequestController {
@RequestMapping("/req6")
public ModelAndView resp6(User user) {
System.out.println(user);
return null;
}
}
4.4.2 基本数据类型 + String类型
我们可以在Spring MVC的Controller中具体请求处理方法中,直接使用基本数据类型或者String类型的参数进行对映,只要前端页面和方法中指定的参数名称是完全一致的,这里包括大小写,那么前端这个参数名称所赋有的值,就会传递到后台的处理请求的方法(Controller)中。其实这其中,是Spring MVC底层的拦截器帮我们实现的,它会将请求中传来的所有参数,跟我们所定义的请求处理方法中的参数进行对比,如果发现是一模一样的,它就会从请求中把那个参数的值拿出来赋给我们处理方法中的那个变量,于是,我们就可以直接使用了。
总的要求就是传递参数的key要与对应方法的形参变量名一致才可以完成自动赋值。
SpringMVC的Controller中,具体的请求处理方法:
/**
* Spring MVC的自动匹配参数
*
* 形参paramName会自动匹配请求中key为paramName的参数值。
*
* 可以接收AJAX封装的请求参数
*
* @param paramName
*/
@RequestMapping("/simple")
// 参数中不需要加再加@RequestParam等注解,只要是方法的参数名和请求参数的key一致就可以Spring MVC就可以自动匹配
public String simple(int id, String name, ModelMap modelMap){
System.out.println("id:" + id);
System.out.println("name:" + name);
modelMap.addAttribute("result", "Hello " + name + "," + id);
return "hello";
}
这里,我们可以看出,Controller中定义了,请求路径为/simple的请求,将直接执行simple()这个方法,而这个方法,我们给了3个形参,id、name、modelMap,这里,我们只关注int id, String name 即可,这两个参数的类型一个是int,属于基本数据类型之一;另一个是String字符串类型。那么这么写的意义是什么呢?其实就是前端页面在传递值的时候,如果有参数名为id或者name的属性,则会直接将属性的值赋给这两个变量。
前端页面,在访问时,只需要给出指定的url和传递指定名称的参数即可,例如:
http://localhost:8080/param/simple?id=1&name=golden3young
通过上面的url,可以看出,请求直接奔向/param/simple,而对应的方法恰好就是simple()方法,同时前端请求通过key=value的格式传递了两个参数,id 和 name, 而我们的simple()方法中,恰好就有同名的id 和 name,那么我们simple()方法中的id和name将获得前端请求传来的值,1 和 golden3young。同时,它们的类型,也会被转变成方法中定义的int和String类型。
有的同学可能发现了,前端请求在传递过程中,明明都是通过字符串格式传递的,而为什么到了Controller的simple()方法中,就变成了int和String类型?是怎么转换的?其实,这就是Spring MVC的拦截器为我们做的事情。它不光接收映射同名的参数,而且还会帮助我们将类型转换成功。但是,类型的转换,存在着问题,比如:int类型是整数类型,刚才传来的参数id就是int类型,而值恰好是1,则不会出现问题,可是如果将值改成abc等非数字的内容呢?那肯定是会报错的,因为数字格式异常,无法进行转换。同时,还有传递的时候忘记传递id属性而只传了name属性,或者id属性只给了key,而没有value值,这些情况,都会导致错误的出现。
罗列一下几种错误:
http://localhost:8080/param/simple?id=&name=golden3young
此时,id为空字符串,那么Spring MVC底层在为我们转换的时候,是将空串转成int类型,那肯定是会报错的。页面报错400.
http://localhost:8080/param/simple?name=golden3young
此时,我们没有传递id这个属性,那么Spring MVC底层在匹配的时候,一旦没有找到前端传来的属性,那么就会直接给Controller的方法中的参数赋值成null,那么把null转成int类型,同样也是会报错的。页面报错500.
那像以上几种情况,都需要大家在前端参数传递的时候,注意不要遗漏并且给出正确类型的数值。这里加一个小的拓展,有些情况,我们可能无法断定前端一定会传过来某一个属性,有可能不传,那这种情况,我们可以进行规范。使用的注解是@RequestParam。
4.4.2.1 @RequestParam 注解
其实通过Spring MVC接收参数,在Controller的方法参数上都可以使用@RequestParam注解。使用它和不适用它达到的效果是一样的,都可以接收参数,但是这个注解给我们提供了一些额外的拓展功能,可以帮助我们完成一些额外的事情,比如解决上面说的前端没有传递基本数据类型的参数,导致报错的情况。
下面我们简单讲解一下这个注解。
1、作用
@RequestParam:将请求参数绑定到你控制器的方法参数上(是Spring MVC中接收普通参数的注解)
2、语法
@RequestParam(value = "参数名", required = "true/false", defaultValue = "")
- value:接收的参数名
- required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。设置false的话,请求路径中就不用必须包含该参数
- defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值
我们可以通过设置参数的defaultValue来避免前端没有传递基本数据类型的参数,导致报错的问题了。
这个注解还可以解决一天个问题,那就是请求参数名和控制器方法参数列表形参不同名的情况下,如果不加注解Spring MVC是没办法将它们匹配上的。
如果前台传递过来的参数名和控制器方法中参数列表的形参参数名不相同的话,我们需要使用一个注解@RequestParam("前台携带的参数名")来告诉Spring MVC我们任何对数据来进行赋值。
// 请求路径为:/req1?username=zs&age=18
package cn.linstudy.web.controller;
@Controller
public class RequestController {
@RequestMapping("/req1")
public ModelAndView resp1(@RequestParam("username") String username1, @RequestParam("age") int age1) {
System.out.println(username);
System.out.println(age);
return null;
}
}
4.4.3 数组类型
这种方式,简单来说,就是在前端页面通过发送数组格式的数据,后台Controller的处理方法中,在接收参数的时候,直接转换成数组格式。跟 基本数据类型和String类型的 思想是一样的,但是转换的类型是不同的。
那这种方式,我们在前端页面中,经常用到的地方,其实就是form表单中的多选框,因为多选框的name都是一样的,但是值有多个,传递到后端页面后,传统方式是进行截取,而现在Spring MVC可以帮助我们完成底层的工作,直接给我们一个array数组。我们需要接收数组类型的时候,只需将要接收的数组类型直接声明为方法的形式参数,数组名和要传入的数组名一致即可。
示例:
前端页面代码
<form action="${pageContext.request.contextPath}/param/array" method="post">
爱好:
<input type="checkbox" name="hobby" value="唱歌" />唱歌
<input type="checkbox" name="hobby" value="跳舞" />跳舞
<input type="checkbox" name="hobby" value="书法" />书法
<input type="checkbox" name="hobby" value="滑雪" />滑雪
<br>
<input type="submit" value="提交" />
</form>
Controller中的处理方法
@PostMapping("/array")
// 方法上的数组名,和前端传递的数组名一致
public String array(String[] hobby){
for(String hobbyStr : hobby){
System.out.println(hobbyStr);
}
return "hello";
}
前端参数的name为hobby,value的值为多个不同的值,而后端Controller中的方法中,指定了同名hobby的一个String数组类型的参数,于是乎,Spring MVC底层,就把从前端传递过来的多个hobby的值,以数组的格式存放到了hobby数组中。我们使用了for循环进行了遍历打印。
4.4.4 对象类型(Java Bean实体类)
通过上面3种方式的介绍,大家其实也能感觉出来,SpringMVC大大简化了我们将请求中的参数进行转换的这样一个过程,我们只需要吃现成的即可。那么同样有这样一个问题,如果前端页面一次性要传递多个参数,比如十个以上,包括:id,age,name,birthday,gender,school,city,province,area,salary,married等等参数,如果按照上面学过的方法,我们需要把这所有的参数都写在执行方法的形参位置处即可。如:
@RequestMapping("/bean")
public String testBean(int id, int age, String name, String birthday, String school,
String city, String area, double salary, boolean married){
.....
}
这样显然写起来是非常麻烦,如果执行方法不光这一个,还有多个,都需要接收这些参数,那我们写起来就会浪费大量的无用功的时间。这时,我们就用到面向对象的编程思想,也就是创建一个对象,让这些参数都成为这个对象的属性,然后,Spring MVC就会将前端传来的这些参数的名称与我们指定的对象(Java Bean)的属性进行名称的对比,如果一致,那么就进行赋值,于是乎,我们先创建一个对象,用来存放所有的参数:
@Data
public class User{
private int id;
private int age;
private String name;
private String birthday;
private String school;
private String city;
private String area;
private double salary
private boolean married;
}
如果我们需要接收对象类型的话,直接将需要接收的对象作为控制器的方法参数声明即可。Spring MVC会自动封装对象,若传递参数key与对象中属性名一致,就会自动封装成对象。
那么这样,Controller中处理方法的代码就简化成了这样:
/**
* Spring MVC的自动装箱
*
* 如果我们需要接收对象类型的话,直接将需要接收的对象作为控制器的方法参数声明即可。
* Spring MVC会自动封装对象,若传递参数key与对象中属性名一致(只要参数的属性名和传入的key一致即可完成封装),就会自动封装成对象。
*
* @param paramsEntity
* @return
*/
@RequestMapping("/bean")
// 方法参数中直接写User即可,Spring MVC会将传进来的参数和User对象中的属性名匹配,名称相同的就会进行赋值,最终就会将传入的参数封装成user对象
// 这里方法参数名称并不重要,只要是对象的成员属性名称能和传入的参数名称匹配上就可以了
public String testBean(User user){
.....
}
然后,我们只需要在方法中,调用user.getter方法,来获取所有的属性值或者说参数值。
当然,这里有的同学可能会问,那底层是怎么实现的呢?SpringMVC底层其实还是将请求中的参数剥离出来,然后调用我们指定的这个对象(Java Bean)的setter方法来为同名的属性进行赋值,当我们用的时候,直接使用getter方法来用。有同学会问,那你刚才写的User类中没有写getter和setter啊,其实这里,我偷了个懒,使用的是lombok插件,它会自动为我们生成getter和setter,有兴趣的同学,可以自己学一下,非常简单。
那前端页面在传参的时候,还是保证访问指定的url,然后传递同名的参数即可,没有其他的变化,例如:
http://localhost:8080/param/bean?id=1&name=golden3young&age=18&married=false
根据我上面的url请求来看,后端Spring MVC在接收值的时候,只能映射到id,name,age,married这4个参数的值给User对象的同名属性,而其他的属性由于我没有传值,所以都会保持初始默认值。
4.4.5 集合类型
Spring MVC不能直接通过形式参数列表的方式接收集合类型的参数,想要Spring MVC自动将接收的参数转换成集合类型,有两种方法:
- 将List集合定义在Java Bean中
- 接收JSON格式数组
4.4.5.1 List集合
1、将集合定义在Java Bean中
如果需要接收集合类型的参数,可以将集合放入一个对象中,并且提供get/set方法,才可以。推荐放入VO对象中进行封装,进而使用对象类型来进行接收。
这种方式比较简单直接,需要先创建一个Bean对象,然后将我们需要接收的参数定义成这个Bean对象的一个属性,这里要求参数的name需要与属性名完全一致,这样Spring MVC底层就可以自动的将名称相同的参数和bean中对象的属性进行映射赋值,我们只需要使用即可。
这里,我们看一下后端的代码实现:
创建一个User对象(Java Bean)
public class User {
private List<Integer> idList;
// 必须有 getter 和 setter方法
public void setIdList(List<Integer> idList){
this.idList = idList;
}
public List<Integer> getIdList(){
return this.idList;
}
}
将想要接收的参数名称定义成这个类对象的一个属性,属性的类型需要定义成List集合形式,至于泛型也是可以自动进行转换的,可以根据自己的需求进行调整,不单单是String类型。例如这里,我们让它自动接收前端传来名为idList的多个参数,然后自动将其存储在List集合中,并且所有的idList参数都由字符串自动转成Integer类型。而我们只需要在Controller中,定义一下Bean对象参数即可,代码如下:
@RequestMapping("/testList")
// 参数的对象名不重要,只要是这个对象的参数名称和传递进来的参数key一样即可
public String testList(User user){
List<Integer> idList = user.getIdList();
idList.forEach(id -> System.out.println(id));
return "hello";
}
在Controller的处理方法中,我们只需要在方法参数列表中,声明一个User user对象,将我们刚才写有idList属性的类对象引入即可,不需要直接引入idList属性,而是引用包含它的Bean 对象,这样,我们就可以直接在方法中,使用user这个对象了,通过getIdList()属性,拿到转换后的List类型的参数。
前端页面在传递List类型参数的时候,可以直接用form表单传递,代码:
<h2>测试List传参</h2>
<form action="${pageContext.request.contextPath}/param/testList" method="post">
ID:
<input type="checkbox" name="idList" value="1" />1
<input type="checkbox" name="idList" value="2" />2
<input type="checkbox" name="idList" value="3" />3
<input type="checkbox" name="idList" value="4" />4
<br>
<input type="submit" value="提交" />
</form>
这里的代码是典型的发送List集合类型参数的前端代码,使用的是form表单中的checkbox多选框,由于多选框的值有多个,并且name属性相同,那么后端在接收起来时,就不再是一对一的关系,而是一个属性(字段)有多个值,那么正好可以使用List集合来存放。
如果前端页面不想使用form表单提交,那么还有第二种方式,可以通过js代码来发送JSON格式的数组信息来进行数据的提交,更加的灵活。
2、接收JSON格式数组
后端接收前端多个参数时,不仅是通过Bean对象的属性来进行转换存储,还可以直接接收,无需借助任何的变量类型。但是,这里就对前端传递参数的格式有了要求,也就是说,如果想在后端Controller中接收到前端传递的多个参数,并转换成List集合形式,那么就需要前端按照规范来进行传递,那到底是什么规范呢?那就是JSON格式。
先来看一下后端对于请求的处理方法:
@RequestMapping(value = "/jsonToList", method = RequestMethod.POST)
@ResponseBody // 此注解与本例无关,这个注解只是为了返回json格式的字符串
public String jsonToList(@RequestBody List<String> hobby){
for(String hob: hobby){
System.out.println(hob);
}
// 以下代码与本例无关
// Spring MVC 可以将json格式的字符串转换成json
return "{\"code\":200,\"msg\":\"SUCCESS\"}"; //JSON格式的字符串
}
详细剖析一下这个方法,首先@RequestMapping注解定义value属性来指定拦截的路径,为/jsonToList,method属性来指定拦截请求的类型为POST类型,其他的类型不作拦截。紧接着定义了一个名为jsonToList的处理方法,参数列表中定义了一个List< String>类型的变量名为hobby,这其实就是前端一会传来的参数,名字就是hobby,并且有多个,统一存放在List< String>类型的hobby变量中,但是这里非常重要的一点就是,这里使用了 @RequestBody 注解,只有加上这个注解,才能告诉springmvc底层,它需要自动为我们完成参数的转换映射赋值,否则,我们定义的List类型的hobby变量,是没有办法将前端传来的参数装到自己肚子里的,这个工作是springmvc底层帮我们做的,需要的就是 @RequestBody 这个注解, 将它加载参数声明前面即可。
那这样,后端就可以直接在方法中使用变量了。我们看一下前端发送参数时的代码:
const path = '${pageContext.request.contextPath}';
// 将Json数据转换成List
function testJsonToList(){
//定义一个json数组 new Array()
let hobby = ['唱歌','跳舞','喝酒','烫头']
$.ajax({
url: path + '/param/jsonToList',
type: 'post',
data: JSON.stringify(hobby), //数组也是js对象
dataType: 'json', //简写 application/json
contentType: 'application/json',
success: res=>{
alert('code:' + res.code + ', msg:' + res.msg)
}
})
}
以上代码片段是写在前端< script>标签中的,即脚本代码。这里进行一下剖析,以便大家理解。
定义了一个名为testJsonToList的方法,其中首先定义了一个名为hobby的数组,这个数组有4个值,分别是唱歌、跳舞、喝酒、烫头。数组名为hobby,与后端controller中我们定义的List名称完全一致(只有这样才能完成自动映射);同时,需要注意的是,这里我们将参数是封装在一个数组对象中,格式是:用 [ ] 将内容包裹在内。思考一下:为什么要用数组进行存储呢? 下面会给出解释。
接下来发送ajax请求,指定 url 到实现约定的地址;
方法中的几个属性解释:
- type: 指定为POST类型;
- data: 为我们要传递的参数,这里我们使用了 JSON.stringify() 方法,这个发送是JSON所持有的,作用是将一个js对象转换成JSON格式,为什么要转换成JSON格式?这是本例在开头就说好的,后端想要直接接收多个同名参数并存放到List集合中,前端发送参数时就必须按规定发送,规定就是JSON格式,这里的JSON.stringify() 其作用就是帮助我们完成格式的转换,换句话说,就是我们可以直接在前端定义一个js支持的数组对象或者是Js对象都可以,然后通过这个方法让它自动为我们完成转换;当然,我们也可以自己直接将内容写成JSON格式,例如:{“hobby:”:“唱歌”,“hobby”:“跳舞”,“hobby”:“喝酒”,“hobby”:“烫头”},但是这样书写如果字段比较多的时候,会比较的繁琐,所以不如直接写一个js中的数组对象,然后使用 JSON.stringify() 来帮助我们完成格式的转换。
- dataType: 为我们传递数据的格式,毋庸置疑是JSON格式,所以写成’application/json’,当然简写就是’json‘.
- contentType: 为指定编码格式。服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。使用 POST 提交数据方案,包含了 Content-Type 和消息主体编码方式两部分。application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数。
- sucess: 为回调函数,此处代码与本例无关,不作过多解释。
3、总结
发送JSON格式直接到后端Controller,无非需要注意两点:
① 后端Controller的执行方法中,需要给参数列表加上@RequestBody注解,并确保存储变量与前端发送参数的name保持一致。
② 前端发送的内容,必须是JSON格式,可以自己手写JSON格式也可以使用JSON.stringify来转换成JSON格式。ajax请求中,需要修改dataType和contentType,值都是’application/json’,可以不使用简写。
4.4.5.2 Map集合
接受Map集合的参数,同样也是有两种方法:
- 将Map集合定义在Java Bean中
- 接收JSON格式对象
1、将集合定义在Java Bean中
有了List集合的介绍,这里直接上代码,先看一下Bean对象:
public class User {
private Map<String, String> userMap;
public Map<String, String> getUserMap() {
return userMap;
}
public void setUserMap(Map<String, String> userMap) {
this.userMap = userMap;
}
}
定义一个Map类型的结合,用于存储前端传过来的名为userMap的多个参数。
接下来,看一下Controller中的处理方法:
@PostMapping("/testMap")
public String testMap(User user){
Map<String, String> userMap = user.getUserMap();
System.out.println(userMap);
...
}
这里剖析一下:@PostMapping注解为指定只拦截POST请求,同时拦截的Url为 /testMap,执行方法名成testMap,参数列表中直接定义User user对象,与List集合完全相同。于是,在方法中,就可以直接使用user对象,使用getUserMap()方法就可以拿取到userMap这个对象中的内容,我们使用System.out.println()将其遍历打印出来。
接下来,看一下前端页面的传递参数代码:
const path = '${pageContext.request.contextPath}';
function testMap(){
$.ajax({
url: path + '/param/testMap',
type: 'POST',
data: "userMap['id']=100&userMap['name']=zs", // Map<String, String> {id=1,name=zs}
dataType: 'text',
success: function(res){
alert(res)
},
error: function (err) {
alert(err)
}
})
}
这里对上述代码片段进行一下剖析,在前端< script >代码中,定义名为testMap的方法;
- url: 发送ajax请求到指定的Url与后台的controller中的testMap方法对应。
- type: 指定请求类型为POST
- data: 指定参数传递的内容,这里的格式需要大家记住,Map集合由于是键值对类型的格式,所以在发送中,需要采用 name[‘key’]=value的格式进行发送,name为当前属性的name值,在这里也就是userMap,对应了后台controller方法中的Map集合类型的userMap变量,[‘id’]为第一个Key,对应的value就是100,而第二个key是’name’ 对应的值为 zs,这里需要注意的是,如果参数大于1个,那么参数直接需要用&符号进行连接,最终将所有的内容,写在双引号中。
- dataType: 此时的数据类型仅仅为text文本格式
2、接收JSON格式对象
除了上述通过规范传递内容的格式,来发送传递内容外,还可以将发送内容的格式定义为JSON格式。JSON格式就不需要我们手动的规范传递的参数格式了,也就不再需要自己去写 这种:data: "userMap['id']=100&userMap['name']=zs",代码,因为一旦字段多的时候,写起来非常的麻烦。
接下来,看一下后台Controller的代码:
@PostMapping("/jsonToMap")
public User jsonToMap(@RequestBody Map<String, Object> map){
// {id: 1, hobby: ['踢球','跳舞'], user: {id:1, name:'zs'}} -- json格式
System.out.println(map);
...
}
此时,我们在参数列表中定义了 一个Map集合名为map,key的泛型为String,value的泛型为Object,也就是说,value的类型可以是任意类型。(其实这里的变量名称map,可以是任意的名称,不需要再与前端保持一致了,因为这里的映射规则是key和value的键值对了)。Spring MVC就会将从前端传过来的JSON字符串解析成Map类型的对象赋值给方法参数。
接下来,看一下前端页面的代码:
const path = '${pageContext.request.contextPath}';
//将json数据转换成Map
function testJsonToMap(){
let obj = {id: 1, hobby: ['唱歌', '跳舞', '打游戏'], user: {id: 1, name:'四哥'}}
$.ajax({
url: path + '/param/jsonToMap',
type: 'POST',
data: JSON.stringify(obj),
dataType: 'json',
contentType: 'application/json',
success: res=>{
alert(res.id + res.name + res.age)
}
})
}
可以看出,由于此时后端接收参数的类型变成了Map集合类型,也就是键值对类型,需要有一个key和一个value来组合对应。那么前端拥有这种格式的类型,数组已经无法满足了,因为数组的值是一个整体,没有办法体现出一个个键值对(Key=value),此时!前端唯一能有这种格式的,就剩下Js对象了!使用js对象,就可以模拟出key=value的格式,如上代码中所写:let obj = {id: 1, hobby: ['唱歌', '跳舞', '打游戏'], user: {id: 1, name:'四哥'}} ,定义了一个js对象名为obj,这个js对象的键值对之间都用逗号(,)隔开,第一对为id:1,key为id,value为1;第二对,key为hobby,value为数组对象 [‘唱歌’, ‘跳舞’, ‘打游戏’];第三对,key为user,value为js对象{id: 1, name:‘四哥’}。
最终整个对象被{ }包裹,由此可以看出,此时参数传递对应的关键所在已经变成了key和value的对应,此时的key在后端会被自动映射成String类型,而value则被映射成Object类型,如果我们需要将Value的Object类型进行进一步的转换,可以在后端使用强制类型转换,非常的方便。
同时,此时我们也不需要自己手写key=value的格式数据了,直接使用js对象即可。
接下来就是发送ajax请求,
- url: 指定到规定的请求地址
- type: 必须修改成post
- data: 传递的参数,这里使用JSON.stringify()来帮助我们将js对象转换成json格式的数据
- dataType: 数据的类型,当然是’json’,这是简写。
- contentType: 请求头中规定的编译格式,为’application/json’
- success: 回调函数
3、总结
发送JSON格式直接到后端Controller,无非需要注意两点:
① 后端Controller的执行方法中,需要给参数列表加上@RequestBody注解,并确保存储变量与前端发送参数的name保持一致。
② 前端发送的内容,必须是JSON格式,可以自己手写JSON格式也可以使用JSON.stringify来转换成JSON格式。ajax请求中,需要修改dataType和contentType,值都是’application/json’,可以不使用简写。
4.4.6 JSON 格式
这里所谓的JSON格式传递参数,其实已经在上一节介绍过了,这里做一下总结。
- JSON格式传递参数,我们可以手写JSON格式,或者是将js中的数据、对象进行转换,采用的是JSON.stringify()方法,可以节省我们手写的繁琐。
- http协议的请求方法使用post,http协议的请求头的contentType是application/json
- Spring MVC底层在帮我们进行JSON格式的解析和与Java属性之间的映射,Spring MVC使用jackson作为默认的json转换器,所以,需要我们引入maven依赖。总共有3个jar包:jackson-core.jar ,jackson-databind.jar,jackson-annotation.jar。而jackson-databind.jar依赖于其他两个jar,于是我们在引入的时候,只需要引入jackson-databind.jar这一个jar包即可,另外两个自动引入。
- @RequestBody注解可以将json格式数据转换成JavaBean、list、map。@ResponseBody注解可以将Object类型返回值转成json格式的数据。
4.4.7 获取URL中的参数
URL参数,或者叫请求路径参数是基于URL模板获取到的参数,例如/user/{userId}是一个URL模板(URL模板中的参数占位符是{}),实际请求的URL为/user/1,那么通过匹配实际请求的URL和URL模板就能提取到userId为1。
在Spring MVC中,URL模板中的路径参数叫做PathVariable,对应注解@PathVariable,对应的参数处理器为PathVariableMethodArgumentResolver。
注意一点是,@PathVariable的解析是按照value(name)属性进行匹配,和URL参数的顺序是无关的。举个简单的例子:
后台的控制器如下:
@GetMapping(value = "/user/{name}/{age}")
// @PathVariable注解中的value需要和@GetMapping中URL的占位名一致
public String findUser1(@PathVariable(value = "age") Integer age,
@PathVariable(value = "name") String name) {
String content = String.format("name = %s,age = %d", name, age);
log.info(content);
return content;
}
这种用法被广泛使用于Representational State Transfer(REST)的软件架构风格,个人觉得这种风格是比较灵活和清晰的(从URL和请求方法就能完全理解接口的意义和功能)。下面再介绍两种相对特殊的使用方式。
4.4.7.1 带条件的URL参数
其实路径参数支持正则表达式,例如我们在使用/sex/{sex}接口的时候,要求sex必须是F(Female)或者M(Male),那么我们的URL模板可以定义为/sex/{sex:M|F},代码如下:
@GetMapping(value = "/sex/{sex:M|F}")
public String findUser2(@PathVariable(value = "sex") String sex) {
log.info(sex);
return sex;
}
只有/sex/F或者/sex/M的请求才会进入findUser2控制器方法,其他该路径前缀的请求都是非法的,会返回404状态码。
再来一个例子:
/**
* @PathVariable注解的作用就是从URL里面读取参数值(GET请求方式)
*
* @PathVariable注解一般用于只传递一个参数的场景,当然也可以传递多个参数。
*
* @param param1 占位符{}添加了正则表达式,限定5位数值,如果传递过来的参数不合要求则不会执行方法的代码。
* @param param2
* @return
*/
@GetMapping("/testGet2_1/{param1:[0-9]{5}}/{param2}")
public String testGet2_1(@PathVariable String param1,@PathVariable String param2){
System.out.println("param1:"+param1);
System.out.println("param2:"+param2);
return param1+","+param2;
}
这里仅仅是介绍了一个最简单的URL参数正则表达式的使用方式,更强大的用法可以自行摸索。
4.4.7.2 @MatrixVariable的使用
MatrixVariable也是URL参数的一种,对应注解@MatrixVariable,不过它并不是URL中的一个值(这里的值指定是两个"/"之间的部分),而是值的一部分,它通过";"进行分隔,通过"="进行K-V设置。说起来有点抽象,举个例子:假如我们需要打电话给一个名字为doge,性别是男,分组是码畜的程序员,GET请求的URL可以表示为:/call/doge;gender=male;group=programmer,我们设计的控制器方法如下:
@GetMapping(value = "/call/{name}")
public String find(@PathVariable(value = "name") String name,
@MatrixVariable(value = "gender") String gender,
@MatrixVariable(value = "group") String group) {
String content = String.format("name = %s,gender = %s,group = %s", name, gender, group);
log.info(content);
return content;
}
当然,如果你按照上面的例子写好代码,尝试请求一下该接口发现是报错的:400 Bad Request - Missing matrix variable 'gender' for method parameter of type String。这是因为@MatrixVariable注解的使用是不安全的,在Spring MVC中默认是关闭对其支持。要开启对@MatrixVariable的支持,需要设置RequestMappingHandlerMapping#setRemoveSemicolonContent方法为false:
@Configuration
public class CustomMvcConfiguration implements InitializingBean {
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Override
public void afterPropertiesSet() throws Exception {
requestMappingHandlerMapping.setRemoveSemicolonContent(false);
}
}
除非有很特殊的需要,否则不建议使用@MatrixVariable。
4.5 Spring MVC向前端页面传递数据
Spring MVC可以通过request域,从后台向前端传递值
后台向前端通过request域传递值的方式:
- Servlet api 中的 HttpServletRequest对象
- ModelAndView 对象
- ModelMap 对象
- Model 对象
- Map 集合
4.5.1 Servlet api 中的 HttpServletRequest对象
代码:
//servlet请求
@RequestMapping("/servlet")
// 返回值是String,返回的是视图名称
public String servlet(HttpServletRequest request){
String name = request.getParameter("name");
System.out.println("name:" + name);
// 以下为本例代码
request.setAttribute("result", "hello " + name);
// 要跳转到的视图页面
return "hello";
}
这种方式是最传统的方式,直接向request域中进行参数的传递,通过setAttribute()方法,来指定key和value,即可在前端页面hello.jsp中直接获取到request域中的内容。如何获取?代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Hello</title>
</head>
<body>
<h2>${result}</h2>
</body>
</html>
4.5.2 ModelAndView 对象
方法中返回 ModelAndView 对象,此对象中设置模型数据并指定视图。它有两个常用方法:
- addObject(String key, Object value):设置共享数据的 key 和 value。
- addObject(Object value):设置共享数据的 value,key 为该 value 类型首字母小写。
代码片段:
// Controller中的方法返回值设置为ModelAndView
public ModelAndView modelAndView(){
ModelAndView mv = new ModelAndView();
// 向request域传值
mv.addObject("result","Hello spring mvc");
// 设置视图名称 prefix + viewName + suffix (/jsps/hello.jsp)
mv.setViewName("hello");
// 返回ModelAndView,会将要传递给的数据和视图页面一并返回,用户就会跳转到相应的页面看到返回的数据
return mv;
}
这是Spring MVC独有的ModelAndView对象,通过声明一个ModelAndView类型的对象,就可以使用这个对象来传值或者是指定跳转的页面路径。采用addObject()方法,来规定key和value,即可在前端页面中根据key拿取到对应value的值。通过采用setViewName()方法,来传入一个页面的名称,Spring MVC底层的视图解析器就会将ModelAndView中的view视图进行前缀和后缀的拼接加工,并给最终的页面跳转路径。当然,这里执行方法的返回类型需要是ModelAndView类型,方法最终需要将mv对象return 。
参数列表中也可以提前声明ModelAndView对象,可直接在方法中使用。如:
public ModelAndView modelAndView(ModelAndView mv){
// 向request域传值
mv.addObject("result","Hello spring mvc");
// 设置视图名称 prefix + viewName + suffix (/jsps/hello.jsp)
mv.setViewName("hello");
return mv;
}
4.5.3 ModelMap 对象
@RequestMapping("/simple")
public String simple(int id, String name, ModelMap modelMap){
// modelMap 可以向request域传值【ModelAndView,Request,ModelMap】
System.out.println("id:" + id);
System.out.println("name:" + name);
// 以下为本例的代码
modelMap.addAttribute("result", "Hello " + name + "," + id);
return "hello";
}
这里的ModelMap对象与request对象相似,可以采用addAttribute()方法来传递参数,向ModelMap对象中添加key和value,其实就是在向request域中添加key和value,只不过ModelMap是又将request进行了一层封装,原因是彻底与Servlet的内容分离,仅用Spring MVC的对象,就可以完成对request域的赋值。
视图解析器会将modelMap中的数据解析到视图页面上。
4.5.4 Model 对象
返回 String 类型(使用广泛),此时如果我们需要共享数据,那么就需要用到HttpServlet对象,Spring帮我们封装好了一个对象:Model 。组合使用,用其往作用域或模型中存入数据。
Model类型参数的处理器是ModelMethodProcessor,实际上处理此参数是直接返回ModelAndViewContainer实例中的Model(ModelMap类型),因为要桥接不同的接口和类的功能,因此回调的实例是BindingAwareModelMap类型,此类型继承自ModelMap同时实现了Model接口。举个例子:
@RequestMapping("/bean")
public String testBean(User user, Model model){
System.out.println(user);
// 使用Model对象向request域传值
model.addAttribute("result", "hello " + user.getName());
return "hello";
}
Model对象与ModelMap对象基本上是一样的。ModelMap或者Model中添加的属性项会附加到HttpRequestServlet中带到页面中进行渲染。
4.5.5 Map 集合
我们先来简单介绍一下它的常见用法。
@PostMapping("/array")
public String array(Map<String, String> map){
//使用 java.util.Map 向request域传值
map.put("result","测试数组传参");
return "hello";
}
在执行方法的参数列表中,声明一个Map集合,并向其中put()进key和value,就可以被Spring MVC解析成向request域中传递值。底层就是使用request.setAttribute()在赋值。而我们无需引入request对象,仅仅使用一个简单的map集合,就可以完成对request对象的操作。彻底与Servlet解耦。
这个Map的数据就会被传递给视图,在页面上显示出来。
下面我们再详细讲一下Map。Map类型参数的范围相对比较广,对应一系列的参数处理器,注意区别使用注解的Map类型和完全不使用注解的Map类型参数,两者的处理方式不相同。下面列举几个相对典型的Map类型参数处理例子。
1、不使用任何注解的Map<String,Object>参数
这种情况下参数实际上直接回调ModelAndViewContainer中的ModelMap实例,参数处理器为MapMethodProcessor,往Map参数中添加的属性将会带到页面中。
2、使用@RequestParam注解的Map<String,Object>参数
这种情况下的参数处理器为RequestParamMapMethodArgumentResolver,使用的请求方式需要指定ContentType为x-www-form-urlencoded,不能使用application/json的方式:
控制器代码为:
@PostMapping(value = "/map")
public String mapArgs(@RequestParam Map<String, Object> map) {
log.info("{}", map);
return map.toString();
}
3、使用@RequestHeader注解的Map<String,Object>参数
这种情况下的参数处理器为RequestHeaderMapMethodArgumentResolver,作用是获取请求的所有请求头的Key-Value。
4、使用@PathVariable注解的Map<String,Object>参数
这种情况下的参数处理器为PathVariableMapMethodArgumentResolver,作用是获取所有路径参数封装为Key-Value结构。
五、文件上传与下载
5.1 文件上传
回顾之前使用 Servlet3.0 来解决文件上传的问题,编写上传表单(POST、multipart/form-data),还在处理方法 doPost 中编写解析上传文件的代码。但是在Spring MVC是可以帮我们简化文件上传的步骤和代码。
5.1.1 编写表单
注意请求数据类型必须是:multipart/form-data,且请求方式是POST。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
文件:<input type="file" name="pic"><br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
5.1.2 修改web.xml
我们可以在web.xml中指定上传文件的大小。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<!-- 文件上传配置 -->
<multipart-config>
<!--<max-file-size>标签用于设置单个文件的最大值,单位为字节,这里设置为50M-->
<max-file-size>52428800</max-file-size>
<!--<max-request-size>标签用于设置上传文件的总大小,单位为字节,这里设置为50M-->
<max-request-size>52428800</max-request-size>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
5.1.3 配置上传解析器
在springmvc.xml中配置上传解析器。要想使用Spring MVC中multipartfile接收客户端上传的文件,就必须配置文件上传解析器且解析的id必须为multipartResolver。
<!--将文件上传解析器装配到Spring中-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--控制文件上传大小单位字节 默认没有大小限制 这里是2-->
<property name="maxUploadSize" value="2097152"/>
</bean>
5.1.4 编写上传控制器
@Controller
public class UploadController {
// Spring 容器存在 ServletContext 类型的对象,所以定义好 ServletContext 类型字段贴@Autowired 注解即可获取到
@Autowired
private ServletContext servletContext;
@RequestMapping("/upload")
public ModelAndView upload(Part pic) throws Exception {
System.out.println(pic.getContentType()); // 文件类型
System.out.println(pic.getName()); // 文件参数名
System.out.println(pic.getSize()); // 文件大小
System.out.println(pic.getInputStream()); // 文件输入流
// FileCopyUtils.copy(in, out),一个 Spring 提供的拷贝方法
// 获取项目 webapp 目录下 uploadDir 目录的绝对路径
System.out.println(servletContext.getRealPath("/uploadDir"));
return null;
}
}
5.1.5 批量文件上传:MultipartFile集合
批量文件上传的时候,我们一般需要接收一个MultipartFile集合,可以有两种选择:
- 1、使用MultipartHttpServletRequest参数,直接调用getFiles方法获取MultipartFile列表。
- 2、使用@RequestParam注解修饰方法参数中的MultipartFile列表,参数处理器是RequestParamMethodArgumentResolver,其实就是第一种的封装而已。
控制器方法代码如下:
@PostMapping(value = "/parts")
public String partArgs(@RequestParam(name = "file") List<MultipartFile> parts) {
log.info("{}", parts);
return parts.toString();
}
5.2 文件下载
文件下载:将服务器上的文件下载到当前用户访问的计算机的过程称之为文件下载。
5.2.1 编写下载控制器
下载时必须设置响应的头信息,指定文件以何种方式保存,另外下载文件的控制器不能存在返回值,代表响应只用来下载文件信息。
/**
* 测试文件下载
* @param fileName 要下载文件名
* @return
*/
@RequestMapping("download")
public String download(String fileName, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取下载服务器上文件的绝对路径
String realPath = request.getSession().getServletContext().getRealPath("/down");
// 根据文件名获取服务上指定文件
FileInputStream is = new FileInputStream(new File(realPath, fileName));
// 获取响应对象设置响应头信息
response.setHeader("content-disposition","attachment;fileName="+ URLEncoder.encode(fileName,"UTF-8"));
// 获取响应输出流
ServletOutputStream os = response.getOutputStream();
IOUtils.copy(is,os);
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
return null;
}
六、Spring MVC常用注解及其作用
下面来总结一下Spring MVC的常用注解。
@Controller:标识这个类是一个控制器
@RequestMapping:给控制器方法绑定一个uri
@ResponseBody:将java对象转成json,并且发送给客户端
@RequestBody:将客户端请求过来的json转成Java对象
@RequestParam:当表单参数和方法形参名字不一致时,做一个名字映射
@PathVarible:用于获取uri中的参数,比如user/1中1的值
Rest风格的新api:
@RestController相当于@Controller+ @ResponseBody
@GetMapping @DeleteMapping @PostMapping @PutMapping:接收四种请求类型的注解
其他注解:
@SessionAttribute:声明将什么模型数据存入session
@CookieValue:获取cookie值
@ModelAttribute:将方法返回值存入model中
@HeaderValue:获取请求头中的值
七、总结
7.1 为什么要使用Spring MVC?
Spring MVC是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring MVC也是要简化日常Web开发。(处理业务数据的对象和显示业务数据的视图之间存在紧密耦合,用MVC也是为了解耦)。
7.2 Spring MVC的特点
- 清晰的角色划分:控制器(controller)、验证器(validator)、 命令对象(command object)、表单对象(formobject)、模型对象(model object)、 Servlet分发器(DispatcherServlet)、处理器映射(handler mapping)、视图解析器(view resolver)等。每一个角色都可以由一个专门的对象来实现。
- 强大而直接的配置方式:将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器(validator)的引用。
- 可适配、非侵入:可以根据不同的应用场景,选择合适的控制器子类 (simple型、command型、form型、wizard型、multi-action型或者自定义),而不是从单一控制器 (比如Action/ActionForm)继承。
- 可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。
- 可定制的绑定(binding) 和验证(validation):比如将类型不匹配作为应用级的验证错误, 这可以保存错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象,需要手动解析它并转换到业务对象。
- 可定制的handlermapping和view resolution:Spring提供从最简单的URL映射, 到复杂的、专用的定制策略。与某些webMVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。
- 灵活的model转换:在Springweb框架中,使用基于Map的 键/值对来达到轻易地与各种视图技术的集成。
- 可定制的本地化和主题(theme)解析:支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。
- 简单而强大的JSP标签库(SpringTag Library):支持包括诸如数据绑定和主题(theme) 之类的许多功能。
- JSP表单标签库:在Spring2.0中引入的表单标签库,使得在JSP中编写 表单更加容易。
- Spring Bean的生命周期可以被限制在当前的HTTP Request或者HTTP Session。
7.3 Spring MVC的优点
- 让我们能非常简单的设计出干净的Web层和薄薄的Web层
- 进行更简洁的Web层的开发
- 天生与Spring框架集成(如IoC容器、AOP等),可以和Spring框架无缝整合
- 提供强大的约定大于配置的契约式编程支持
- 非常灵活的数据验证、格式化和数据绑定机制
- 注解式开发更高效。
- 支持Restful风格
- Spring MVC是一个典型的轻量级MVC框架,在整个MVC架构中充当控制器框架,相对于之前的struts2框架,SpringMVC运行更快,其注解式开发更高效灵活。
7.4 Spring MVC和Struts2的对比
框架机制:Spring MVC的入口是servlet,而Struts2是filter。
拦截机制:
Struts2:
- Struts2框架是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype(否则会出现线程并发问题),然后通过setter,getter吧request数据注入到属性;
- 一个Action对应一个request,response上下文,在接收参数时,可以通过属性接收,说明属性参数是让多个方法共享的;
- Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了。
SpringMVC:
- SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独立的,独享request,response数据。而每个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过ModeMap返回给框架;
- 在Spring整合时,Spring MVC的Controller Bean默认单例模式Singleton,所以默认对所有的请求,只会创建一个Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope注解修改;
Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样导致Struts2的配置文件量还是比SpringMVC大。
性能方面:
SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,有加载一次单例模式bean注入。而Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,所以,Spring MVC开发效率和性能高于Struts2。
配置方面:
Spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高(当然Struts2也可以通过不同的目录结构和相关配置做到Spring MVC一样的效果,但是需要xml配置的地方不少);
Spring MVC可以认为已经100%零配置。
设计思想:
Struts2更加符合OOP的编程思想, Spring MVC就比较谨慎,在servlet上扩展。
集成方面:
Spring MVC集成了Ajax。
注意:Spring MVC是单例模式的框架,但它是线程安全的,因为Spring MVC没有成员变量,所有参数的封装都是基于方法的,属于当前线程的私有变量,因此是线程安全的框架,所以效率高。
struts action是多例的。所以可以使用成员变量获取参数。所以效率低。