目录
一、MVC模式
1.1 MVC模式的发展
1.1.1 Model1 模型
1.1.2 Model2 模型
1.2 MVC模式简介
1.模型(Model)
2.视图(View)
3.控制器(Controller)
二、Spring MVC模型简介
三、Spring MVC 六大核心组件
3.1 六大组件简介
1.前端控制器 DispatcherServlet(不需要工程师开发,由框架提供)
2.处理器映射器 HandlerMapping(不需要工程师开发,由框架提供)
3.处理器适配器 HandlerAdapter(不需要工程师开发,由框架提供)
4.处理器 Handler(需要工程师开发)
5.视图解析器 ViewResolver (不需要工程师开发,由框架提供)
6.视图渲染 View (需要工程师开发jsp...)
3.2 Spring MVC中的handler究竟是什么?
3.3 Spring MVC拦截器
拦截器(Interceptor)和过滤器(Filter)的区别
四、Spring MVC的使用
4.1 环境搭建
4.1.1 引入依赖
4.1.2 配置web.xml
4.1.2.1 配置映射路径的注意点
4.1.2.1.1 访问静态资源和 JSP 被拦截的原因
4.1.2.1.2 如何解决
1、方式一
2、方式二
4.1.3 编写Spring配置文件
4.1.4 编写控制器Controller
4.2 Spring MVC相关注解简介
4.2.1 @Controller
4.2.2 @RequestMapping
4.3 Spring MVC的跳转方式
4.3.1 Controller ----> 前台页面
4.3.1.1 forward
4.3.1.2 redirect
4.3.2 Controller ----> Controller
4.3.2.1 forward
4.3.2.2 redirect
4.4 Spring MVC的参数接收
4.4.1 Servlet接收参数的方式
4.4.2 基本数据类型 + String类型
4.4.2.1 @RequestParam 注解
1、作用
2、语法
4.4.3 数组类型
4.4.4 对象类型(Java Bean实体类)
4.4.5 集合类型
4.4.5.1 List集合
1、将集合定义在Java Bean中
2、接收JSON格式数组
3、总结
4.4.5.2 Map集合
1、将集合定义在Java Bean中
2、接收JSON格式对象
3、总结
4.4.6 JSON 格式
4.4.7 获取URL中的参数
4.4.7.1 带条件的URL参数
4.4.7.2 @MatrixVariable的使用
4.5 Spring MVC向前端页面传递数据
4.5.1 Servlet api 中的 HttpServletRequest对象
4.5.2 ModelAndView 对象
4.5.3 ModelMap 对象
4.5.4 Model 对象
4.5.5 Map 集合
1、不使用任何注解的Map参数,object>
2、使用@RequestParam注解的Map参数,object>
3、使用@RequestHeader注解的Map参数,object>
4、使用@PathVariable注解的Map参数,object>
五、文件上传与下载
5.1 文件上传
5.1.1 编写表单
5.1.2 修改web.xml
5.1.3 配置上传解析器
5.1.4 编写上传控制器
5.1.5 批量文件上传:MultipartFile集合
5.2 文件下载
5.2.1 编写下载控制器
六、Spring MVC常用注解及其作用
七、总结
7.1 为什么要使用Spring MVC?
7.2 Spring MVC的特点
7.3 Spring MVC的优点
7.4 Spring MVC和Struts2的对比
之前详解了Spring体系结构的两大核心:Spring IOC和Spring AOP,今天主要谈Spring MVC。
Spring MVC 基于 MVC 模式,因此理解 Spring MVC 需要先对 MVC 模式有所了解。
Model1 模型是很早以前项目开发的一种常见模型,项目主要由 jsp 和 JavaBean 两部分组成。
它的优点是:
结构简单。开发小型项目时效率高。
它的缺点也同样明显:
在Model 1模式下,整个Web应用几乎全部由JSP页面组成,JSP页面接收处理客户端请求,对请求处理后直接做出响应。用少量的JavaBean来处理数据库连接、数据库访问等操作。
Model2 模型是在 Model1 的基础上进行改良,它是 MVC 模型的一个经典应用。它把处理请求和展示数据进行分离,让每个部分各司其职。 此时的 JSP 已经就是纯粹的展示数据了,而处理请求的事情交由控制器来完成,使每个组件充分独立,提高了代码可重用性和易维护性。所以这个就是最终形态的MVC模型了。下图展示的就是 Model2 模型:
Model 2是基于MVC架构的设计模式。 在Model 2架构中,Servlet作为前端控制器,负责接收客户端发送的请求在Servlet中只包含控制逻辑和简单的前端处理; 后端JavaBean来完成实际的逻辑处理; 最后,转发到相应的JSP页面处理显示逻辑。 Model 2具有组件化的特点,更适用于大规模应用的开发。
MVC模式是软件工程中常见的一种软件架构模式,该模式把软件系统(项目)分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。MVC就是 Model、View、和Controller的缩写。
MVC各部分根据职责进行分离,使程序的结构更为直观,增加了程序的可扩展性、可维护性、可复用性。
可以用如下的图形来表示MVC三者之间的关系:
模型封装了数据及对数据的操作,可以直接对数据库进行访问,不依赖视图和控制器,也就是说模型并不关注数据如何展示,只负责提供数据。GUI 程序模型中数据的变化一般会通过观察者模式通知视图,而在 web 中则不会这样。
视图从模型中拉取数据,只负责展示,没有具体的程序逻辑。
控制器用于控制程序的流程,将模型中的数据展示到视图中。
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模型:
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。
DispatcherServlet本质上是一个Servlet,相当于一个中转站,所有的访问都会走到这个Servlet中,再根据配置进行中转到相应的Handler中进行处理,获取到数据和视图后,在使用相应视图做出响应。
HandlerMapping本质上就是一段映射关系,将访问路径和对应的Handler存储为映射关系,在需要时供前端控制器查阅(即根据请求的url查找Handler)。
Spring MVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
本质上是一个适配器,可以根据要求(HandlerAdapter要求的规则)找到对应的Handler来运行。
由于 Handler 涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发 Handler。
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler。
Handler叫做处理器,也叫控制器,是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
本质上也是一种映射关系,可以将视图名称映射到真正的视图地址。前端控制器调用处理器适配完成后得到model和view,将view信息传给视图解析器进行视图解析,来得到真正的view。
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)。 View对象是通过视图解析器生成的,它的作用就是将handler处理器中返回的model数据嵌入到视图解析器解析后得到的页面中,向客户端做出响应。
了解过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是什么?
Spring MVC提供了拦截器支持,这也算是Spring MVC的一个组件,是利用AOP实现的。
Servlet提供了过滤器(Filter)和监听器(Listener),Spring MVC提供了拦截器(Interceptor)。
下面简单介绍一下它的使用方法,我们以后会单独对拦截器进行详细讲解。
1、创建自定义的拦截器类,实现HandlerInterceptor接口
true表示放行,false表示拦截。
三种拦截器:
2、注册拦截器
XML注册:
在springmvc.xml中进行注册
注解注册:
对于注解配置来说,需要将 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 注解。
两者的执行流程图:
层次关系图:
这里我们以xml配置为例来讲解。其实也可以用注解来进行配置,但是用xml配置更能帮助我们理解底层的原作原理。
org.springframework
spring-webmvc
5.1.6.RELEASE
javax.servlet
servlet-api
3.0-alpha-1
provided
com.fasterxml.jackson.core
jackson-databind
2.12.4
Spring MVC 已经提供了一个 DispatcherServlet 类作为前端控制器,Java Web项目只要是使用servlet,就需要配置web.xml,所以要使用 Spring MVC 必须在web.xml 中配置前端控制器。
Archetype Created Web Application
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
springmvc
/
register.jsp
其中
注意:
load-on-startup 元素是可选的:若值为 0 或者大于 0 时,表示容器在应用启动时就构建 Servlet 并调用其 init 方法做初始化操作(非负数的值越小,启动该 Servlet 的优先级越高);若值为一个负数时或者没有指定时,则在第一次请求该 Servlet 才加载。配置的话,就可以让 Spring MVC 初始化的工作在容器启动的时候完成,而不是丢给用户请求去完成,提高用户访问的体验性。
配置前端控制器的映射路径一般有以下的三种形式:
Tomcat 容器处理静态资源是交由内置 DefaultServlet 来处理的(拦截路径是 /),处理 JSP 资源是交由内置的 JspServlet 处理的(拦截路径是*.jsp | *.jspx)。
启动项目时,先加载容器的 web.xml,而后加载项目中的 web.xml。当拦截路径在两者文件中配置的一样,后面会覆盖掉前者。
所以前端控制器配置拦截路径是 / 的所有静态资源都会交由前端控制器处理,而拦截路径配置 /*,所有静态资源和 JSP 都会交由前端控制器处理。
在 web.xml 中修改,修改前端控制器的映射路径修改为*.do,但注意,访问控制器(Controller)里的处理方法时,请求路径须携带 .do。
dispatcherServlet
*.do
在 springmvc.xml中加入一段配置,这个配置会在 Spring MVC 上下文中创建存入一个
DefaultServletHttpRequestHandler 的 bean,它会对进入DispatcherServlet的请求进行筛查,若不是映射的请求,就将该请求交由容器默认的 Servlet处理。
在 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 标签描述即可。
>
注:在 Spring4.0 之后,如果不配置处理映射器、处理器适配器和视图解析器,会使用默认的。
在 spring-mvc.xml 的配置文件里加上
这里讲一下视图解析器的配置,一般如果在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";
}
}
@Controller
public class HellowController {
/*
@RequestMapping的修饰范围:可以用在类上和方法上,他的作用如下:
1. 用在方法上可以给当前方法加入指定的请求路径
2. 用在类上可以给类中的所有方法都加入一个统一的请求路径,在这个方法访问之前都必须加上
*/
@RequestMapping("/hello")
public String hello(String username,String password){
System.out.println("hello");
// 返回视图名称
return "index";
}
}
Spring MVC有三种不同的实现方式
上面三种方法的控制器应该都是一样的,都是DispatcherServlet,都调用了doService方法。
该注解作用于类上,用来标识这是一个控制器组件类并创建这个Bean,告诉spring我是一个控制器。
这个注解可以作用在方法上或者是类上,用来指定请求路径。
传统的Servlet开发跳转方式有两种:
在请求转发和重定向的时候,我们一般有两种方式来写请求路径:
通过测试我们可以发现,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";
如果我们想使用重定向的方式来进行跳转的话,需要使用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跳转不会经过视图解析器。
如果我们想使用请求转发的方式跳转到相同(不同)Controller的不同方法的时候,我们也需要使用Spring MVC提供的关键字:forward:。
语法:
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
return:"forward: /需要跳转的类上的@RequestMapping的值/需要跳转的方法上的@RequestMapping的值;"
}
}
如果我们想使用重定向的方式跳转到相同(不同)Controller的不同方法的时候,我们也需要使用Spring MVC提供的关键字:redirect:。
语法:
@Controller
@RequestMapping("forwoartAndRedirect")
public class TestForwoartAndRedirect {
@RequestMapping("test")
public String test() {
System.out.println("test");
return:"redirect: /需要跳转的类上的@RequestMapping的值/需要跳转的方法上的@RequestMapping的值;"
}
}
前端向后台传递参数的方式:
这种方式想必是学习了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有几个需要注意的点:
Spring MVC使用的是控制器中方法形参列表来接收客户端的请求参数,它可以进行自动类型转换,要求传递参数的key要与对应方法的形参变量名一致才可以完成自动赋值。它的优势很明显:
@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;
}
}
我们可以在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。
其实通过Spring MVC接收参数,在Controller的方法参数上都可以使用@RequestParam注解。使用它和不适用它达到的效果是一样的,都可以接收参数,但是这个注解给我们提供了一些额外的拓展功能,可以帮助我们完成一些额外的事情,比如解决上面说的前端没有传递基本数据类型的参数,导致报错的情况。
下面我们简单讲解一下这个注解。
@RequestParam:将请求参数绑定到你控制器的方法参数上(是Spring MVC中接收普通参数的注解)
@RequestParam(value = "参数名", required = "true/false", defaultValue = "")
我们可以通过设置参数的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;
}
}
这种方式,简单来说,就是在前端页面通过发送数组格式的数据,后台Controller的处理方法中,在接收参数的时候,直接转换成数组格式。跟 基本数据类型和String类型的 思想是一样的,但是转换的类型是不同的。
那这种方式,我们在前端页面中,经常用到的地方,其实就是form表单中的多选框,因为多选框的name都是一样的,但是值有多个,传递到后端页面后,传统方式是进行截取,而现在Spring MVC可以帮助我们完成底层的工作,直接给我们一个array数组。我们需要接收数组类型的时候,只需将要接收的数组类型直接声明为方法的形式参数,数组名和要传入的数组名一致即可。
示例:
前端页面代码
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循环进行了遍历打印。
通过上面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对象的同名属性,而其他的属性由于我没有传值,所以都会保持初始默认值。
Spring MVC不能直接通过形式参数列表的方式接收集合类型的参数,想要Spring MVC自动将接收的参数转换成集合类型,有两种方法:
如果需要接收集合类型的参数,可以将集合放入一个对象中,并且提供get/set方法,才可以。推荐放入VO对象中进行封装,进而使用对象类型来进行接收。
这种方式比较简单直接,需要先创建一个Bean对象,然后将我们需要接收的参数定义成这个Bean对象的一个属性,这里要求参数的name需要与属性名完全一致,这样Spring MVC底层就可以自动的将名称相同的参数和bean中对象的属性进行映射赋值,我们只需要使用即可。
这里,我们看一下后端的代码实现:
创建一个User对象(Java Bean)
public class User {
private List idList;
// 必须有 getter 和 setter方法
public void setIdList(List idList){
this.idList = idList;
}
public List getIdList(){
return this.idList;
}
}
将想要接收的参数名称定义成这个类对象的一个属性,属性的类型需要定义成List集合形式,至于泛型也是可以自动进行转换的,可以根据自己的需求进行调整,不单单是String类型。例如这里,我们让它自动接收前端传来名为idList的多个参数,然后自动将其存储在List集合中,并且所有的idList参数都由字符串自动转成Integer类型。而我们只需要在Controller中,定义一下Bean对象参数即可,代码如下:
@RequestMapping("/testList")
// 参数的对象名不重要,只要是这个对象的参数名称和传递进来的参数key一样即可
public String testList(User user){
List idList = user.getIdList();
idList.forEach(id -> System.out.println(id));
return "hello";
}
在Controller的处理方法中,我们只需要在方法参数列表中,声明一个User user对象,将我们刚才写有idList属性的类对象引入即可,不需要直接引入idList属性,而是引用包含它的Bean 对象,这样,我们就可以直接在方法中,使用user这个对象了,通过getIdList()属性,拿到转换后的List类型的参数。
前端页面在传递List类型参数的时候,可以直接用form表单传递,代码:
测试List传参
这里的代码是典型的发送List集合类型参数的前端代码,使用的是form表单中的checkbox多选框,由于多选框的值有多个,并且name属性相同,那么后端在接收起来时,就不再是一对一的关系,而是一个属性(字段)有多个值,那么正好可以使用List集合来存放。
如果前端页面不想使用form表单提交,那么还有第二种方式,可以通过js代码来发送JSON格式的数组信息来进行数据的提交,更加的灵活。
后端接收前端多个参数时,不仅是通过Bean对象的属性来进行转换存储,还可以直接接收,无需借助任何的变量类型。但是,这里就对前端传递参数的格式有了要求,也就是说,如果想在后端Controller中接收到前端传递的多个参数,并转换成List集合形式,那么就需要前端按照规范来进行传递,那到底是什么规范呢?那就是JSON格式。
先来看一下后端对于请求的处理方法:
@RequestMapping(value = "/jsonToList", method = RequestMethod.POST)
@ResponseBody // 此注解与本例无关,这个注解只是为了返回json格式的字符串
public String jsonToList(@RequestBody List 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 到实现约定的地址;
方法中的几个属性解释:
发送JSON格式直接到后端Controller,无非需要注意两点:
① 后端Controller的执行方法中,需要给参数列表加上@RequestBody注解,并确保存储变量与前端发送参数的name保持一致。
② 前端发送的内容,必须是JSON格式,可以自己手写JSON格式也可以使用JSON.stringify来转换成JSON格式。ajax请求中,需要修改dataType和contentType,值都是’application/json’,可以不使用简写。
接受Map集合的参数,同样也是有两种方法:
有了List集合的介绍,这里直接上代码,先看一下Bean对象:
public class User {
private Map userMap;
public Map getUserMap() {
return userMap;
}
public void setUserMap(Map userMap) {
this.userMap = userMap;
}
}
定义一个Map类型的结合,用于存储前端传过来的名为userMap的多个参数。
接下来,看一下Controller中的处理方法:
@PostMapping("/testMap")
public String testMap(User user){
Map 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 {id=1,name=zs}
dataType: 'text',
success: function(res){
alert(res)
},
error: function (err) {
alert(err)
}
})
}
这里对上述代码片段进行一下剖析,在前端< script >代码中,定义名为testMap的方法;
除了上述通过规范传递内容的格式,来发送传递内容外,还可以将发送内容的格式定义为JSON格式。JSON格式就不需要我们手动的规范传递的参数格式了,也就不再需要自己去写 这种:data: "userMap['id']=100&userMap['name']=zs",代码,因为一旦字段多的时候,写起来非常的麻烦。
接下来,看一下后台Controller的代码:
@PostMapping("/jsonToMap")
public User jsonToMap(@RequestBody Map 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请求,
发送JSON格式直接到后端Controller,无非需要注意两点:
① 后端Controller的执行方法中,需要给参数列表加上@RequestBody注解,并确保存储变量与前端发送参数的name保持一致。
② 前端发送的内容,必须是JSON格式,可以自己手写JSON格式也可以使用JSON.stringify来转换成JSON格式。ajax请求中,需要修改dataType和contentType,值都是’application/json’,可以不使用简写。
这里所谓的JSON格式传递参数,其实已经在上一节介绍过了,这里做一下总结。
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和请求方法就能完全理解接口的意义和功能)。下面再介绍两种相对特殊的使用方式。
其实路径参数支持正则表达式,例如我们在使用/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参数正则表达式的使用方式,更强大的用法可以自行摸索。
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。
Spring MVC可以通过request域,从后台向前端传递值
后台向前端通过request域传递值的方式:
代码:
//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" %>
Hello
${result}
方法中返回 ModelAndView 对象,此对象中设置模型数据并指定视图。它有两个常用方法:
代码片段:
// 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;
}
@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中的数据解析到视图页面上。
返回 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中带到页面中进行渲染。
我们先来简单介绍一下它的常见用法。
@PostMapping("/array")
public String array(Map 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类型参数处理例子。
这种情况下参数实际上直接回调ModelAndViewContainer中的ModelMap实例,参数处理器为MapMethodProcessor,往Map参数中添加的属性将会带到页面中。
这种情况下的参数处理器为RequestParamMapMethodArgumentResolver,使用的请求方式需要指定ContentType为x-www-form-urlencoded,不能使用application/json的方式:
控制器代码为:
@PostMapping(value = "/map")
public String mapArgs(@RequestParam Map map) {
log.info("{}", map);
return map.toString();
}
这种情况下的参数处理器为RequestHeaderMapMethodArgumentResolver,作用是获取请求的所有请求头的Key-Value。
这种情况下的参数处理器为PathVariableMapMethodArgumentResolver,作用是获取所有路径参数封装为Key-Value结构。
回顾之前使用 Servlet3.0 来解决文件上传的问题,编写上传表单(POST、multipart/form-data),还在处理方法 doPost 中编写解析上传文件的代码。但是在Spring MVC是可以帮我们简化文件上传的步骤和代码。
注意请求数据类型必须是:multipart/form-data,且请求方式是POST。
文件上传
我们可以在web.xml中指定上传文件的大小。
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:mvc.xml
1
52428800
52428800
dispatcherServlet
/
在springmvc.xml中配置上传解析器。要想使用Spring MVC中multipartfile接收客户端上传的文件,就必须配置文件上传解析器且解析的id必须为multipartResolver。
@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;
}
}
批量文件上传的时候,我们一般需要接收一个MultipartFile集合,可以有两种选择:
控制器方法代码如下:
@PostMapping(value = "/parts")
public String partArgs(@RequestParam(name = "file") List parts) {
log.info("{}", parts);
return parts.toString();
}
文件下载:将服务器上的文件下载到当前用户访问的计算机的过程称之为文件下载。
下载时必须设置响应的头信息,指定文件以何种方式保存,另外下载文件的控制器不能存在返回值,代表响应只用来下载文件信息。
/**
* 测试文件下载
* @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的常用注解。
@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:获取请求头中的值
Spring MVC是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring MVC也是要简化日常Web开发。(处理业务数据的对象和显示业务数据的视图之间存在紧密耦合,用MVC也是为了解耦)。
框架机制:Spring MVC的入口是servlet,而Struts2是filter。
拦截机制:
Struts2:
SpringMVC:
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是多例的。所以可以使用成员变量获取参数。所以效率低。