从原理层面掌握@InitBinder的使用【享学Spring MVC】 (2)

binderMethods是通过构造函数进来的,它表示和本次请求有关的所有的标注有@InitBinder的方法,所以需要了解它的实例是如何被创建的,那就是接下来这步。

3、ServletRequestDataBinderFactory的创建
任何一个请求进来,最终交给了HandlerAdapter.handle()方法去处理,它的创建流程如下:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean { ... @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ... // 处理请求,最终其实就是执行控制器的方法,得到一个ModelAndView mav = invokeHandlerMethod(request, response, handlerMethod); ... } // 执行控制器的方法,挺复杂的。但本文我只关心WebDataBinderFactory的创建,方法第一句便是 @Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ... } // 创建一个WebDataBinderFactory // Global methods first(放在前面最先执行) 然后再执行本类自己的 private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { // handlerType:方法所在的类(控制器方法所在的类,也就是xxxController) // 由此可见,此注解的作用范围是类级别的。会用此作为key来缓存 Class<?> handlerType = handlerMethod.getBeanType(); Set<Method> methods = this.initBinderCache.get(handlerType); if (methods == null) { // 缓存没命中,就去selectMethods找到所有标注有@InitBinder的方法们~~~~ methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS); this.initBinderCache.put(handlerType, methods); // 缓存起来 } // 此处注意:Method最终都被包装成了InvocableHandlerMethod,从而具有执行的能力 List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>(); // 上面找了本类的,现在开始看看全局里有木有@InitBinder // Global methods first(先把全局的放进去,再放个性化的~~~~ 所以小细节:有覆盖的效果哟~~~) // initBinderAdviceCache它是一个缓存LinkedHashMap(有序哦~~~),缓存着作用于全局的类。 // 如@ControllerAdvice,注意和`RequestBodyAdvice`、`ResponseBodyAdvice`区分开来 // methodSet:说明一个类里面是可以定义N多个标注有@InitBinder的方法~~~~~ this.initBinderAdviceCache.forEach((clazz, methodSet) -> { // 简单的说就是`RestControllerAdvice`它可以指定:basePackages之类的属性,看本类是否能被扫描到吧~~~~ if (clazz.isApplicableToBeanType(handlerType)) { // 这个resolveBean() 有点意思:它持有的Bean若是个BeanName的话,会getBean()一下的 // 大多数情况下都是BeanName,这在@ControllerAdvice的初始化时会讲~~~ Object bean = clazz.resolveBean(); for (Method method : methodSet) { // createInitBinderMethod:把Method适配为可执行的InvocableHandlerMethod // 特点是把本类的HandlerMethodArgumentResolverComposite传进去了 // 当然还有DataBinderFactory和ParameterNameDiscoverer等 initBinderMethods.add(createInitBinderMethod(bean, method)); } } }); // 后一步:再条件标注有@InitBinder的方法 for (Method method : methods) { Object bean = handlerMethod.getBean(); initBinderMethods.add(createInitBinderMethod(bean, method)); } // protected方法,就一句代码:new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer()) return createDataBinderFactory(initBinderMethods); } ... }

到这里,整个@InitBinder的解析过程就算可以全部理解了。关于这个过程,我有如下几点想说:

对于binderMethods每次请求过来都会新new一个(具有第一次惩罚效果),它既可以来自于全局(Advice),也可以来自于Controller本类

倘若Controller上的和Advice上标注有次注解的方法名一毛一样,也是不会覆盖的(因为类不一样)

关于注解有@InitBinder的方法的执行,它和执行控制器方法差不多,都是调用了InvocableHandlerMethod#invokeForRequest方法,因此可以自行类比

目前方法执行的核心,无非就是对参数的解析、封装,也就是对HandlerMethodArgumentResolver的理解。强烈推荐你可以参考 这个系列的所有文章~

有了这些基础理论的支撑,接下来当然就是它的使用Demo Show了

@InitBinder的使用案例

我抛出两个需求,借助@InitBinder来实现:

请求进来的所有字符串都trim一下

yyyy-MM-dd这种格式的字符串能直接用Date类型接收(不用先用String接收再自己转换,不优雅)

为了实现如上两个需求,我需要先自定义两个属性编辑器:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyjxyj.html