SpingMVC
1.什么是SpringMVC
是spring的一直子项目,为表述层开发的一套完整。
表述层是前台页面和后天servlet
注:三层架构分为表述层,业务逻辑层,数据范围程
2.SpringMVC特点
1.Sping家族原生产品,与IOC容器等集成基础无缝对接
2.基于原生Servlet,通过功能前端控制器DispatcherServlet,对需求和响应处理
3.表述层各细分领域需要解决问题全方位覆盖,提供解决方案
4.代码简洁,大幅度提高开发效率
5.内部组件化程度高,需要什么功能插件使用
6.性能卓越,适合大型的互联网项目
3.项目创建
pom.xml中 provided 设置,如果不设置打包后会将依赖webapp/info/lib中 。如果设置代表服务器提供无需添加
配置
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
可以选择注解
/*指的所有请求 /是除了.jsp的所有请求
<url-pattern>/</url-pattern>
4.注解创建 组件
控制器组件@Controller
@RequestMapping("/")
添加到方法前处理相对于的请求时候调用方法 返回的是视图
通过语法添加上下文路径访问目标页面target.html
3.@RequestMapping注解
1.@RequestMapping注解功能
将请求和对应处理请求的方法连接起来,形成映射关系
映射可以多对一 不能一对多
2.@RequestMapping注解位置
1.标志一个类:设置映射请求路径的初始信息(常用设置不同的模块)
2.标志一个方法:设置映射请求的具体信息
@Controller
@RequestMapping("test")
public class questMapping {
//此刻的访问路径为 /test/requestmaping
@RequestMapping("/requestmaping")
public String a(){
return "success";
}
}
3.@RequestMapping注解位置value属性
String[]数组 @RequestMapping(value = {“/b”,“/a”})匹配多个请求满足其中一个
必须有value属性
4.@RequestMapping注解位置method属性
请求方式:post/get 匹配映射
是一个RequestMethod的数组 可以匹配多种请求方式的映射
如果满足value属性但是不满足method属性浏览器报错405
不设置任何请求方式都匹配
@RequestMapping(value = "/target",method = {RequestMethod.POST,RequestMethod.GET})
请求方式:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
在基础上派生注解
@GetMapping("/test/requestmaping")
public String GET(){
return "success";
}
5.@RequestMapping注解位置params属性(!!必须同时满足数组内设置)
通过匹配请求的参数来匹配请求
是一个字符串类型的数组可以设置多个匹配方式
param:要求必须有参数请求
!param:要求都不能携带参数
param=value要求必须携带参数且参数类型必须等于value
param=value要求必须携带参数且参数类型必须不等于value
拼接请求参数****
报错为400
6.@RequestMapping注解位置headers属性(!!必须同时满足数组内设置)
匹配请求头中的键值对 F12 network
写法
报错为404
7.SpringMVC支持ant风格的路径
?:表示单个占位符。可以是任意单个字符
@RequestMapping("/a?a/test") 可以匹配任意类似于aca/test ava/test路径
*:表示0个或者多个字符。
@RequestMapping("/a*a/test") 可以匹配任意类似于acddadaa/test avdasdasda/test aa/test 路 径
*和?只能都是两层路径 / local/local
**:表示多层
@RequestMapping("/**/test") 可以匹配任意类似于da/dad/asda/test dsad/test等 路 径
不可写成/a**a/会别解析为单个*
8.SpringMVC中的占位符
常用方式用问号传参 :/test?1
rest方式:/test/1
使用@PathVariable的注解将站位符的值传递给参数
请求地址是/tset/1/admin
@RequestMapping("/test/{id}/{admin}")
public String testPath(@PathVariable("id")int id, @PathVariable("name")String name){
使用该注解给参数修饰
4.SpringMVC的参数请求 基本不用
1.通过Servlet API获取 设定一个request对象 DispatchServerlet请求时会生成
@RequestMapping("/servletAPI")
public String toServletAPI(HttpServletRequest request){
int id = Integer.parseInt(request.getParameter("id"));
String name= request.getParameter("name");
System.out.println(id+" "+name);
2.通过控制器来获取参数
可以直接获取,但需要保持参数名和形参一致
@RequestMapping("/testParam")
public String toServletParam(int id,String name){
多个同名的参数可以通过数组获取 比如复选框 类似于servlet中获取
@RequestMapping("/testParam")
public String toServletParam(int id,String name,String[] hobby){
如果不使用数组获取,hobby的值将通过逗号拼接
@RequestMapping("/testParam")
public String toServletParam(int id,String name,String[] hobby){
3.通过RquestPatam获取请求参数
参数和形参进行映射关系
public String toServletParam(@RequestParam("uid") int id, String name){
System.out.println(id+" "+name);
可以处理传递参数名和形参不一致的情况 这里的uid是必须传递的参数
public String toServletParam(@RequestParam(value = "uid",required = false) int id, String name){
required设置非必要传输需传输
public String toServletParam(@RequestParam(value = "uid",required = false,defaultValue = "6666") int id, String name){
System.out.println(" "+name);
defauktValue属性来设置如果传递为空 的默认属性值
报错为400
也可以处理List集合 必须使用@RequestParam
public String toServletParam(@RequestParam(value = "uid",required = false,defaultValue = "6666") List<int> uid, String name){
System.out.println(" "+name);
4.通过@RequestHeader
属性同@RequestHeader
@RequestMapping("/testParam")
public String toServletParam(@RequestParam(value = "uid",required = false,defaultValue = "6666") int id, String name,
@RequestHeader(value = "host",defaultValue = "hh",required = false) String host
5.通过@CookieValue
属性3个同@RequestParam
session依赖cookie
@RequestMapping("/testParam")
public String toServletParam(@RequestParam(value = "uid",required = false,defaultValue = "6666") int id, String name,
@RequestHeader(value = "host",defaultValue = "hh",required = false) String host,
@CookieValue(value = "JSESSIONID") String cookie
6.通过POJO来获取请求参数
控制器方法的形式参数位置设置应该实体类型的形参,若请求的形参参数名和实体类属性名一致,请求参数为此赋值
@RequestMapping("/testBean")
public String ParaByUser(user user ){
System.out.println(user.toString());
return "success";
javabean的格式
7.request请求乱码处理
get请求的是服务器中配置的问题 tomcat的servlet.xml
post请求通过过滤器处理spring自带的CharacterEncodingFilter
在web.xml中配置
<!-- 过滤器配置-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 请求编码-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 响应编码-->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5.域对象共享数据
1.使用servletAPI向request域对象共享数据
@RequestMapping("/scope1")
public String scopeByServletAPI(HttpServletRequest request){
request.setAttribute("scope1","hello");
return "ok";
};
2.使用ModelAndView向request域对象共享数据
使用ModelAndView类对象 的addObject方法和setViewName
返回值必须是ModelAndView类型
2个功能 一向requst对象共享数据 二设置视图名称
@RequestMapping("/testModelAndView")
public ModelAndView mov(){
ModelAndView mav=new ModelAndView();
//处理模型数据,向请求区域request共享数据
mav.addObject("scope1","modelandview");
//设置视图名称
mav.setViewName("ok");
return mav;
}
3.通过Model向request域对象共享数据
使用生成的model参数形参 使用addAttribute方法
@RequestMapping("/testModel")
public String model(Model model){
model.addAttribute("scope1","model");
return "ok";
}
4.使用map向request域对象共享数据
使用map键值对参数
@RequestMapping("/testMap")
public String Map(Map<String,Object> map){
map.put("scope1","map");
System.out.println(map);
return "ok";
}
5.使用ModelMap向request域对象共享数据
使用ModelMap参数
@RequestMapping("/testModelMap")
public String modelmap(ModelMap modelMap){
modelMap.addAttribute("scope1","modelmap");
System.out.println(modelMap);
return "ok";
}
6.map , model ,modelmap的关系
起本质调用的都是BindingAwareaModelMap
以上所有都是最后封装到ModelAndView中
7.向session域共享数据
使用HttpSession的参数
@RequestMapping("/testSession")
public String session(HttpSession session){
session.setAttribute("scope1","session");
return "ok";
}
浏览器关闭,session关闭。用于用户
@SessionAttributes("user")
注解方式声明
8.向application域共享数据
使用session参数 创建 ServletContext 。调用session的GetServletContext()方法
@RequestMapping("/testapplication")
public String Application(HttpSession session){
ServletContext application=session.getServletContext();
application.setAttribute("scope1","application" );
return "ok";}
服务器关闭 application关闭 ,用于服务器 ,相对于全局变量
6.SpirngMVC中视图
SpringMVC中的视图是View接口,视图作用渲染数据,将Model中数据给用户,默认转发视图和重定向视图
当使用jstl依赖,视图自动转化为jstlView
视图没有任何前缀时候,用ThymeleafView解析
1.ThymeleafView
@RequestMapping("/testThymeleafview")
public String testThymeleafView(){
return "ok";
}
2.转发视图
默认转发视图是InternalResourceView,会讲forward:去掉,剩余部分最终通过转发请求跳转
在视图前加上forward:开始
处理请求 不能用于网页地址访问
@RequestMapping("/testForward")
public String testForward() {
return "forward:/testThymeleafview";
}
3.重定向视图
默认的重定向RedirectView,以"redirect"为前缀创建RedirectView视图实现跳转
重定向不能使用请求域的对象,重定向服务器请求响应2次
重定向可以跨域 请求不能跨域
重定向不能访问web-info下的内容
@RequestMapping("/testredirect")
public String testRedirect() {
return "redirect:/testThymeleafview" ;
}
4.视图控制器view-controller
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称,可以将处理器方法使用view-controller标签使用
直接在配置文件中配置
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
当没有任何的请求处理时候可以使用,使用后所有请求映射处理失效
解决方法:在配置文件中后加上
<!-- 开启mvc注解驱动-->
<mvc:annotation-driven/>
7.RESTFul
1.RESTFul简历
REST:Representational State Transfer 表现呈资源状态转移
a>资源
资源是一种看待服务器的方式
b>资源的表述
c>状态转移
2.RESTFul的实现
使用四个表示操作的方式的动词:GET,POST,PUT,DELETE
分别用来 获取资源,新建资源,更新资源,删除资源
REST风格提出URL使用斜杠分开,不使用键值对来请求参数
3.使用HiddenHttpMethodFileter过滤器
<!-- 配置HiddenHttpMethodFilter-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
put 和delete的请求必须是post的请求 所以需要写一个隐藏域
<form th:action="@{/user}" method="post">
<input type="hidden" name="_method" value="put">
用户名字 <input type="text" name="username"> <br>
密码 <input type="password" name="password"> <br>
<button type="submit ">修改</button>
</form>
!!!注意此过滤器必须在处理编码的过滤器之后
4.a标签中使用put和delete请求
在配置中添加 使得可以访问静态资源
因为工程的设置和服务器中的冲突,就近原则会使用前端控制器dispathservlet访问所有资源,所以需要修改用使用该配置default访问资源。但是如果不用mvc注解驱动则所有都是静态访问资源 mvc:annotation-driven/
方式:1.使用vue获取a标签的点击事件2.获取put或者删除的表单3.获取a表单的访问路径4.提交表单5.将a标签的事件禁止
JSON
轻量级的数据交换格式,独立编程语言
[ ]JSON数组,{ “name”:“666” }JSON对象
Ajax:
通过XMLHttpRequest对象
open方法,send方法
Ajax跟Jquery不同
@RequestBody接受返回的json字符再转为对象
文件上传:
2中方式表单和ajax:使用了MultipartFile来获取上传的文件
ajax通过formData不需要使用@requestBody
ajax实例代码
<form id="myfrom" method="post" enctype="multipart/form-data">
描述:<input type="text" name="title2" id="title2">
文件 :<input type="file" name="myfile" accept="image/*" id="myfile">
<button type="submit">提交</button>
</form>
<script src="${pageContext.request.contextPath}/static/js/jquerymin.js"></script>
<script>
$(document).ready(function() {
$('#myfrom').submit(function (event){
console.info("no")
event.preventDefault();//防止表单正常提交
console.info("ok")
let formDate=new FormData($('#myfrom')[0]);
$.ajax({
url:'${pageContext.request.contextPath}/file/json/doUpload',
type:'post',
processData:false,
contentType:false,
data:formDate,
success:function (rs){
console.info(rs);
},
error: function (xhr, status, error) {
console.error(xhr.responseText);
}
})
})
})
后端
@PostMapping("/json/doUpload")
@ResponseBody
public Result down2(MultipartFile myfile, String title2,
HttpServletRequest request) throws IOException {
//指定文件上传到服务器位置
String realPath= request.getServletContext().getRealPath("uploadfiles");
//上传指定文件夹
//文件类型
// String filetype =file.getOriginalFilename().substring(file.getOriginalFilename().indexOf("."));
//为文件名取名字使用UUID防止文件名重复
String fileName = UUID.randomUUID().toString()+"-"+myfile.getOriginalFilename();
System.out.println(realPath);
//跳转显示
request.setAttribute("fileName",fileName);
request.setAttribute("title2",title2);
File targetFile = new File(realPath,fileName);
if(!targetFile.exists()){
targetFile.mkdirs();
}
myfile.transferTo(targetFile);;
return new Result("上传成功","/uploadfiles/"+fileName);
}
通过表单
<form id="fileForm" action="${pageContext.request.contextPath}/file/doUpload" method="post" enctype="multipart/form-data">
选择文件 <input type="file" name="myfile" />
文件描述 <input type="text" name="description">
<input type="submit" value="上传" />
</form>
@PostMapping("/doUpload")
public String down1(@RequestParam("myfile") MultipartFile file, String description,
HttpServletRequest request) throws IOException {
//指定文件上传到服务器位置
String realPath= request.getServletContext().getRealPath("uploadfiles");
//上传指定文件夹
String uploadDirectory = "D:/新建文件夹/email/Sping-mybatis/src/main/webapp/file/sql";
System.out.println(realPath);
//为文件名取名字使用UUID防止文件名重复
String fileName = UUID.randomUUID().toString()+"-"+file.getOriginalFilename();
System.out.println(fileName);
//跳转显示
request.setAttribute("fileName",fileName);
request.setAttribute("description",description);
File targetFile = new File(realPath,fileName);
if(!targetFile.exists()){
targetFile.mkdirs();
}
file.transferTo(targetFile);;
return "file_success";
}
文件下载:
1.通过a标签直接访问路径
2.通过代码
使用ResponseEntity,表示响应编码,响应头,响应内容
//下载文件,通过地址栏传入文件名字
@RequestMapping("/down/{filename:.+}")
public ResponseEntity<byte[]> down(@PathVariable("filename") String file, HttpServletRequest request ) throws IOException {
//获取下载的字节流
ServletContext context = request.getServletContext();
InputStream in = context.getResourceAsStream("/uploadfiles/"+file);
//设置响应内容,将文件转为响应字节数组
byte[] body=new byte[in.available()];
in.read(body);
//设置响应头,激活下载
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition","attachment;filename="+ URLEncoder.encode(file,"UTF-8"));
HttpStatus statusCode=HttpStatus.OK;
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(body,headers,statusCode);
return response;
}
拦截器:
是一种动态拦截Controller方法的对象,可以指定在方法调用前或者调用之后。
拦截器类似于Filter过滤器,但是技术和拦截内容不同。Filter采用Servlet技术,拦截器采用Spirngmvc技术,Filter会对所有请求拦截,拦截器只针对Spirng MVC请求拦截
方法一:实现HandlerInterceptor接口拦截器
方法二:继承HandlerInterceptorAdapter拦截器(基本弃用)
创建一个拦截器
!!必须标记为组件交给spring管理
@Component
public class mytestInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("开始前");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("结束后");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
System.out.println("完成后");
}
}
添加拦截器2种方法
1.继承WebMvcConfigurationSupport
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books" );
}
}
2.简化,实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
@Configuration
@EnableWebMvc
@ComponentScan({"edcu.ry.controller","edcu.ry.interceptor"})
public class Springmvc implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Bean
public RequestMappingHandlerMapping create() {
return new RequestMappingHandlerMapping();
}
@Bean
public RequestMappingHandlerAdapter create1(FastJsonHttpMessageConverter fastJsonHttpMessageConverter) {
RequestMappingHandlerAdapter a = new RequestMappingHandlerAdapter();
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(fastJsonHttpMessageConverter);
a.setMessageConverters(converters);
return a;
}
@Bean
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter f = new FastJsonHttpMessageConverter();
List<MediaType> l = new ArrayList<>();
l.add(MediaType.APPLICATION_JSON);
l.add(MediaType.TEXT_HTML);
f.setSupportedMediaTypes(l);
return f;
}
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/com/");
resolver.setSuffix(".jsp");
return resolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Bean(name = "multipartResolver")
public CommonsMultipartResolver commonsMultipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("UTF-8");
resolver.setMaxUploadSize(5242880); // 5MB
//设置缓存 文件<4M不会缓存
resolver.setMaxInMemorySize(40960);
//延迟文件解析
resolver.setResolveLazily(true);
return resolver;
}
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置多拦截器
//拦截/json的请求
//但放行/json/test2
registry.addInterceptor(projectInterceptor).addPathPatterns("/json/*").excludePathPatterns("/json/test2");
;
}
}
全局异常处理
1.简单异常处理器
//简单异常处理器
@Bean
public SimpleMappingExceptionResolver Exception(){
SimpleMappingExceptionResolver smer=new SimpleMappingExceptionResolver();
//设置默认异常处理页面
smer.setDefaultErrorView("error");
//定义异常处理页面来获取异常变量
smer.setExceptionAttribute("ex");
//定义需要特殊处理的异常
Properties mappings=new Properties();
mappings.put("java.lang.RuntimeException","error");
smer.setExceptionMappings(mappings);
return smer;
}
2.继承HandlerExceptionResolver
3.注解@ControllerAdvice +@ExceptionHandler
知识点1:@RestControllerAdvice(包括@Controller,@ResponseBody)
名称 | @RestControllerAdvice |
---|---|
类型 | 类注解 |
位置 | Rest风格开发的控制器增强类定义上方 |
作用 | 为Rest风格开发的控制器类做增强 |
知识点2:@ExceptionHandler
名称 | @ExceptionHandler |
---|---|
类型 | 方法注解 |
位置 | 专用于异常处理的控制器方法上方 |
作用 | 设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行 |
**说明:**此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常
@ResponseStatus(HttpStatus.ok)
在方法之上可以返回状态码
异常处理跳转视图,需要@Component
@ControllerAdvice
@Component
public class exception {
@ExceptionHandler(Exception.class)
public String handleException(Exception e) {
return "error";
}
异常处理自定义异常
需要手动抛出自定义异常
@ExceptionHandler(systemException.class)
@ResponseBody
public systemException H(systemException a){
a.setCode(404);
return a;
}
@ResponseBody
@RequestMapping("/test2")
public US toJson2() throws systemException {
try {
int a=1/0;
}catch (Exception e){
throw new systemException(404,e.getMessage());
}
US u=new US();
u.setName("1");
u.setPhone("66666");
return u;
}
数据校验
JSR-303为Bean验证定义了元数据模型和API,替代了if-else
依赖:validation-api,hibernate-validator
再方法上直接使用注解数据验证,需要在方法上加上
通常在类中加上注解验证,在使用类时需要添加@Valid或者,@Validated
@Valid支持嵌套,@Validated不支持嵌套
@Validated的分组验证
1.定义2个接口add.class和delete.class
2.先在实体类中定义
@NotNull(message ="当删除和添加时候不为空,名字不为空",groups = {add.class, delete.class})
private String name;
@NotBlank(message = "当添加时候,电话不为空",groups = {add.class})
private String phone;
@Max(value = 100,message = "当删除时候age限制最大值",groups = {delete.class})
private int age;
2.需要使用那个分组就选择
@RequestMapping("/get6")
public String to(@RequestBody @Validated({add.class}) US a, BindingResult bindingResult){
System.out.println(a.getName()+"0"+a.getPhone()+a.getAge());
// 获取验证错误信息
String errorMessage = bindingResult.toString();
System.out.println("Validation error: " + errorMessage);
return "ok";
}
格式化数据
528701256)]
再方法上直接使用注解数据验证,需要在方法上加上
通常在类中加上注解验证,在使用类时需要添加@Valid或者,@Validated
[外链图片转存中…(img-Fjjzpbb1-1704528701256)]
[外链图片转存中…(img-yvCsXLUg-1704528701257)]
@Valid支持嵌套,@Validated不支持嵌套
@Validated的分组验证
1.定义2个接口add.class和delete.class
2.先在实体类中定义
@NotNull(message ="当删除和添加时候不为空,名字不为空",groups = {add.class, delete.class})
private String name;
@NotBlank(message = "当添加时候,电话不为空",groups = {add.class})
private String phone;
@Max(value = 100,message = "当删除时候age限制最大值",groups = {delete.class})
private int age;
2.需要使用那个分组就选择
@RequestMapping("/get6")
public String to(@RequestBody @Validated({add.class}) US a, BindingResult bindingResult){
System.out.println(a.getName()+"0"+a.getPhone()+a.getAge());
// 获取验证错误信息
String errorMessage = bindingResult.toString();
System.out.println("Validation error: " + errorMessage);
return "ok";
}
格式化数据
[外链图片转存中…(img-C3WIqYco-1704528701257)]
[外链图片转存中…(img-dX5fD2Nq-1704528701257)]