引出
写控制层的时候,我们发现对于一类方法,经常要将一些参数从Session解析,然后固定装入某个pojo中。比如不论创建什么记录,都要将当前用户作为创建人写入相应DO的字段,而这些字段名往往都是一样的。这样势必会有重复编码,那有没有可以偷懒的办法呢?
参数解析器
springmvc提供了HandlerMethodArgumentResolver接口作为定义参数解析策略的入口:
1 | public interface HandlerMethodArgumentResolver { |
对于上述问题,一般而言,可以在控制层方法入参多写一个SessionBean类的参数,然后写一个参数解析器,将Session中的数据装载进去。最后在方法体中进行Bean之间的属性拷贝即可。
1 | public class SessionBeanArgumentResolver implements HandlerMethodArgumentResolver { |
1 | ("/foo") |
不要忘记注册该参数解析器,单纯@Component注解是无用的。注册方法多种多样,列一个java注解式的:
1 |
|
至此已经省事不少,但看到每个方法体中重复的copyProperties,强迫症又犯。
略加探索,确实还有更隐蔽的做法。
@ModelAttribute 参数解析器
我们想直接对原有的比如FooQuery类型解析动刀,那就要清楚,spring默认的参数解析器里,哪个是干组装POJO类型的活的。
你或许想到对于每个Controller,可以定义@InitBinder标识的方法,在方法里注册自定义的PropertyEditor,似乎可以实现这个需求:
1 |
|
但你会发现,到binder这一层范围太窄,PropertyEditor完成的是String到特定类型数据的转换,WebDataBinder完成的是表单到JavaBean属性的绑定,如果前端传来的是bean的json字串,或许还可以一用。
我们需要的是更上一层的定制化参数解析。这就引出了ServletModelAttributeMethodProcessor。
该类命名为Processor而不是ArgumentResolver是因为其继承ModelAttributeMethodProcessor, 该类又同时实现了HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler两个接口。我们只关注参数解析那一部分。
- 先看其支持的类型:
1 |
|
其支持@ModelAttribute标注的入参(这一点基本上没人用),以及默认条件下(annotationNotRequired默认设置为true)的非’简单’参数。
何为简单参数?
1 | public static boolean isSimpleProperty(Class<?> clazz) { |
总结来说:基本类型及其包装类、枚举、字串、日期、URI、URL、Local、Class类型和上述类型的数组。
不是上述类型都会归该解析器管。自然,自定义的pojo也是。
- 解析参数的过程
简单而言分为两部:创建对象,属性绑定。
1 | // 不可重写 |
该方法为final,不可扩展。
1创建对象
其父类直接用默认无参构造器构造一个对象,该方法可继承扩展,到了
ServletModelAttributeMethodProcessor,会先判断是否有同名参数,若有,试图用ConversionService转化成bean,若无,就用父类的方法。
1 | // 不可重写 |
有趣的是,这里用DataBinder的ConversionService,前面不是有PropertyEditor了吗,感觉干的是类似的事情。
1 | // 一个DataBinder同时实现了属性编辑器注册(propertyEditorRegitry) 和 类型转换器 |
仔细翻看源码,粗略理解:
a. DataBinder实现TypeConverter和PropertyEditorRegistry的方式,是代理了SimpleTypeConverter。
b. SimpleTypeConverter的父类PropertyEditorRegistrySupport实现了属性编辑器注册。
c. PropertyEditorRegistrySupport内含 ConversionService与一系列PropertyEditor(默认和自定义)。
d. converter是spring自定义的一套数据转换逻辑,关于Converter 和 PropertiyEditor的区别可以参考博文https://blog.csdn.net/pentiumchen/article/details/44066173
2.2 参数绑定
直接用binder,不过与其父类不同,ServletModelAttributeMethodProcessor使用的是ServletRequestDataBinder。
- 问题实现
回到问题上,既然这些属性都是公共的,那就定义一个基类(或接口)作为父类,抽出公共部分的字段:
1 |
|
然后继承扩展Processor:
1 | public class SessionBaseBeanArgumentResolver extends ModelAttributeMethodProcessor { |
因为 ServletModelAttributeMethodProcessor难于扩展,故使用其父类。
这样便实现了要求。