@InitBinder的value属性的作用
获取你可能还不知道,它还有个value
属性呢,并且还是数组
public @interface InitBinder { // 用于限定次注解标注的方法作用于哪个模型key上 String[] value() default {}; }
说人话:若指定了value值,那么只有方法参数名(或者模型名)匹配上了此注解方法才会执行(若不指定,都执行)。
@Controller @RequestMapping public class HelloController { @InitBinder({"param", "user"}) public void initBinder(WebDataBinder binder, HttpServletRequest request) { System.out.println("当前key:" + binder.getObjectName()); } @ResponseBody @GetMapping("/test/initbinder") public String testInitBinder(String param, String date, @ModelAttribute("user") User user, @ModelAttribute("person") Person person) { return param + ":" + date; } }
请求:/test/initbinder?param=fsx&date=2019&user.name=demoUser
,控制台打印:
当前key:param 当前key:user
从打印结果中很清楚的看出了value属性的作用~
需要说明一点:虽然此处有key是user.name,但是User对象可是不会封装到此值的(因为request.getParameter('user')没这个key嘛~)。如何解决???需要绑定前缀,原理可参考这里
其它应用场景
上面例举的场景是此注解最为常用的场景,大家务必掌握。它还有一些奇淫技巧的使用,心有余力的小伙伴不妨也可以消化消化:
若你一次提交需要提交两个"模型"数据,并且它们有重名的属性。形如下面例子:
@Controller @RequestMapping public class HelloController { @Getter @Setter @ToString public static class User { private String id; private String name; } @Getter @Setter @ToString public static class Addr { private String id; private String name; } @InitBinder("user") public void initBinderUser(WebDataBinder binder) { binder.setFieldDefaultPrefix("user."); } @InitBinder("addr") public void initBinderAddr(WebDataBinder binder) { binder.setFieldDefaultPrefix("addr."); } @ResponseBody @GetMapping("/test/initbinder") public String testInitBinder(@ModelAttribute("user") User user, @ModelAttribute("addr") Addr addr) { return user + ":" + addr; } }
请求:/test/initbinder?user.id=1&user.name=demoUser&addr.id=10&addr.name=北京市海淀区,结果为:HelloController.User(id=1, name=demoUser):HelloController.Addr(id=10, name=北京市海淀区)
至于加了前缀为何能绑定上,这里简要说说:
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校验…
参考解析类:ModelAttributeMethodProcessor对参数部分的处理
总结
本文花大篇幅从原理层面总结了@InitBinder这个注解的使用,虽然此注解在当下的环境中出镜率并不是太高,但我还是期望小伙伴能理解它,特别是我本文举例说明的例子的场景一定能做到运用自如。
最后,此注解的使用的注意事项我把它总结如下,供各位使用过程中参考:
- @InitBinder标注的方法执行是多次的,一次请求来就执行一次(第一次惩罚)
- Controller实例中的所有@InitBinder只对当前所在的Controller有效
- @InitBinder的value属性控制的是模型Model里的key,而不是方法名(不写代表对所有的生效)
- @InitBinder标注的方法不能有返回值(只能是void或者returnValue=null)
- @InitBinder对@RequestBody这种基于消息转换器的请求参数无效
- 1. 因为@InitBinder它用于初始化DataBinder数据绑定、类型转换等功能,而@RequestBody它的数据解析、转换时消息转换器来完成的,所以即使你自定义了属性编辑器,对它是不生效的(它的WebDataBinder只用于数据校验,不用于数据绑定和数据转换。它的数据绑定转换若是json,一般都是交给了jackson来完成的)
- 只有AbstractNamedValueMethodArgumentResolver才会调用binder.convertIfNecessary进行数据转换,从而属性编辑器才会生效