互联网应用主流框架整合之SpringMVC基础组件开发

多种传参方式

在前一篇文章互联网应用主流框架整合之SpringMVC初始化及各组件工作原理中讨论了最简单的参数传递,而实际情况要复杂的多,比如REST风格,它往往会将参数写入请求路径中,而不是以HTTP请求参数传递;比如查询客户,查询参数可能很多,需要传递JSON,需要分页,然后将数据集组装并传递分页参数;比如有时候需要传递多个对象等等,实际场景比想象的要多

SpringMVC提供了诸多的注解来解析参数,其目的是在于把控制器从复杂的Servlet API中剥离出来,这样就可以在非Web容器环境中重用这些控制器,同时也方便测试工程师进行有效地测试

接收普通请求参数

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>参数</title>
<!-- 加载Query文件-->
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.0.js"></script>
<!-- 此处插入JavaScript脚本  暂时忽略-->
</head>
<body><form id="form" action="./common"><table><tr><td>角色名称</td><td><input id="roleName" name="roleName" value="" /></td></tr><tr><td>备注</td><td><input id="note" name="note" /></td></tr><tr><td></td><td align="right"><input id="commit" type="button" value="提交" /></td></tr></table></form>
</body>
</html>

如果代码所示这是一个非常简单的表单,它传递了两个HTTP参数角色名称和备注,响应请求的是"./common", 也就是提交表单后,它就会请求到对应的URL上,对应的Controller如下代码所示

/*** 参数处理控制器,负责处理各种请求参数的场景,包括路径变量、请求体、请求参数等的不同组合。*/
@Controller
@RequestMapping("/params")
public class ParamsController {@Autowiredprivate RoleService roleService;/*** 首页请求处理方法,返回角色管理页面。** @return 视图模型,指向角色页面。*/@RequestMapping("/index")public ModelAndView index() {return new ModelAndView("role");}/*** 处理带有普通请求参数的请求,演示如何获取和使用这些参数。** @param roleName 角色名称参数* @param note 备注参数* @return 视图模型,用于重定向或显示结果。*/@RequestMapping("/common")public ModelAndView commonParams(String roleName, String note) {// 简单演示如何使用参数System.out.println("roleName =>" + roleName);System.out.println("note =>" + note);ModelAndView mv = new ModelAndView();mv.setViewName("index");return mv;}

此类情况是通过参数名称和HTTP请求参数的名称保持一致,来获取参数,如果不一致则无法获取参数,这样的方法允许参数为空;虽然这种方式能够满足大部分表单请求,但在有些场景下并不适合,比如新增一个用户,可能需要N多个字段,用这种方式传输,参数会非常多,这个时候就需要考虑用一个POJO来管理这些参数,在不借助其他注解的情况下,SpringMVC也有映射POJO的能力

新建一个角色参数类,代码如下所示

package com.sma.vo;/*** 角色参数类* 用于封装角色相关参数及分页参数的实体类。*/
public class RoleParams {// 角色名称private String roleName;// 角色备注信息private String note;/*** 获取角色名称* @return 角色名称*/public String getRoleName() {return roleName;}/*** 设置角色名称* @param roleName 角色名称*/public void setRoleName(String roleName) {this.roleName = roleName;}/*** 获取角色备注信息* @return 角色备注信息*/public String getNote() {return note;}/*** 设置角色备注信息* @param note 角色备注信息*/public void setNote(String note) {this.note = note;}
}

这个POJO中除了分页参数外,POJO的属性和HTTP参数一一对应了,接着在控制器中增加一个方法来通过这个POJO获取HTTP请求参数

