简介
本文用示例说明SpringBoot里几个注解的用法,包括:@InitBinder/@ModelAttribute/@SessionAttributes。
@InitBinder
使用
见:SpringMVC/SpringBoot--全局异常处理/全局响应/全局请求--实例_IT利刃出鞘的博客
原理/总结
加了前缀为何能绑定上
1、ModelAttributeMethodProcessor#resolveArgument里依赖attribute = createAttribute(name, parameter, binderFactory, webRequest)方法完成数据的封装、转换
2、createAttribute先request.getParameter(attributeName)看请求域里是否有值(此处为null),若木有就反射创建一个空实例,回到resolveArgument方法。
3、继续利用WebDataBinder来完成对这个空对象的数据值绑定,这个时候这些FieldDefaultPrefix就起作用了。执行方法是:bindRequestParameters(binder, webRequest),实际上是((WebRequestDataBinder) binder).bind(request);。对于bind方法的原理,就不陌生了~
4、完成Model数据的封装后,再进行@Valid校验...
@InitBinder总结
- @InitBinder标注的方法执行是多次的,一次请求来就执行一次
- Controller实例中的所有@InitBinder只对当前所在的Controller有效
- @InitBinder的value属性控制的是模型Model里的key,而不是方法名(不写代表对所有的生效)
- @InitBinder标注的方法不能有返回值(只能是void或者returnValue=null)
- @InitBinder对@RequestBody这种基于消息转换器的请求参数无效
因为@InitBinder它用于初始化DataBinder数据绑定、类型转换等功能,而@RequestBody它的数据解析、转换时消息转换器来完成的,所以即使你自定义了属性编辑器,对它是不生效的(它的WebDataBinder只用于数据校验,不用于数据绑定和数据转换。它的数据绑定转换若是json,一般都是交给了jackson来完成的)。
只有AbstractNamedValueMethodArgumentResolver才会调用binder.convertIfNecessary进行数据转换,从而属性编辑器才会生效
@ModelAttribute
作用
ModelAttribute可以应用在方法参数上或方法上,它的作用主要是当注解在方法参数上时会将注解的参数对象添加到Model中。若被@ModelAttribute注释的方法不是请求方法,则此方法会在此controller每个方法执行前被执行。
使用场景
当@ModelAttribute注解用于方法时,与其处于同一个处理类的所有请求方法执行前都会执行一次此方法,这可能并不是我们想要的,我们使用更多的是将其应用在请求方法的参数上。
@ModelAttribute的一部分功能与@RequestParam注解是一致的,只不过@RequestParam用于绑定单个参数值,而@ModelAttribute注解可以绑定所有名称匹配的,此外它自动将绑定后的数据添加到模型中。
实例:用在方法上获得任意实体
实体类
package com.example.entity;
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private Integer age;
}
控制器
package com.example.controller;
import com.example.entity.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/test1")
public User test1(User user) {
System.out.println("HelloController.test1");
return user;
}
}
@ModelAttribute类
package com.example.advice;
import com.example.entity.User;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class GlobalControllerAdvice {
@ModelAttribute
public User authenticationUser(HttpServletRequest httpServletRequest, User user) {
System.out.println("GlobalControllerAdvice.authenticationUser");
System.out.println("URL:" + httpServletRequest.getRequestURL());
if (user.getName() != null && "Tony".equals(user.getName())) {
user.setAge(20);
return user;
}
throw new RuntimeException("用户名错误");
}
}
测试结果
postman访问:http://localhost:8080/hello/test1?name=Tony
后端结果
GlobalControllerAdvice.authenticationUser
URL:http://localhost:8080/hello/test1
HelloController.test1
postman结果
{
"id": null,
"name": "Tony",
"age": 20
}
postman访问:http://localhost:8080/hello/test1?name=Stark
后端结果
GlobalControllerAdvice.authenticationUser
URL:http://localhost:8080/hello/test1
2020-09-05 18:17:41.669 ERROR 4240 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 用户名错误] with root cause
java.lang.RuntimeException: 用户名错误
at com.example.advice.GlobalControllerAdvice.authenticationUser(GlobalControllerAdvice.java:19) ~[classes/:na]
postman结果
HTTP Status 500 – Internal Server Error
实例:用在参数上获得实体
@ModelAttribute注释方法的一个参数表示应从模型model中取得。若在model中未找到,那么这个参数将先被实例化后加入到model中。若在model中找到,则请求参数名称和model属性字段若相匹配就会自动填充。这个机制对于表单提交数据绑定到对象属性上很有效。
后端
package com.example.controller;
import com.example.entity.User;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
// 方法1:通过返回值的方式默认地将添加一个属性
// 属性名没有被显式指定的时:框架将根据属性的类型给予一个默认名称
// 例如:本例返回一个 User 类型的对象,则默认的属性名为"user"
// 你可以通过设置 @ModelAttribute 注解的值来改变默认值 @ModelAttribute("myUser")
@ModelAttribute("user")
public User addUser(String name) {
System.out.println("addUser without model");
User user = new User();
user.setName(name);
return user;
}
@RequestMapping("/test")
public User test(@ModelAttribute("user")User user) {
user.setAge(22);
return user;
}
}
前端
访问:http://localhost:8080/test?name=Tony
响应:
{
"id": null,
"name": "Tony",
"age": 22
}
实例:用在方法上设置属性
有两种类型的@ModelAttribute方法。一种是:只加入一个属性,用方法的返回类型隐含表示。另一种是:方法接受一个Model类型的参数,这个model可以加入任意多个model属性。
后端
package com.example.controller;
import com.example.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class HelloController {
// 方法1:通过返回值的方式默认地将添加一个属性
// 属性名没有被显式指定的时:框架将根据属性的类型给予一个默认名称
// 例如:本例返回一个 User 类型的对象,则默认的属性名为"user"
// 你可以通过设置 @ModelAttribute 注解的值来改变默认值 @ModelAttribute("myUser")
@ModelAttribute
public User addUser(String name) {
System.out.println("addUser without model");
User user = new User();
user.setName(name);
return user;
}
// 方法2:方法接收一个 Model 对象,然后可以向其中添加任意数量的属性
@ModelAttribute
public void addUser(String name, Model model) {
System.out.println("addUser with model");
model.addAttribute("name", "Tony");
model.addAttribute("age", 20);
}
@RequestMapping("/test")
public ModelAndView test() {
return new ModelAndView("hello");
}
}
前端
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
账户名称:${account.name}<br/>
年龄:${account.age}<br/>
number:${number}<br/>
other:${other}<br/>
</body>
</html>
@ModelAttribute和@RequestMapping同时注释一个方法
后端
@Controller
@RequestMapping(value="/test")
public class TestController {
@RequestMapping(value = "/helloWorld")
@ModelAttribute("attributeName")
public String helloWorld() {
return "hi";
}
}
这时这个方法的返回值并不是表示一个视图名称,而是model属性的值,视图名称由RequestToViewNameTranslator根据请求"/helloWorld"转换为helloWorld。Model属性名称由@ModelAttribute(value="")指定,相当于在request中封装了key=attributeName,value=hi。
前端Jsp页面:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>helloWorld</title>
</head>
<body>
The attributeValue is: ${attributeName}
</body>
</html>
@SessionAttributes
简介
在默认情况下,ModelMap中的属性作用域是request级别,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享ModelMap中的属性,必须将其属性转存到session 中,这样 ModelMap 的属性才可以被跨请求访问。Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注 @SessionAttributes 注解来实现的。
后端:Controller
@Controller
@RequestMapping("/anno")
@SessionAttributes(value={"msg"}) // 把Mapmodel中名字为msg的属性存入到session属性列表中
public class AnnoController {
@RequestMapping(value="/testSessionAttributes")
public String testSessionAttributes(Model model){
System.out.println("testSessionAttributes...");
// 底层会存储到request域对象中
model.addAttribute("msg","testSessionAttributes");
return "success";
}
@RequestMapping(value="/getSessionAttributes")
public String getSessionAttributes(ModelMap modelMap){
System.out.println("getSessionAttributes...");
String msg = (String) modelMap.get("msg");
System.out.println(msg);
return "success";
}
@RequestMapping(value="/delSessionAttributes")
public String delSessionAttributes(SessionStatus status){
status.setComplete();//删除session域中的存入的数据
return "success";
}
}
前端:success.html
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>入门成功</h3>
${ msg }
${sessionScope}
</body>
</html>
测试1
访问:http://localhost:8080/anno/testSessionAttributes/
前端
测试2
访问:http://localhost:8080/anno/getSessionAttributes/
后端打印
getSessionAttributes...
testSessionAttributes
测试3
访问:http://localhost:8080/anno/getSessionAttributes/
测试4
再次访问:http://localhost:8080/anno/getSessionAttributes/
后端打印
getSessionAttributes...
null
前端
其他网址
从原理层面掌握@InitBinder的使用【享学Spring MVC】