    /*** 通过POJO对象接收请求体中的参数,便于处理复杂或多个参数的情况。** @param roleParams 包含角色参数和备注的VO对象* @return 视图模型,用于显示结果或进行重定向。*/@RequestMapping("/common/pojo")public ModelAndView commonParamPojo(RoleParams roleParams) {// 使用POJO对象获取参数System.out.println("roleName =>" + roleParams.getRoleName());System.out.println("note =>" + roleParams.getNote());ModelAndView mv = new ModelAndView();mv.setViewName("index");return mv;}

请求路径变为/common/pojo, 修改一下对应的form请求的action

<body><form id="form" action="./common/pojo"><table><tr><td>角色名称</td><td><input id="roleName" name="roleName" value="" /></td></tr><tr><td>备注</td><td><input id="note" name="note" /></td></tr><tr><td></td><td align="right"><input id="commit" type="button" value="提交" /></td></tr></table></form>
</body>

通过这样的方式可以将多个参数组织为一个POJO,以便于在参数较多时进行管理,这里需要注意的是POJO的属性也要和HTTP请求保持一致,它们也能够有效传递参数,但是有时候前端的参数命名规则和后端不一样,比如前端把角色名称的参数命名为role_name,这个时候就要进行转换,Spring MVC提供了诸多注解来实现各类转换规则

注解@RequestParam获取参数

把jsp代码中的角色名称参数名roleName改为role_name,获取参数会失败,SpringMVC提供了注解@RequestParam来处理这种情况,进行重新绑定规则,代码如下

    /*** 处理使用@RequestParam注解的请求参数,可以指定参数名称和是否必须。** @param roleName 角色名称请求参数* @param note 备注请求参数* @return 视图模型,用于显示结果或进行重定向。*/@RequestMapping("/request")public ModelAndView requestParam(@RequestParam("role_name") String roleName, String note) {// 使用@RequestParam注解获取参数System.out.println("roleName =>" + roleName);System.out.println("note =>" + note);ModelAndView mv = new ModelAndView();mv.setViewName("index");return mv;}

如果参数被@RequestParam注解,在默认情况下该参数不能为空,如果为空则会抛异常,如果要允许它为空,需要加上required=false,如下代码所示

public ModelAndView requestParam(@RequestParam(value = "role_name", required = false) String roleName, String note)

使用URL传递参数

使用URL的形式传递参数,这符合REST风格,对于一些业务比较简单的应用也十分常见,SpringMVC也对这种形式提供了良好的支持,如下代码所示

    /*** 通过路径变量获取URL中指定的id值,用于展示或操作特定ID的角色。** @param id 角色的ID,从URL路径中获取* @return 视图模型,包含角色信息的JSON视图。*/@RequestMapping("/role/{id}")public ModelAndView pathVariable(@PathVariable("id") Long id) {Role role = roleService.getRole(id);ModelAndView mv = new ModelAndView();mv.addObject(role);mv.setView(new MappingJackson2JsonView());return mv;}

在注解@RequestMapping的路径配置中的{id}表示控制器需要URL带有这个名为id的参数一起请求,方法中的@PathVariable("id")表示将获取这个在注解@RequestMapping中带过来的名为id的参数,然后通过角色服务类获取角色对象,并将其绑定到视图中,将视图设置为JSON;注意@PathVariable允许对应的参数为空

传递JSON参数

首先定义一个分页参数类PageParams,代码如下

/*** 分页参数类* 用于封装分页查询时的起始位置和每页记录数。*/
package com.sma.vo;public class PageParams {private int start; // 起始位置,表示从第几条记录开始查询private int limit; // 每页记录数,表示每页最多显示多少条记录/*** 获取起始位置* @return 起始位置的索引值*/public int getStart() {return start;}/*** 设置起始位置* @param start 起始位置的索引值,用于指定从哪条记录开始查询*/public void setStart(int start) {this.start = start;}/*** 获取每页记录数* @return 每页显示的记录数量*/public int getLimit() {return limit;}/*** 设置每页记录数* @param limit 每页显示的记录数量,用于指定每页最多显示多少条记录*/public void setLimit(int limit) {this.limit = limit;}
}

在角色参数类中添加分页属性,代码如下

package com.sma.vo;/*** 角色参数类* 用于封装角色相关参数及分页参数的实体类。*/
public class RoleParams {// 角色名称private String roleName;// 角色备注信息private String note;// 分页参数对象,用于角色列表的分页查询private PageParams pageParams = null;/*** 获取角色名称* @return 角色名称*/public String getRoleName() {return roleName;}/*** 设置角色名称* @param roleName 角色名称*/public void setRoleName(String roleName) {this.roleName = roleName;}/*** 获取角色备注信息* @return 角色备注信息*/public String getNote() {return note;}/*** 设置角色备注信息* @param note 角色备注信息*/public void setNote(String note) {this.note = note;}/*** 获取分页参数对象* @return 分页参数对象*/public PageParams getPageParams() {return pageParams;}/*** 设置分页参数对象* @param pageParams 分页参数对象*/public void setPageParams(PageParams pageParams) {this.pageParams = pageParams;}
}

向表单插入一段JavaScript,模拟通过jQuery传递JSON数据,代码如下

<script type="text/javascript">
$(document).ready(function() {//JSON参数和类RoleParams一一对应var data = {//角色查询参数roleName : 'role',note : 'note',//分页参数pageParams : {start : 0,limit : 20}}//Jquery的post请求$.post({url : "./roles",//此处需要告知传递参数类型为JSON,不能缺少contentType : "application/json",//将JSON转化为字符串传递data : JSON.stringify(data),//成功后的方法success : function(result) {}});
});
</script>

如代码所示,传递的JSON数据需要和对应参数的POJO保持一致,它将以请求体传递给控制器,所以只能用POST请求;在请求的时候须告知请求的参数类型为JSON,否则会引发控制器接收参数的异常;传递的参数是一个字符串,而不是JSON,所以这里使用了JSON.stringify()方法,将JSON数据转换为字符串

这个时候可以使用SpringMVC提供的注解@RequestBody接收参数,代码如下

/*** 使用@RequestBody注解从请求体中接收整个对象,适用于POST等提交复杂数据的场景。** @param roleParams 包含搜索条件的VO对象* @return 视图模型,包含搜索结果的JSON视图。*/@RequestMapping(value = "/roles", method = RequestMethod.POST)public ModelAndView findRoles(@RequestBody RoleParams roleParams) {List<Role> roleList = roleService.findRoles(roleParams);ModelAndView mv = new ModelAndView();mv.addObject("roleList", roleList);mv.setView(new MappingJackson2JsonView());return mv;}

这样SpringMVC就会把传递过来的请求体对应到POJO上了

接收列表数据和表单序列化

假如需要一次性删除多个数据,这时候可以考虑将一个数据传递给后端,同样的新增也是同样的情况,这就需要使用Java的集合或者数组保存对应的参数

SpringMVC对类似场景也有良好的支撑,如下JavaScript模拟传递数组给后端

<script type="text/javascript">$(document).ready(function() {//删除角色数组var idList = [ 1, 2, 3 ];//jQuery的post请求$.post({url : "./remove/roles",//将JSON转化为字符串传递data : JSON.stringify(idList),//指定传递数据类型,不可缺少contentType : "application/json",//成功后的方法success : function(result) {}});});
</script>

控制器接收数组参数代码如下

    /*** 删除角色,通过请求体接收一个角色ID列表,进行批量删除操作。** @param idList 角色ID列表,用于删除多个角色* @return 视图模型,包含删除总数的JSON视图。*/@RequestMapping(value = "/remove/roles", method = RequestMethod.POST)public ModelAndView removeRoles(@RequestBody List<Long> idList) {ModelAndView mv = new ModelAndView();int total = roleService.deleteRoles(idList);mv.addObject("total", total);mv.setView(new MappingJackson2JsonView());return mv;}

SpringMVC 通过@RequestBody注解,将传递过来的JSON数组数据转换为对应的Java集合类型

新增多个数据也是一样的逻辑

 <script type="text/javascript">
$(document).ready(function () {//新增角色数组var roleList = [{roleName: 'role_name_1', note: 'note_1'},{roleName: 'role_name_2', note: 'note_2'},{roleName: 'role_name_3', note: 'note_3'}];//jQuery的post请求$.post({url: "./insert/roles",//将JSON转化为字符串传递data: JSON.stringify(roleList),contentType: "application/json",//成功后的方法success: function (result) {}});
});
</script>
    /*** 新增角色,通过请求体接收一个角色列表,进行批量插入操作。** @param roleList 角色列表,用于批量插入新角色* @return 视图模型,包含插入总数的JSON视图。*/@RequestMapping(value = "/insert/roles", method = RequestMethod.POST)public ModelAndView insertRoles(@RequestBody List<Role> roleList) {ModelAndView mv = new ModelAndView();int total = roleService.insertRoles(roleList);mv.addObject("total", total);mv.setView(new MappingJackson2JsonView());return mv;}

通过表单序列化可以将表单数据转换为字符串传递给后端,因为一些特殊的字符需要进行一定的转换提交给后端,所以有时候需要在用户点击提交按钮后,通过序列化提交表单数据,代码如下

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>参数</title>
<!-- 加载Query文件-->
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.0.js"></script><script type="text/javascript">// 页面加载完成后执行的函数$(document).ready(function() {// 点击提交按钮时执行的函数$("#commit").click(function() {// 将表单数据序列化为字符串var str = $("form").serialize();// 使用jQuery的post方法提交表单数据// 提交表单$.post({// 设置提交的URL地址url:"./serialize/params",// 设置提交的数据,这里使用serialize方法获取的表单数据字符串// 将form数据序列化,传递给后台,// 则将数据以roleName=xxx&&note=xxx传递data:$("form").serialize(),// 设置提交成功后的回调函数success:function(result) {// 处理提交成功后的逻辑,这里留空}});});});</script></head>
<body><form id="form" action="./common"><table><tr><td>角色名称</td><td><input id="roleName" name="roleName" value="" /></td></tr><tr><td>备注</td><td><input id="note" name="note" /></td></tr><tr><td></td><td align="right"><input id="commit" type="button" value="提交" /></td></tr></table></form>
</body>
</html>

序列化之后,传递规则变为了roleName=xxx&&note=xxx,所以获取参数也响应的发生了变化,代码如下

    /*** 处理序列化参数的请求,展示如何接收和处理通过@RequestParam注解的序列化参数。** @param roleName 角色名称请求参数* @param note 备注请求参数* @return 视图模型,包含序列化参数的JSON视图。*/@RequestMapping(value = "/serialize/params", method = RequestMethod.POST)public ModelAndView serializeParams(@RequestParam("roleName") String roleName, @RequestParam("note") String note) {ModelAndView mv = new ModelAndView();mv.setView(new MappingJackson2JsonView());mv.addObject("roleName", roleName);mv.addObject("note", note);return mv;}

重定向

首先看一段将角色信息转化为JSON视图的功能代码,如下所示

    /*** 展示特定角色的JSON信息,通过路径变量获取角色ID。** @param id 角色ID* @param roleName 角色名称* @param note 角色备注* @return 视图模型,包含角色信息的JSON视图。*/@RequestMapping("/role/info")public ModelAndView showRoleJsonInfo(Long id, String roleName, String note) {ModelAndView mv = new ModelAndView();mv.setView(new MappingJackson2JsonView());mv.addObject("id", id);mv.addObject("roleName", roleName);mv.addObject("note", note);return mv;}

有一个这样的需求,每当新增一个角色信息时,需要将新增的数据以JSON视图的形式展示给请求者,在数据保存到数据库后,由数据库返回角色编号,再将角色信息传递给showRoleJsonInfo方法,就可以展示JSON视图给请求者,代码如下

    /*** 插入角色并重定向到角色信息页面,展示如何在插入后获取并使用新角色的ID。** @param model Spring的Model接口,用于向视图传递数据* @param roleName 角色名称* @param note 角色备注* @return 重定向到角色信息页面的字符串路径。*/@RequestMapping("/role/insert")public String insertRole(Model model, String roleName, String note) {Role role = new Role();role.setRoleName(roleName);role.setNote(note);roleService.insertRole(role);model.addAttribute("roleName", roleName);model.addAttribute("note", note);model.addAttribute("id", role.getId());return "redirect:./info";}

这里用到了Model,它代表数据模型,可以给它附上对应的数据模型,然后通过返回字符串实现重定向的功能,Spring MVC有一个约定,当返回的字符串以redirect为前缀时,就会被认为请求最后需要重定向;不仅仅可以通过返回字符串来实现重定向,也可以通过返回视图来实现重定向,代码如下

    /*** 另一种插入角色的方法,使用ModelAndView对象进行重定向并传递数据。** @param mv ModelAndView对象,用于设置重定向视图和附加数据* @param roleName 角色名称* @param note 角色备注* @return 视图模型,用于重定向到角色信息页面。*/@RequestMapping("/role/insert2")public ModelAndView insertRole2(ModelAndView mv, String roleName, String note) {Role role = new Role();role.setRoleName(roleName);role.setNote(note);roleService.insertRole(role);mv.setViewName("redirect:./info");mv.addObject("roleName", roleName);mv.addObject("note", note);mv.addObject("id", role.getId());return mv;}

这样可以将参数顺利的传给重定向的地址,同样的如果参数比较多,有些时候要传递POJO来完成,而不是一个个字段传递,代码如下

    /*** 通过角色信息展示JSON格式的数据。* 此方法处理请求的URL路径为/role/info2,旨在返回一个包含角色信息的JSON对象。* 使用MappingJackson2JsonView将模型对象转换为JSON格式,以便在客户端如JavaScript中使用。* @param role 角色对象,包含需要展示的角色信息。* @return ModelAndView 对象,配置了JSON视图和角色对象。*/@RequestMapping("/role/info2")public ModelAndView showRoleJsonInfo2(Role role) {ModelAndView mv = new ModelAndView();mv.setView(new MappingJackson2JsonView());mv.addObject("role", role);return mv;}

在RUL重定向的过程中,并不能有效地传递对象,因为HTTP的重定向参数是以字符串的形式传递的,这个时候Spring MVC提供了一个方法,就是flash属性,需要的数据模型是RedirectAttribute,代码如下

    /*** 使用RedirectAttributes进行角色插入并重定向,展示如何在重定向中携带额外信息。* @param ra RedirectAttributes接口,用于在重定向中添加闪现属性,SpringMVC会自动初始化它* @param roleName 角色名称* @param note 角色备注* @return 重定向到角色信息页面的字符串路径。*/@RequestMapping("/role/insert3")public String insertRole3(RedirectAttributes ra, String roleName, String note) {Role role = new Role(roleName, note);roleService.insertRole(role);ra.addFlashAttribute("role", role);return "redirect:./info2";}

这样就能传递POJO对象,使用addFlashAttribute方法后,Spring MVC会将数据保存到Session中,Session会在一个会话期有效,重定向后,就会将其清除,这样就能传递给下一个地址了
在这里插入图片描述

属性标签

有时候我们会将数据暂存到HTTP的request对象或者Session对象中,同样在开发控制器时,有时候也需要将对应的数据保存到这些对象中去,或者从它们当中读取数据,为此,SpringMVC有良好的支持,主要注解有@RequestAttribute@SessionAttribute@SessionAttributes

这三个注解都是Spring MVC框架中用于处理HTTP请求时,向控制器方法传递属性的注解,它们主要用于不同范围内的数据传递和管理。下面是这三个注解的解释:

  • @RequestAttribute
    • 用途: 该注解用于从HttpServletRequest的属性中获取数据,并将其绑定到控制器方法的参数上。它允许你在请求级别传递数据,这意味着这些属性只存在于当前HTTP请求的生命周期内
    • 应用场景: 当你需要在同一个请求的不同处理环节间传递数据时非常有用,比如在拦截器、过滤器或是在重定向之前设置某些属性供后续处理使用
  • @SessionAttribute
    • 用途: 此注解用于从HttpSession中获取属性值,并将其绑定到控制器方法的参数上。与@RequestAttribute不同,它操作的是session范围的数据,意味着这些属性可以在用户的整个会话期间保持有效,即使跨多个请求
    • 应用场景: 当你需要在用户的不同请求之间保持某些状态信息时,比如用户登录信息、购物车等,可以使用此注解来实现
  • @SessionAttributes
    • 用途: 这是一个类级别的注解,用于声明哪些模型属性(即控制器方法添加到Model中的属性)需要存储在HttpSession中。与单个方法上的@SessionAttribute不同,它定义了一个更大的作用域,影响控制器类中的所有处理器方法
    • 应用场景: 当你的控制器中有多个方法需要共享一些数据,且这些数据需要在用户的整个会话期间持久化时,使用@SessionAttributes可以在类级别声明这些共享的session属性。这在处理多步表单、维护临时的用户选择状态等场景中非常有用

总结来说,@RequestAttribute适用于单次请求内部的数据传递,@SessionAttribute用于单个方法中从session获取数据,而@SessionAttributes则是在控制器类级别管理那些需要跨请求持久化的数据。选择哪个注解取决于你希望数据存活的作用域以及具体的应用场景。

@RequestAttribute

这个注解的作用是从HTTP的请求对象(HttpServletRequest)中取出请求属性,只是它的有效性是在一次请求中存在,先建一个/WEB-INF/jsp/request_attribute.jsp文件,代码如下

<%@page contentType="text/html" pageEncoding="UTF-8"%> <!-- 设置页面的Content-Type和字符编码 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <!-- 定义HTML文档的类型和版本 -->
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <!-- 再次声明页面的Content-Type和字符编码,确保浏览器正确解析 --><title>SMA</title> <!-- 页面标题 -->
</head>
<body><%// 设置请求属性,这里将一个长整型数值1赋值给"id"属性request.setAttribute("id", 1L);// 向前转发请求到指定的资源,这里是处理请求参数的控制器或处理器request.getRequestDispatcher("/mvc/attribute/request/param").forward(request, response);// 清空输出缓冲区,避免之前的内容影响后续的输出out.clear();// 重新获取页面输出流,以便继续向客户端输出内容out = pageContext.pushBody();%>
</body>
</html>

代码中首先设置了id为1L的请求属性,然后进行了转发控制器,这样将由对应的控制器处理业务逻辑,代码如下

@Controller
@RequestMapping("/attribute")
public class AttributeController {// 角色服务@Autowiredprivate RoleService roleService = null;// 访问页面request_attribute.jsp@RequestMapping("/request/page")public ModelAndView requestPage() {return new ModelAndView("request_attribute");}/*** 测试@RequestAttribute* @param id 角色编号* @return ModelAndView*/@RequestMapping("/request/param")public ModelAndView requestAttribute(@RequestAttribute(value="id", required = false) Long id) {ModelAndView mv = new ModelAndView();Role role = roleService.getRole(id);mv.addObject("role", role);mv.setView(new MappingJackson2JsonView());return mv;}

@SessionAttribute@SessionAttributes

这两个注解和HTTP的会话对象(HttpSession)有关,在浏览器和服务器保持联系的时候HTTP会创建一个会话对象,这样可以让浏览器和服务器会话期间,通过它读/写会话对象的属性,缓存一定的数据信息

在控制器总可以使用注解@SessionAttributes设置对应的键值对,不过这个注解只能对类进行标注,不能对方法或者参数进行注解,它可以配置属性名称或者属性类型,其作用是当用它标注了某个类,SpringMVC执行完控制器的逻辑后,将数据模型中对应的属性名称或者属性类型保存到HTTP的会话对象中,如下代码所示

package com.sma.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;import com.sma.pojo.Role;
import com.sma.service.RoleService;@Controller
@RequestMapping("/attribute")
//可以配置数据模型的名称和类型,两者取或关系
@SessionAttributes(names = { "id" }, types = { Role.class })
public class AttributeController {// 角色服务@Autowiredprivate RoleService roleService = null;... .../*** 测试@SessionAttributes* @param id 角色编号* @return ModelAndView*/@RequestMapping("/session/{id}")public ModelAndView sessionAttrs(@PathVariable("id") Long id) {ModelAndView mv = new ModelAndView();Role role = roleService.getRole(id);// 根据类型,Session将会保存角色信息mv.addObject("role", role);// 根据名称,Session将会保存idmv.addObject("id", id);// 视图名称,定义跳转到一个JSP文件上mv.setViewName("session_show");return mv;}

这个时候请求/mvc/attribute/session/1,那么请求会进入sessionAttrs方法中,数据模型保存了一个id和角色,由于它们都满足了注解@SessionAttributes的配置,所以最后请求会保存到Session对象中,视图名称设置为session_show,说明要进一步跳转到/WEB-INF/jsp/session_show.jsp中,这样就可以通过JSP文件去验证注解@SessionAttributes的配置是否有效了,session_show.jsp代码如下

<%@ page language="java" import="com.sma.pojo.Role" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Show Session Attribute</title>
</head>
<body><%// 从session中获取Role对象,用于后续显示角色信息Role role = (Role) session.getAttribute("role");// 输出角色的id,以便用户确认当前角色的标识out.println("id = " + role.getId() + "<p/>");// 输出角色的名称,以便用户了解当前角色的名称out.println("roleName = " + role.getRoleName() + "<p/>");// 输出角色的备注信息,以便用户了解角色的详细描述out.println("note = " + role.getNote() + "<p/>");// 从session中获取用户id,用于后续显示或验证用户身份Long id = (Long) session.getAttribute("id");// 输出用户id,以便用户确认当前登录的用户标识out.println("id = " + id + "<p/>");%>
</body>
</html>

这样就可以在控制器内不使用给Servlet的API造成侵入的HttpSession对象设置Session的属性了;既然有了设置Session的属性,自然有读取Session属性的要求,SpringMVC是通过注解@SessionAttribute实现的

首先写个/WEB-INF/jsp/session_attribute.jsp,让它保存Session的属性,代码如下

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.io.*" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>session</title>
</head>
<body>
<%// 设置会话属性,将一个长整型值1存储到会话中,键为"id"session.setAttribute("id", 1L);// 向Dispatcher请求将请求转发到指定的资源,这里是处理会话参数的控制器request.getRequestDispatcher("/mvc/attribute/session/param").forward(request, response);// 清空输出缓冲区,确保之前的输出不会影响后续的页面渲染out.clear();// 重新设置输出流,以便可以继续向客户端输出内容out = pageContext.pushBody();
%>
</body>
</html>

当请求JSP时,它会在Session中设置一个属性id,然后跳转到对应的控制器上,在控制器中加入对应的方法,并在方法的参数中通过注解@SessionAttribute来获取Session属性值,如下代码所示

// 访问session_attribute.jsp@RequestMapping("/session/page")public ModelAndView sessionPage() {ModelAndView mv = new ModelAndView("session_attribute");return mv;}/*** 测试@SessionAttribute* @param id 角色名称* @return ModelAndView*/@RequestMapping("/session/param")public ModelAndView sessionParam(@SessionAttribute(value = "id", required = false) Long id) {ModelAndView mv = new ModelAndView();Role role = roleService.getRole(id);mv.addObject("role", role);mv.setView(new MappingJackson2JsonView());return mv;}

@CookieValue@RequestHeader

这两个注解分别用于从Cookie和HTTP请求头获取对应的请求信息,它们用法比较简单,且大同小异,只是对于Cookie而言,需要考虑的是用户是可以禁用的

    /***  获取Cookie和请求头(RequestHeader)属性* @param userAgent 用户代理* @param jsessionId 会话编号* @return ModelAndView*/@RequestMapping("/header/cookie")public ModelAndView testHeaderAndCookie(@RequestHeader(value = "User-Agent",required = false,defaultValue = "attribute") String userAgent,@CookieValue(value = "JSESSIONID",required = true,defaultValue = "MyJsessionId") String jsessionId) {ModelAndView mv = new ModelAndView();mv.setView(new MappingJackson2JsonView());mv.addObject("User-Agent", userAgent);mv.addObject("JSESSIONID", jsessionId);return mv;}

表单验证

在实际的工作中,得到数据后的第一步就是验证数据的正确性,如果存在录入上的问,那么一般会通过注解验证,发现错误后返回给用户,但是对于一些逻辑错误就很难使用注解方式验证,这个时候可以使用Spring提供的验证器(Validator)规则去验证,Spring的验证规则符合JSR(Java Specification Requests), 但是它只是一个提案,存在多种实现,目前业界广泛使用的是Hibernate Validator

在Spring MVC中,所有的验证都需要先注册验证器,验证器是由Spring MVC自动注册和加载的,不需要用户处理,为了使用JSR功能,需要引入如下依赖

    <dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.1.0.Final</version></dependency>

JSR303注解验证输入内容

JSR 303, 正式名称为 Bean Validation (在 JSR 349 中更新为 Bean Validation 1.1, 而最新的版本是 JSR 380, 也称为 Bean Validation 2.0), 是Java为企业级应用提供的一个数据验证的标准规范。它允许开发者使用注解来声明性地规定数据验证规则,而无需在业务逻辑中混入验证代码。

  • 常用的JSR 303验证注解包括但不限于:
    • @Null:被注释的元素必须为 null。
    • @NotNull:被注释的元素必须不为 null。
    • @AssertTrue:被注释的元素必须为 true。
    • @AssertFalse:被注释的元素必须为 false。
    • @Min(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值。
    • @Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值。
    • @Size(min=, max=):被注释的元素的大小必须在指定的范围内。
    • @Length(min=, max=):字符串的长度限制,与 @Size 类似,但仅用于字符串。
    • @Pattern(regex=, flags=):被注释的字符串必须符合指定的正则表达式。
    • @Email:被注释的字符串必须是电子邮箱地址。
  • 自定义验证注解:除了上述内置注解外,JSR 303 还支持自定义验证注解。自定义注解需要配合以下元注解使用:
    • @Constraint(validatedBy = {YourValidator.class}):指定实现约束验证的类。
    • @Target({ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE}):定义该注解可以应用到哪些程序元素上。
    • @Retention(RetentionPolicy.RUNTIME):确保注解在运行时可见。
    • @Documented:表示这个注解应该被 javadoc 工具记录。
    • message:默认的错误消息模板。
    • groups 和 payload:用于分组验证和携带额外的元数据。

Spring 提供了Bean的功能验证,通过注解@Valid标明哪个Bean需要启用注解式的验证,在javax.validation.constraints.*中定义了一系列的JSR规范给出的注解
在这里插入图片描述
org.hibernate.validator.constraints.*中也定义了一系列的JSR规范给出的注解
在这里插入图片描述
实际使用,假设有这样一个表单/WEB-INF/jsp/validation.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>validate</title>
</head>
<body><form id="form" method="post" action="./validator"><table><tr><td>产品编号:</td><td><input name="productId" id="productId" /></td></tr><tr><td>用户编号:</td><td><input name="userId" id="userId" /></td></tr><tr><td>交易日期:</td><td><input name="date" id="date" /></td></tr><tr><td>价格:</td><td><input name="price" id="price" /></td></tr><tr><td>数量:</td><td><input name="quantity" id="quantity" /></td></tr><tr><td>交易金额:</td><td><input name="amount" id="amount" /></td></tr><tr><td>用户邮件:</td><td><input name="email" id="email" /></td></tr><tr><td>备注:</td><td><textarea id="note" name="note" cols="20" rows="5"></textarea></td></tr><tr><td colspan="2" align="right"><input type="submit" value="提交" /></tr></table></form>
</body>
</html>

对应的POJO代码如下

package com.sma.pojo;import java.util.Date;import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;public class Transaction {@NotNull //不能为空private Long productId;@NotNull //不能为空private Long userId;@Future //只能是将来的日期@DateTimeFormat(pattern = "yyyy-MM-dd")//日期格式化转换@NotNull //不能为空private Date date;@NotNull //不能为空@DecimalMin(value = "0.1") //最小值0.1元private Double price;@Min(1) //最小值为1@Max(100)//最大值@NotNull //不能为空private Integer quantity;@NotNull //不能为空@DecimalMax("500000.00") //最大金额为5万元@DecimalMin("1.00") //最小交易金额1元private Double amount;@Pattern(regexp = "^([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)*@"+ "([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+"+ "[\\.][A-Za-z]{2,3}([\\.] [A-Za-z]{2})?$",message="不符合邮件格式")private String email;@Size(min = 0, max = 256) //0到255个字符private String note;public Long getProductId() {return productId;}public void setProductId(Long productId) {this.productId = productId;}public Long getUserId() {return userId;}public void setUserId(Long userId) {this.userId = userId;}public Date getDate() {return date;}public void setDate(Date date) {this.date = date;}public Double getPrice() {return price;}public void setPrice(Double price) {this.price = price;}public Integer getQuantity() {return quantity;}public void setQuantity(Integer quantity) {this.quantity = quantity;}public Double getAmount() {return amount;}public void setAmount(Double amount) {this.amount = amount;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public String getNote() {return note;}public void setNote(String note) {this.note = note;}
}

这样就加入了对每个字段的验证,它会生成默认的错误消息,邮件的验证还是用了配置项message来重新定义验证失败后的错误信息,如此便能启动Spring的验证规则来验证表单了,配以如下控制器

package com.sma.controller;import java.util.List;import javax.validation.Valid;import org.springframework.stereotype.Controller;
import org.springframework.validation.DataBinder;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;import com.sma.pojo.Transaction;
import com.sma.validator.TransactionValidator;@Controller
@RequestMapping("/validate")
public class ValidateController {// 表单页面@RequestMapping("/form")public ModelAndView formPage() {return new ModelAndView("validation");}/*** Spring验证(JSR 303)* @param trans 交易* @param errors 错误* @return*/@RequestMapping("/annotation")public ModelAndView annotationValidate(@Valid Transaction trans, Errors errors) {ModelAndView mv = new ModelAndView();// 是否存在错误if (errors.hasErrors()) {// 获取错误信息List<FieldError> errorList = errors.getFieldErrors();for (FieldError error : errorList) {// 获取错误信息mv.addObject(error.getField(), error.getDefaultMessage());}}mv.setView(new MappingJackson2JsonView());return mv;}

@Valid Transaction trans, Errors errors标明这个Bean将会被验证,另一个类型为Errors的参数则用于记录是否存在错误信息,也就是当采用JSR规范进行验证后,它会将错误信息保存到这个参数中,进入方法后使用Errors对象的hasErrors方法,便能够判断其验证是否出现错误,启动项目,访问/mvc/validate/form进入表单,输入数据,点击提交按钮,数据会提交到控制器中,页面会给出对应的错误信息

自定义验证器

创建一个检查手机号码格式的自定义注解 @IsMobile

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;@Documented
@Constraint(validatedBy = IsMobileValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsMobile {String message() default "手机号码格式不正确";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}

然后实现对应的验证器 IsMobileValidator:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {private static final String MOBILE_PATTERN = "^1[3-9]\\d{9}$";@Overridepublic void initialize(IsMobile constraintAnnotation) {}@Overridepublic boolean isValid(String mobile, ConstraintValidatorContext context) {return mobile != null && mobile.matches(MOBILE_PATTERN);}
}

这样,你就可以在需要验证手机号码的字段或参数上使用@IsMobile注解了

Spring验证器

Spring提供了Validator接口实现验证,它将在进入控制器逻辑之前对参数的合法性进行验证,Validator接口是Spring MVC验证表单逻辑的核心接口,其接口代码如下所示

/*** 验证器接口,用于验证特定类型的对象。* 实现这个接口的类必须提供支持验证的类型,并执行实际的验证逻辑。*/
package org.springframework.validation;
/*** Validator接口定义了验证器的行为,验证器用于验证给定对象是否符合特定的验证规则。*/
public interface Validator {/*** 检查此验证器是否支持给定的类* 通过此方法,可以确定验证器是否适用于特定类型的对象验证* @param clazz 需要验证的对象的类。参数类型使用通配符?,表示支持任何类* @return 如果验证器支持该类,则返回true;否则返回false*/boolean supports(Class<?> var1);/*** 验证给定对象是否符合特定的验证规则* 此方法将验证逻辑委托给实现类,实现类负责实际的验证工作,并通过Errors参数报告任何验证错误* @param object 待验证的对象* @param errors 用于收集验证过程中发现的错误的Errors实例*/void validate(Object var1, Errors var2);
}

Validator接口实例是一个具体的验证器,在Spring中最终被注册到验证器列表中,这样就可以提供给各个控制器使用,它通过supports方法判定是否会启用验证器验证数据,对于逻辑的验证咋通过validate方法实现,接口实例如下代码所示

package com.sma.validator;import org.springframework.validation.Errors;
import org.springframework.validation.Validator;import com.sma.pojo.Transaction;/*** 交易验证器类,用于验证Transaction对象的合法性。* 实现了Spring的Validator接口,用于在业务逻辑中进行数据验证。*/
public class TransactionValidator implements Validator {/*** 判断当前验证器是否支持指定的类。* 本验证器仅支持com.sma.pojo.Transaction类的验证。** @param clazz 需要验证的类* @return 如果clazz等于Transaction类,则返回true;否则返回false。*/@Overridepublic boolean supports(Class<?> clazz) {// 匹配为交易记录类型return Transaction.class.equals(clazz);}/*** 对Transaction对象进行验证。* 验证交易金额是否等于价格乘以数量。* 如果不相等,则认为数据不合法,将错误信息添加到Errors对象中。** @param target 需要验证的对象,类型应为Transaction* @param errors 用于收集验证过程中发现的错误信息的对象*/@Overridepublic void validate(Object target, Errors errors) {// 强制转换类型Transaction trans = (Transaction) target;// 计算交易金额与价格乘以数量的差值// 求交易金额和价格×数量的差额double dis = trans.getAmount()- (trans.getPrice() * trans.getQuantity());// 如果差值的绝对值大于0.01,认为交易金额与价格乘以数量不匹配,添加错误信息// 如果差额大于0.01,则认为业务错误if (Math.abs(dis) > 0.01) {//加入错误信息errors.rejectValue("amount", null, "交易金额和购买数量与价格不匹配");}}
}

这样这个验证器就会现在supports方法中判断是否为Transaction对象,如果判断为是,才会进行后面的逻辑验证,SpringMVC提供了注解@InitBinder,通过它可以将验证器和控制器绑定到一起,这样就能验证表单请求了,控制器代码如下所示

    @InitBinderpublic void initBinder(DataBinder binder) {//数据绑定器加入验证器binder.setValidator(new TransactionValidator());}@RequestMapping("/validator")public ModelAndView validator(@Valid Transaction trans, Errors errors) {ModelAndView mv = new ModelAndView();//是否存在错误if (errors.hasErrors()) {//获取错误信息List<FieldError>errorList = errors.getFieldErrors();for (FieldError error : errorList) {// 获取错误信息mv.addObject(error.getField(), error.getDefaultMessage());}}mv.setView(new MappingJackson2JsonView());return mv;}
}

这样把表单的请求URL修改为./validator,就能够请求得到我们的validator方法了

JSR注解方式和验证器方式不能同时使用,不过可以在使用JSR注解方式得到基本的验证信息后,再使用自己的方法验证

数据模型

视图是业务处理后展现给用户的内容,一般伴随着业务处理返回的数据,用来给用户查看,控制器处理对应业务逻辑后,首先会将数据绑定到数据模型中,并且指定视图的信息,然后将视图名称转发到视图解析器中,通过视图解析器定位到最终视图,最后将数据模型渲染到视图中,展示最终的结果给用户

之前的代码中一直用ModelAndView定义视图类型,包括JSON视图,也用它来加载数据模型;ModelAndView有一个类型为ModelMap的属性model,而ModelMap继承了LinkedHashMap<String,Object>,因此它可以存放各种键值对,为了进一步定义数据模型功能,Spring还创建了类ExtendedModelMap,这个类实现了数据模型定义的Model接口,并且在此基础上派生了关于数据绑定的类BindingAwareModelMap,关系如下图所示
在这里插入图片描述
在控制器方法中,可以把ModelAndView、Model、ModelMap作为参数,Spring MVC在运行的时候,会自动初始化它们,Spring MVC可以选择ModelMap或者其子类作为数据模型;ModelAndView被初始化后,Model属性为空,当调用它增加数据模型的方法后,会自动创建一个ModelMap实例,用以保存数据模型,这就是数据模型之间的关系

创建一个控制器,代码如下

package com.sma.controller;import java.util.List;
import java.util.Map;import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;import com.sma.pojo.Role;
import com.sma.service.ExcelExportService;
import com.sma.service.RoleService;
import com.sma.view.ExcelView;
import com.sma.vo.PageParams;
import com.sma.vo.RoleParams;@Controller
@RequestMapping("/role")
/*** 角色控制器类,负责处理与角色相关的请求。* 使用@SessionAttributes注解来指示将角色对象存储在会话中,以便在多个请求之间共享。*/
@SessionAttributes(names = "role", types = Role.class)
public class RoleController {/*** 自动注入角色服务,用于处理角色相关的业务逻辑。*/@Autowiredprivate RoleService roleService = null;/*** 根据角色ID从模型映射中获取角色信息。* @param id 角色ID* @param modelMap 模型映射对象* @return 返回包含角色信息的ModelAndView对象,以JSON格式展示。*/@RequestMapping(value = "/modelmap/{id}", method = RequestMethod.GET)public ModelAndView getRoleByModelMap(@PathVariable("id") Long id, ModelMap modelMap) {Role role = roleService.getRole(id);ModelAndView mv = new ModelAndView();modelMap.addAttribute("role", role);mv.setView(new MappingJackson2JsonView());return mv;}/*** 根据角色ID从模型中获取角色信息。* @param id 角色ID* @param model 模型对象* @return 返回包含角色信息的ModelAndView对象,以JSON格式展示。*/@RequestMapping(value = "/model/{id}", method = RequestMethod.GET)public ModelAndView getRoleByModel(@PathVariable("id") Long id, Model model) {Role role = roleService.getRole(id);ModelAndView mv = new ModelAndView();model.addAttribute("role", role);mv.setView(new MappingJackson2JsonView());return mv;}/*** 根据角色ID获取角色信息,并返回一个ModelAndView对象,该对象将被渲染为特定的页面。* @param id 角色ID* @param mv ModelAndView对象,用于存储视图和模型数据* @return 返回一个ModelAndView对象,其中包含角色信息和要显示的页面。*/@RequestMapping(value = "/mv/{id}", method = RequestMethod.GET)@ResponseBodypublic ModelAndView getRoleByMv(@PathVariable("id") Long id, ModelAndView mv) {Role role = roleService.getRole(id);mv.addObject("role", role);mv.addObject("id", id);// 跳转到具体的页面(/WEB-INF/jsp/session_show.jsp)mv.setViewName("session_show");return mv;}

无论使用Model还是ModelMap,都是BindingAwareModelMap的实例,BindingAwareModelMap是一个继承了ModelMap且实现了Model接口的类,所以就有了相互转换的功能

视图和视图解析器

视图是展示给用户的内容,在此之前,要通过控制器得到对应的数据模型,如果是非逻辑视图,就不会经过视图解析器定位视图,而是直接渲染数据模型便结束了;如果是逻辑视图,就要对其进一步解析,以定位真实视图,这就是视图解析器的作用,而视图则把控制器返回的数据模型进行渲染,从而将数据展示给用户

视图

在请求之后,SpringMVC控制器获取了对应的数据,被绑定到数据模型中,视图就可以展示数据模型的信息了;Spring MVC定义了多种视图,每一种都需要满足视图接口View的定义

/*** 接口View定义了视图的规范,视图是MVC模式中的V(视图)部分,负责渲染响应。* 它不直接与HTTP请求或响应交互,而是通过一个Map对象来获取渲染所需的数据,* 并通过HttpServletRequest和HttpServletResponse来获取或设置HTTP特定的信息。*/
package org.springframework.web.servlet;import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;public interface View {// 常量RESPONSE_STATUS_ATTRIBUTE用于存储响应状态属性的名称String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";// 常量PATH_VARIABLES用于存储路径变量属性的名称String PATH_VARIABLES = View.class.getName() + ".pathVariables";// 常量SELECTED_CONTENT_TYPE用于存储选定的Content-Type属性的名称String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";/*** 获取视图的Content-Type。* 默认情况下,返回null,表示视图将自行决定或不设置Content-Type。* @return 视图的Content-Type,可能为null。*/@Nullabledefault String getContentType() {return null;}/*** 渲染视图。* 此方法使用给定的模型数据、HTTP请求和响应来呈现视图。* 模型数据是一个Map对象,包含键值对,其中键是变量名,值是变量的值。* HttpServletRequest和HttpServletResponse提供了关于HTTP请求和响应的信息,* 可以用于获取请求参数或设置响应头等操作。* @param model    包含渲染视图所需数据的Map对象。* @param request  HTTP请求对象,用于获取请求信息。* @param response HTTP响应对象,用于设置响应信息。* @throws Exception 如果渲染过程中发生错误。*/void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

在这里插入图片描述
图中是常用的视图类及其关系,Spring MVC还有其他的视图类,例报表使用的AbstractJasperReportsSingleFormatView等等;JstlView和InternalResourceView是父子类,它们可以被归为一类,主要是为JSP的渲染服务的,可以使用JSTL标签库,也可以使用Spring MVC定义的标签库;MappingJackson2JsonView则是JSON视图类,它是一个非逻辑视图,木器是将数据模型转换为JSON视图,例如如下代码

    /*** 根据角色ID从模型映射中获取角色信息。* @param id 角色ID* @param modelMap 模型映射对象* @return 返回包含角色信息的ModelAndView对象,以JSON格式展示。*/@RequestMapping(value = "/modelmap/{id}", method = RequestMethod.GET)public ModelAndView getRoleByModelMap(@PathVariable("id") Long id, ModelMap modelMap) {Role role = roleService.getRole(id);ModelAndView mv = new ModelAndView();modelMap.addAttribute("role", role);mv.setView(new MappingJackson2JsonView());return mv;}

mv.setView(new MappingJackson2JsonView());指定了具体视图的类型,由于MappingJackson2JsonView是非逻辑视图,所以在没有视图解析器的情况下也可以渲染,最终将其绑定的数据模型转换为JSON数据

InternalResourceView是一个逻辑视图,它需要一个视图解析器,通常会在dispatcher-servlet.xml中进行如下配置

<?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:p="http://www.springframework.org/schema/p"xmlns:tx="http://www.springframework.org/schema/tx"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-4.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"><!-- 使用注解驱动 --><mvc:annotation-driven /><!-- 定义扫描装载的包 --><context:component-scan base-package="com.*" /><!-- 定义视图解析器 --><!-- 找到Web工程/WEB-INF/JSP文件夹,且文件结尾为jsp的文件作为映射 --><bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver"p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" /><!-- 如果有配置数据库事务,需要开启注解事务的,需要开启这段代码 --><!-- <tx:annotation-driven transaction-manager="transactionManager" /> -->
</beans>

也可以使用Java配置的方式取代它,如下代码所示

package com.ssmvc.web.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;/*** Spring MVC的配置类* 通过@Configuration注解标记这个类是一个配置类,等同于Spring XML配置文件* 使用@EnableWebMvc注解开启Spring MVC的功能* 使用@ComponentScan注解指定Spring要扫描的组件包,这些包中的组件会被自动注册为Spring Bean*/
@Configuration
@ComponentScan("com.ssmvc.controller")
@EnableWebMvc
public class WebConfig {/*** 配置InternalResourceViewResolver,作为Spring MVC的视图解析器* 通过@Bean注解标记这个方法会返回一个Bean,该Bean会被注册到Spring的ApplicationContext中* 方法名称viewResolver和@Bean注解中的name属性一起定义了这个Bean的名称* @return 返回一个配置好的InternalResourceViewResolver实例*/@Bean(name = "viewResolver")public ViewResolver initViewResolver() {InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();viewResolver.setPrefix("/WEB-INF/jsp/");viewResolver.setSuffix(".jsp");return viewResolver;}
}

也可以通过实现WebMvcConfigurer接口来实现,代码如下

package com.ssmvc.web.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;/*** Spring MVC的配置类* 通过@Configuration注解标记这个类是一个配置类,等同于Spring XML配置文件* 使用@EnableWebMvc注解开启Spring MVC的功能* 使用@ComponentScan注解指定Spring要扫描的组件包,这些包中的组件会被自动注册为Spring Bean*/
@Configuration
@ComponentScan("com.ssmvc.controller")
@EnableWebMvc
public class WebConfigII implements WebMvcConfigurer {/*** 配置视图解析器* 本方法用于设置Spring MVC中视图解析的前缀和后缀,指定视图文件在项目中的位置和格式。* 使用InternalResourceViewResolver作为视图解析器,它能够处理JSP视图。* 设置前缀为"/WEB-INF/jsp/",确保视图文件位于WEB-INF目录下的jsp子目录中。* 设置后缀为".jsp",指明视图文件的格式为JSP。** @param registry ViewResolverRegistry实例,用于注册和配置视图解析器*/@Overridepublic void configureViewResolvers(ViewResolverRegistry registry) {InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();viewResolver.setPrefix("/WEB-INF/jsp/");viewResolver.setSuffix(".jsp");registry.viewResolver(viewResolver);}/*** 本方法用于配置默认的控制器,即无需处理业务逻辑的简单页面跳转。* 通过ViewControllerRegistry添加一个控制器,将"/config/index"路径映射到"index"视图。* 这样当请求"/config/index"时,会直接展示对应的"index.jsp"页面,无需额外的控制器逻辑。* @param registry ViewControllerRegistry实例,用于注册和配置ViewController*/@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/config/index").setViewName("index");}
}

无论使用何种方法,都是为了创建一个视图解析器,让Spring MVC可以通过前缀和后缀加上视图名称找到对应的JSP文件,然后把数据模型渲染到JSP文件中

视图解析器

非逻辑视图是不需要用视图解析器解析的,例如MappingJackson2JsonView,它的含义是把当前数据模型转化为JSON,不需要转换试图逻辑名称,但是对于逻辑视图而言,通过视图名称定位到最终视图是一个必备过程,例如InternalResourceView就是这样的一个视图,当它被配置后,就会加载到SpringMVC的视图解析器列表中,当返回ModelAndView时,SpringMVC就会在视图解析器列表中遍历,找到对应的视图解析器去解析式图,视图解析器接口源码如下

/*** 视图解析器接口,用于根据视图名称和区域设置解析视图。* 视图解析器的职责是将逻辑视图名称映射到实际的视图对象上,以便视图可以负责渲染响应。* 它是Spring MVC框架中的一部分,用于支持不同的视图技术。*/
import java.util.Locale;
import org.springframework.lang.Nullable;
public interface ViewResolver {/*** 根据给定的视图名称和区域设置解析视图。* * @param viewName 视图的逻辑名称,通常是由控制器返回的字符串。* @param locale 用户的区域设置,用于支持国际化。* @return 解析后的视图对象,如果无法解析则返回null。* @throws Exception 如果解析过程中出现错误。* * 方法注释解释了为什么方法会返回null,以及当无法解析视图时应该做什么。*/@NullableView resolveViewName(String viewName, Locale locale) throws Exception;
}

接口源码就两个参数,一个视图名,一个Locale类型,Locale类型参数是用于国际化的,这就说明了Spring MVC是支持国际化的,对于Spring MVC框架而言,它也配置了多种视图解析器,如下UML图所示
在这里插入图片描述
图中展示了Spring MVC自带的视图解析器,当控制器返回视图的逻辑名称时,通过这些解析器就能定位到具体的视图

有时候在控制器中并没有返回一个ModelAndView,而是只返回一个字符串,它也能够渲染视图,因为视图解析器定位了对应的视图,例如如下代码

    /*** 首页请求处理方法的另一种形式,返回角色管理页面的字符串路径。* @return 角色页面的字符串路径。*/@RequestMapping("/index2")public String index2() {return "role";}

由于配置了InternalResourceViewResolver,所以通过Spring MVC系统能够找到InternalResourceView视图,如果存在数据模型,那么Spring MVC会将视图和数据模型绑定到一个ModelAndView上,然后视图解析器会根据视图的名称,找到对应的视图资源,这就是视图解析器的作用

Excle视图使用

导出Excel

在实际的应用开发中,经常遇到需要导出Excel的功能,对于Excel视图的开发,Spring MVC推荐使用AbstractXlsView,它实现了视图接口,是一个抽象类,不能生成实例对象,但它自己定义了一个抽象方法buildExcelDocument去实现,代码如下

/*** 抽象类,作为生成Excel文档视图的基础。* 该类继承自AbstractView,用于处理以Excel格式响应的视图解析。* 它默认将内容类型设置为application/vnd.ms-excel,适合Excel文件下载。*/
package org.springframework.web.servlet.view.document;import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.web.servlet.view.AbstractView;public abstract class AbstractXlsView extends AbstractView {/*** 构造函数,设置默认的内容类型为Excel的MIME类型。*/public AbstractXlsView() {this.setContentType("application/vnd.ms-excel");}/*** 判断视图是否生成用于下载的内容。* 对于Excel视图,通常是用于下载的。* @return 布尔值,表示是否生成下载内容。*/protected boolean generatesDownloadContent() {return true;}/*** 渲染合并后的模型数据到Excel文档。* 该方法是抽象类AbstractView中的具体实现部分,用于处理Excel文档的生成。* @param model 映射表,包含所有要呈现的数据。* @param request HTTP请求对象,可能用于获取额外的渲染上下文信息。* @param response HTTP响应对象,用于设置响应头信息和输出Excel内容。* @throws Exception 如果渲染过程中发生错误。*/protected final void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {Workbook workbook = this.createWorkbook(model, request);this.buildExcelDocument(model, workbook, request, response);response.setContentType(this.getContentType());this.renderWorkbook(workbook, response);}/*** 创建一个空的工作簿对象。* 默认实现使用HSSFWorkbook创建一个Excel 2003及以前版本的工作簿。* @param model 渲染所需的数据模型。* @param request HTTP请求对象,可能用于获取额外的上下文信息。* @return 工作簿对象,用于构建Excel文档。*/protected Workbook createWorkbook(Map<String, Object> model, HttpServletRequest request) {return new HSSFWorkbook();}/*** 将工作簿写入HTTP响应中,完成Excel文档的渲染。* @param workbook 要写入响应的工作簿对象。* @param response HTTP响应对象,用于输出Excel内容。* @throws IOException 如果写入过程中发生I/O错误。*/protected void renderWorkbook(Workbook workbook, HttpServletResponse response) throws IOException {ServletOutputStream out = response.getOutputStream();workbook.write(out);workbook.close();}/*** 子类必须实现该方法,用于填充工作簿的具体内容。* 这是抽象方法,需要子类根据实际情况实现,以将模型数据填充到Excel工作簿中。* @param model 映射表,包含所有要呈现的数据。* @param workbook 用于构建Excel文档的工作簿对象。* @param request HTTP请求对象,可能用于获取额外的上下文信息。* @param response HTTP响应对象,可能用于设置额外的响应头信息。* @throws Exception 如果构建Excel文档过程中发生错误。*/protected abstract void buildExcelDocument(Map<String, Object> var1, Workbook var2, HttpServletRequest var3, HttpServletResponse var4) throws Exception;
}

只要完成这个buildExcelDocument方法,便能使用Excel视图功能了,该方法主要任务是创建一个Workbook,这里需要用到POI的API,需要引入如下依赖

    <!-- 引入Apache POI依赖,用于处理Microsoft Office文档 --><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.1</version></dependency>

假设要导出所有角色信息,先定义一个接口,该接口主要用于自定义生成Excel的规则,如下代码所示

package com.sma.service;import java.util.Map;import org.apache.poi.ss.usermodel.Workbook;public interface ExcelExportService {/****  生成Exel文档规则* @param model 数据模型* @param workbook POI的Excel workbook*/public void makeWorkBook(Map<String, Object> model, Workbook workbook);
}

然后还需要一个可实例化的Excel视图类,因为导出文档还需要一个下载文件名称,所以还需要定义一个文件名属性,由于该视图不是一个逻辑视图,所以无需解析器运行,代码如下

package com.sma.view;import java.util.Map;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.view.document.AbstractXlsView;import com.sma.service.ExcelExportService;/*** Excel视图类,继承自AbstractXlsView,用于导出Excel文件。* 通过提供不同的构造方法,可以灵活地配置导出服务和文件名。*/
public class ExcelView extends AbstractXlsView {// Excel文件名private String fileName = null;// 导出服务接口,用于生成Excel内容private ExcelExportService excelExpService = null;/*** 构造方法,仅传入导出服务接口。* @param excelExpService 导出服务接口,用于生成Excel内容。*/public ExcelView(ExcelExportService excelExpService) {this.excelExpService = excelExpService;}/*** 构造方法,传入视图名称和导出服务接口。* @param viewName 视图名称,用于Spring MVC框架识别。* @param excelExpService 导出服务接口,用于生成Excel内容。*/public ExcelView(String viewName, ExcelExportService excelExpService) {this.setBeanName(viewName);}/*** 构造方法,传入视图名称、导出服务接口和文件名。* @param viewName 视图名称,用于Spring MVC框架识别。* @param excelExpService 导出服务接口,用于生成Excel内容。* @param fileName Excel文件名,用于设置导出文件的名称。*/public ExcelView(String viewName,ExcelExportService excelExpService, String fileName) {this.setBeanName(viewName);this.excelExpService = excelExpService;this.fileName = fileName;}// 获取文件名public String getFileName() {return fileName;}// 设置文件名public void setFileName(String fileName) {this.fileName = fileName;}// 获取导出服务接口public ExcelExportService getExcelExpService() {return excelExpService;}// 设置导出服务接口public void setExcelExpService(ExcelExportService excelExpService) {this.excelExpService = excelExpService;}/*** 覆盖父类方法,用于构建Excel文档。* @param model 视图模型,包含导出数据。* @param workbook 工作簿对象,用于存储Excel内容。* @param request HTTP请求对象,用于获取请求参数。* @param response HTTP响应对象,用于设置响应头信息。* @throws Exception 如果导出过程中发生错误。*/@Overrideprotected void buildExcelDocument(Map<String, Object> model,Workbook workbook, HttpServletRequest request,HttpServletResponse response) throws Exception {// 检查导出服务接口是否为空if (excelExpService == null) {throw new RuntimeException("导出服务接口不能为null!!");}// 文件名不为空,为空则使用请求路径中的字符串作为文件名if (!StringUtils.isEmpty(fileName)) {// 处理文件名的字符编码String reqCharset = request.getCharacterEncoding();reqCharset = reqCharset == null ? "UTF-8" : reqCharset;fileName = new String(fileName.getBytes(reqCharset), "UTF-8");// 设置响应头,指定文件名response.setHeader("Content-disposition", "attachment;filename=" + fileName);}// 回调接口方法,使用自定义生成Excel文档excelExpService.makeWorkBook(model, workbook);}}

代码实现了生成ExcelDocument方法,完成了一个视图类,然后在控制器中加入对应的方法,代码如下

    /*** 导出所有角色列表到Excel。* @return 返回一个包含角色列表的Excel文件的ModelAndView对象。*/@RequestMapping(value = "/excel/list", method = RequestMethod.GET)public ModelAndView export() {//模型和视图ModelAndView mv = new ModelAndView();//Excel视图,并设置自定义导出接口ExcelView ev = new ExcelView("role-list", exportService(), "所有角色.xlsx");//设置SQL后台参数RoleParams roleParams = new RoleParams();//限制1万条PageParams page = new PageParams();page.setStart(0);page.setLimit(10000);roleParams.setPageParams(page);//查询List<Role>roleList = roleService.findRoles(roleParams);//加入数据模型mv.addObject("roleList", roleList);mv.setView(ev);return mv;}/*** 提供一个私有的ExcelExportService实例,用于定制Excel导出的逻辑。* @return 返回一个ExcelExportService实例,用于角色列表的导出。*/@SuppressWarnings({ "unchecked"})private ExcelExportService exportService() {//使用Lambda表达式自定义导出excel规则return (Map<String, Object> model, Workbook workbook) -> {//获取用户列表List<Role>roleList = (List<Role>) model.get("roleList");//生成SheetSheet sheet= workbook.createSheet("所有角色");//加载标题Row title = sheet.createRow(0);title.createCell(0).setCellValue("编号");title.createCell(1).setCellValue("名称");title.createCell(2).setCellValue("备注");//便利角色列表,生成一行行的数据for (int i=0; i<roleList.size(); i++) {Role role = roleList.get(i);int rowIdx = i + 1;Row row = sheet.createRow(rowIdx);row.createCell(0).setCellValue(role.getId());row.createCell(1).setCellValue(role.getRoleName());row.createCell(2).setCellValue(role.getNote());}};}

如此便能够导出Excel了

上传文件

在互联网应用中,上传头像、图片等相关文件的需求十分常见,SpringMVC为上传文件提供了良好的支持,通过MultipartResolver接口来处理,源码如下

/*** 解析器接口,用于处理HTTP请求中的多部分数据,例如文件上传。* 它提供了检查请求是否包含多部分数据、解析多部分请求以及清理多部分请求数据的功能。* * 该接口的实现应该能够处理多部分请求的解析,包括但不限于文件上传。* 解析过程中可能涉及到的步骤包括识别多部分请求、分割多部分数据、为每个部分分配名称和类型等。* 实现类还需要处理解析过程中可能出现的错误,例如文件大小超出限制或文件类型不被允许。*/
package org.springframework.web.multipart;import javax.servlet.http.HttpServletRequest;public interface MultipartResolver {/*** 检查给定的HttpServletRequest是否包含多部分数据。* * @param request 要检查的HTTP请求。* @return 如果请求是多部分的,则返回true;否则返回false。*/boolean isMultipart(HttpServletRequest request);/*** 解析多部分请求,返回一个封装了多部分数据的MultipartHttpServletRequest实例。* * @param request 要解析的HTTP请求。* @return 封装了多部分数据的MultipartHttpServletRequest实例。* @throws MultipartException 如果解析过程中出现错误。*/MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;/*** 清理与给定的MultipartHttpServletRequest相关的多部分数据。* 这一步骤通常包括删除临时文件,这些文件是在解析多部分请求时创建的。* * @param request 包含多部分数据的MultipartHttpServletRequest。*/void cleanupMultipart(MultipartHttpServletRequest request);
}

它有两个实现类,其中CommonsMultipartResolver依赖于Apache下的Jakarta Common FileUpload项目;StandardServletMultipartResolver则不依赖第三方包,在Spring3.1和Servlet3.0以上版本可以直接用,但在这个版本之前的只能使用CommonsMultipartResolver
在这里插入图片描述
在Spring中,既可以通过XML也可以通过Java配置MultipartResolver,对于StandardServletMultipartResolver,它的构造方法没有参数,通过注解@Bean就可以进行初始化,如下代码所示

    /*** 初始化并配置MultipartResolver bean,用于处理HTTP请求中的多部分(multipart)数据,例如文件上传。* 使用StandardServletMultipartResolver实现,这是Spring Boot默认的multipart解析器。** @return 返回一个新的StandardServletMultipartResolver实例,用于处理multipart请求。* @Bean 注解表明该方法会返回一个bean实例,并将其注册到Spring应用上下文中,名称为"multipartResolver"。*/@Bean(name = "multipartResolver")public MultipartResolver initMultipartResolver() {return new StandardServletMultipartResolver();}

multipartResolver是Spring约定好的Bean名称,不可以修改,上传文件还需要对文件进行限制,比如单个文件的大小,设置上传路径等等,在通过Java配置Spring MVC初始化的时候,只需要继承类AbstractAnnotationConfigDispatcherServletInitializer就可以,通过继承它就可以注解配置了,这个类提供了一个可以覆盖的方法customizeRegistration,它是一个用于初始化DispatcherServlet的方法(Servlet3.0以上),通过它可以配置文件上传的一些属性,Spring MVC初始化器代码如下

package com.sma.config;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;import com.sma.backend.config.BackendConfig;
import com.sma.web.config.WebConfig;/*** Spring MVC应用程序的初始化器。* 继承自AbstractAnnotationConfigDispatcherServletInitializer,用于配置Spring MVC的DispatcherServlet和应用程序上下文。*/
public class MyWebAppInitializerextends AbstractAnnotationConfigDispatcherServletInitializer {/*** 配置根应用程序上下文的类。* 这些类被用来初始化ApplicationContext。* @return 根应用程序上下文的类数组。*/@Overrideprotected Class<?>[] getRootConfigClasses() {// 可以返回Spring的Java配置文件数组return new Class<?>[] {BackendConfig.class };}/*** 配置DispatcherServlet的应用程序上下文的类。* 这些类被用来初始化DispatcherServlet的ApplicationContext。* @return DispatcherServlet的应用程序上下文的类数组。*/@Overrideprotected Class<?>[] getServletConfigClasses() {// 可以返回Spring的Java配置文件数组return new Class<?>[] { WebConfig.class };}/*** 配置DispatcherServlet的URL映射。* 这些URLs会被DispatcherServlet处理。* @return URL映射的字符串数组。*/@Overrideprotected String[] getServletMappings() {return new String[] { "/mvc/*" };}/*** 自定义Servlet注册。* 这里用于配置文件上传的相关设置。* @param dynamic Servlet的动态注册接口,用于配置Servlet。*/@Overrideprotected void customizeRegistration(Dynamic dynamic) {// 文件上传路径String filepath = "e:/mvc/uploads";// 5MBLong singleMax = (long) (5*Math.pow(2, 20));// 10MBLong totalMax = (long) (10*Math.pow(2, 20));// 配置MultipartResolver,限制请求,单个文件5MB,总文件10MBdynamic.setMultipartConfig(new MultipartConfigElement(filepath, singleMax, totalMax, 0));}}

如果使用XML,就在web.xml文件中配置DispatcherServlet的地方配置就可以,如下代码所示

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1"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/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"><!-- 配置Spring IoC配置文件路径 --><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/applicationContext.xml</param-value></context-param><!-- 配置ContextLoaderListener用以初始化Spring IoC容器 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- 配置DispatcherServlet --><servlet><!-- 注意:Spring MVC框架会根据这个名词,找到/WEB-INF/dispatcher-servlet.xml作为配置文件载入 --><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 使得Dispatcher在服务器启动的时候就初始化 --><load-on-startup>2</load-on-startup><!--MultipartResolver参数 --><multipart-config><location>e:/mvc/uploads/</location><!-- 单个文件限制5MB --><max-file-size>5242880</max-file-size><!-- 总文件限制10MB --><max-request-size>10485760</max-request-size><file-size-threshold>0</file-size-threshold></multipart-config></servlet><!-- Servlet拦截配置 --><servlet-mapping><servlet-name>dispatcher</servlet-name><!-- 拦截路径匹配 --><url-pattern>/mvc/*</url-pattern></servlet-mapping>
</web-app>

通过这样的XML配置也可以实现对MultipartResolver的配置初始化,然后通过XML或者注解生成StandardServletMultipartResolver即可

也可以使用CommonsMultipartResolver完成,但它依赖第三方包,需要导入如下依赖

    <!-- 引入Apache Commons FileUpload依赖 --><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version></dependency>

使用它需要配置一个Bean, 使用Java配置文件方式,代码如下

    /*** 初始化并配置CommonsMultipartResolver,用于处理文件上传。* @return CommonsMultipartResolver 实例,配置了单个文件和总上传大小的限制,以及上传文件的临时目录。* @bean(name = "multipartResolver") 该方法标记为一个Spring Bean,命名为"multipartResolver"。*/@Bean(name = "multipartResolver")public MultipartResolver initCommonsMultipartResolver() {// 文件上传路径String filepath = "e:/mvc/uploads";// 设置单个文件的最大上传大小为5MBLong singleMax = (long) (5 * Math.pow(2, 20));// 设置总上传大小的最大限制为10MBLong totalMax = (long) (10 * Math.pow(2, 20));// 创建CommonsMultipartResolver实例CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();// 配置单个文件的最大上传大小multipartResolver.setMaxUploadSizePerFile(singleMax);// 配置总上传大小的最大限制multipartResolver.setMaxUploadSize(totalMax);try {// 设置上传文件的临时存储目录multipartResolver.setUploadTempDir(new FileSystemResource(filepath));} catch (IOException e) {// 如果设置上传目录时发生IO异常,打印异常堆栈跟踪e.printStackTrace();}// 返回配置好的CommonsMultipartResolver实例return multipartResolver;}

处理完上传后,就是处理文件解析,在Spring MVC中,对于MultipartResolver解析的调度是通过DispatcherServlet进行的,它首先判断请求是否是一种enctype="multipart/*"请求,如果是并且存在一个名为multipartResolver的Bean定义,那么它会把HttpServletRequest请求转换为MultipartHttpServletRequest请求对象,MultipartHttpServletRequest是Spring MVC的一个接口,其关系如下

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
操作文件是需要持有一定的资源的,而DispatcherServlet会在请求的最后释放掉这些资源,它还会把文件请求转换为一个MultipartFile对象,通过这个对象可以进一步操作文件

提价文件会以POST请求为主,首先建一个表单WEB-INF/jsp/file_upload.jsp,代码如下

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传</title>
</head>
<body><form method="post" action="./part"enctype="multipart/form-data"><input type="file" name="file" value="请选择上传的文件" /> <inputtype="submit" value="提交" /></form>
</body>
</html>

然后开发控制器,代码如下

package com.sma.controller;import java.io.File;
import java.io.IOException;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;@Controller
@RequestMapping("/file")
public class FileController {// 文件路径private static final String FILE_PATH = "e:/mvc/uploads/";@RequestMapping(value = "/page", method = RequestMethod.GET)public String page() {return "file_upload";}@RequestMapping(value = "/upload", method = RequestMethod.POST)public ModelAndView upload(HttpServletRequest request) {// 进行转换MultipartHttpServletRequest mhsr = (MultipartHttpServletRequest) request;// 获得请求上传的文件MultipartFile file = mhsr.getFile("file");// 设置视图为JSON视图ModelAndView mv = new ModelAndView();mv.setView(new MappingJackson2JsonView());// 获取原始文件名String fileName = file.getOriginalFilename();// 目标文件File dest = new File(FILE_PATH + fileName);try {// 保存文件file.transferTo(dest);// 保存成功mv.addObject("success", true);mv.addObject("msg", "上传文件成功");} catch (IllegalStateException | IOException e) {// 保存失败mv.addObject("success", false);mv.addObject("msg", "上传文件失败");e.printStackTrace();}return mv;}

如此便可以把文件保存到指定的路径中了,但这样会有一个问题,当使用HttpServletRequest作为方法参数时,会造成API侵入,可以修改为用MultipartFile或者Part类对象实现,MultipartFile是Spring MVC提供的类,Part是Servlet API提供的类,在上面FileController的基础上,新增方法,实现代码如下

 package com.sma.controller;import java.io.File;
import java.io.IOException;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;@Controller
@RequestMapping("/file")
public class FileController {// 文件路径private static final String FILE_PATH = "e:/mvc/uploads/";@RequestMapping(value = "/page", method = RequestMethod.GET)public String page() {return "file_upload";}@RequestMapping(value = "/upload", method = RequestMethod.POST)public ModelAndView upload(HttpServletRequest request) {// 进行转换MultipartHttpServletRequest mhsr = (MultipartHttpServletRequest) request;// 获得请求上传的文件MultipartFile file = mhsr.getFile("file");// 设置视图为JSON视图ModelAndView mv = new ModelAndView();mv.setView(new MappingJackson2JsonView());// 获取原始文件名String fileName = file.getOriginalFilename();// 目标文件File dest = new File(FILE_PATH + fileName);try {// 保存文件file.transferTo(dest);// 保存成功mv.addObject("success", true);mv.addObject("msg", "上传文件成功");} catch (IllegalStateException | IOException e) {// 保存失败mv.addObject("success", false);mv.addObject("msg", "上传文件失败");e.printStackTrace();}return mv;}// 使用MultipartFile@RequestMapping("/multipart/file")public ModelAndView uploadMultipartFile(MultipartFile file) {// 定义JSON视图ModelAndView mv = new ModelAndView();mv.setView(new MappingJackson2JsonView());// 获取原始文件名String fileName = file.getOriginalFilename();file.getContentType();// 目标文件File dest = new File(FILE_PATH + fileName);try {// 保存文件file.transferTo(dest);mv.addObject("success", true);mv.addObject("msg", "上传文件成功");} catch (IllegalStateException | IOException e) {mv.addObject("success", false);mv.addObject("msg", "上传文件失败");e.printStackTrace();}return mv;}// 使用Part@RequestMapping("/part")public ModelAndView uploadPart(Part file) {ModelAndView mv = new ModelAndView();mv.setView(new MappingJackson2JsonView());// 获取原始文件名String fileName = file.getSubmittedFileName();File dest = new File(fileName);try {// 保存文件file.write(FILE_PATH + fileName);mv.addObject("success", true);mv.addObject("msg", "上传文件成功");} catch (IllegalStateException | IOException e) {mv.addObject("success", false);mv.addObject("msg", "上传文件失败");e.printStackTrace();}return mv;}
}

只需要修改表单提交地址便可以使用新的方法了,但需要注意Servlet3.0之后才支持Part

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/27150.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

audio标签隐藏播放器尾部的三个点

问题&#xff1a; 在谷歌浏览器上&#xff0c;展示audio音频控件时&#xff0c;后面有三个点&#xff0c;点击后会显示下载和播放速度&#xff0c;想隐藏这两个控件。 注意&#xff1a; 不是所有版本都有这三个点&#xff0c;甚至有的版本里面三个点里面的控件只有下载。 解…

【Windows10】查看WIFI密码

操作步骤 电脑上查看已连接Wi-Fi的密码的步骤如下: 连接需要查看密码的Wi-Fi。右键点击任务栏上的 [网络] 图标&#xff0c;选择 [开启"网络和Internet"设置]。在 高级网络设置 项目中&#xff0c;点选 [网络和共享中心]。开启网络和共享中心的窗口后&#xff0c;点…

vue打包exe实战记录

vue项目不支持直接打包exe,可以依靠electron进行打包,处理方式是将vue打包的dist文件夹放到electron项目中,通过配置后打包electron.先看下本地环境 下面是实操记录: 1.vue项目打包 vue.config.js中设置项目路径为 module.exports {//publicPath: /chat_pc/, // 前端项目…

苍穹外卖笔记-13-导入地址簿功能代码、用户下单、订单支付

文章目录 1. 导入地址簿功能代码1.1 需求分析和设计1.1.1 产品原型1.1.2 接口设计1.1.3 表设计 1.2 代码导入1.2.1 Mapper层1.2.2 Service层1.2.3 Controller层 1.3 功能测试 2. 用户下单2.1 需求分析和设计2.1.1 产品原型2.1.2 接口设计2.1.3 表设计 2.2 代码开发2.2.1 DTO设计…

Spotify 音乐平台宣布成立内部创意机构,测试生成式人工智能配音广告

Spotify是一家流媒体音乐平台&#xff0c;提供广泛的音乐、播客和视频内容。用户可以通过订阅服务Spotify Premium来享受更多高级功能&#xff0c;如无广告播放、离线听歌等。 Spotify 周四宣布&#xff0c;它将通过其首家名为 Creative Lab 的内部创意机构进一步进军广告领域…

集合查询-并(UNION)集运算、交(INTERSECT)集运算、差(EXCEPT)集运算

一、概述 集合查询是对两个SELECT语句的查询结果进行再进行处理的查询 二、条件 1、两个SELECT语句的查询结果必须是属性列数目相同 2、两个SELECT语句的查询结果必须是对应位置上的属性列必须是相同的数据类型 三、并(UNION)运算 1、语法格式&#xff1a; SELECT 语句1…

Vite - 项目打包从 0 到 1(完美解决打包后访问白屏问题)

目录 开始 修改资源相对地址 引入 vitejs/plugin-legacy 插件并配置 修改打包指令 修改 router 中的 history 前端配置跨域相关 打包后成功访问 开始 修改资源相对地址 在 vite.config.js 文件中配置如下&#xff1a; export default defineConfig({base: ./, //1.打包…

揭秘循环购模式:为何商家愿“送钱”,用户能边消费边赚钱?

大家好&#xff0c;我是你们的电商专家吴军。今天&#xff0c;我将带大家走进一个神秘而又吸引人的商业模式——循环购模式。你可能会疑惑&#xff0c;为什么消费者能在这里“消费1000送2000”&#xff0c;每天还能领取现金并提现&#xff1f;商家真的在“送钱”吗&#xff1f;…

(css)el-tabs滚动按钮浮动问题

(css)el-tabs滚动按钮浮动问题 修改前&#xff1a; 修改后&#xff1a; 思路&#xff1a;找到相应元素&#xff0c;降低层级 css写法&#xff1a; ::v-deep .el-tabs__nav {z-index: 1; }

MySql出现的问题

1.在控制面吧输入mysql显示不是内部命令 2.找到mysql安装的目录,复制目录路径 3.打开系统属性设置环境变量中的Path将路径添加到里面 4.添加好以后将控制面板重新打开输入命令 2.解决安装mysql错误 导致多个mysql服务删除教程 1.用管理员身份打开cmd命令板 2.在…

各地区城乡居民基本养老保险情况数据,Shp+excel格式

基本信息. 数据名称: 各地区城乡居民基本养老保险情况数据 数据格式: Shpexcel 数据几何类型: 面 数据坐标系: WGS84 数据时间&#xff1a;2008-2018年 数据来源&#xff1a;网络公开数据 数据可视化.

Android Studio Koala | 2024.1.1 发布,快来看看有什么更新吧

自从三年前 Android Studio 更改了版本方案之后&#xff0c;从 Arctic Fox 开始每个主要版本都发布一个动物代号&#xff0c;同时版本迭代更新的节奏也越来越快&#xff0c;当然“填坑”的速度和“开坑”的速度几乎也“持平”&#xff0c;可以说每个版本都有痛点&#xff0c;都…

68. UE5 RPG 优化敌人角色的表现效果

我们现在已经有了四个敌人角色&#xff0c;接下来&#xff0c;处理一下在战斗中遇到的问题。 处理角色死亡后还会攻击的问题 因为我们有角色溶解的效果&#xff0c;角色在死亡以后的5秒钟才会被销毁掉。所以在这五秒钟之内&#xff0c;角色其实还是会攻击。主要时因为AI行为树…

【踩坑】修复Ubuntu远程桌面忽然无法Ctrl C/V复制粘贴及黑屏

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 说在前面&#xff1a; 需要注意的是&#xff0c;我发现他应该是新开了一个窗口给我。我之前打开的东西&#xff0c;在这个新窗口里都没有了&#xff0c…

区间预测 | Matlab实现GRU-ABKDE门控循环单元自适应带宽核密度估计多变量回归区间预测

区间预测 | Matlab实现GRU-ABKDE门控循环单元自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现GRU-ABKDE门控循环单元自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现GRU-ABKDE门控循环单元自适应…

JAVA动态表达式:Antlr4 表达式树解析

接上面 JAVA动态表达式&#xff1a;Antlr4 G4 模板 读取字符串表达式结构树-CSDN博客 目前已经实现了常量及分组常规表达式的解析。 String formula "啦啦啦1 and 11 and 23 and 1123 contains 1 and 23455 notcontains 5"; String formula "啦啦啦1 and (…

SAP 角色授权账户 重复的问题 解决方案

直接从agr_usrs 里面删除新的 *&---------------------------------------------------------------------* *& Report ZRPT_BC_ROLEASSIGN_RM_DUP *&---------------------------------------------------------------------* *&角色授权去重 *&--------…

植物大战僵尸杂交版 fatal error及问题解决闪退

echo off set KEY_NAMESoftware\PopCap\PlantsVsZombies set VALUE_NAMEScreenmode set DATA0 reg add HKCU%KEY_NAME% /v %VALUE_NAME% /t REG_DWORD /d %DATA% /f if %errorlevel% neq 0 ( echo 注册表数值数据修改失败 ) else ( echo 注册表数值数据已成功修改为0 ) 将上述…

requests post json/data;requests response 接收不同数据

1、requests post json/data 在Python的requests库中&#xff0c;当你发送POST请求时&#xff0c;可以选择使用json参数或data参数来传递数据。这两者之间的主要区别在于它们如何被序列化和发送到服务器。 json参数&#xff1a; 当你使用json参数时&#xff0c;requests库会自…

【CTF Web】CTFShow 数据库恶意下载 Writeup(目录扫描+mdb文件泄露+Access脱库)

数据库恶意下载 10 mdb文件是早期aspaccess构架的数据库文件&#xff0c;文件泄露相当于数据库被脱裤了。 解法 用 dirsearch 扫描。 dirsearch -u 4b9b415f-4062-4bba-a6f5-3b107804043f.challenge.ctf.show找到一个 db 目录。 扫描 db 目录。 dirsearch -u 4b9b415f-4062-…