- 浏览: 236645 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
shuhucy:
必须赞啊,源码理解的很深,解决一个困扰两天的问题
Spring AOP源码分析(八)SpringAOP要注意的地方 -
sealinesu:
精彩
Spring事务源码分析(一)Spring事务入门 -
whlt20090509:
"WEB-INF/view目录下有一个简单的hell ...
SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门 -
hai0378:
兄台 算我一个,最近在研究dubbo motan 及 zk ...
ZooKeeper源码研究寻求小伙伴 -
zpkbtzmbw:
看懂了,原理
SpringMVC源码总结(五)Tomcat的URIEncoding、useBodyEncodingForURI和CharacterEncodingFilter
SpringMVC源码总结(十)自定义HandlerMethodArgumentResolver
- 博客分类:
- SpringMVC源码分析
上一篇文章介绍了HandlerMethodArgumentResolver的来龙去脉,这篇就要说说自定义HandlerMethodArgumentResolver来解决我们的需求,本文提供了四种解决方案。
需求,有一个Teacher类和Student类,他们都有属性name和age:
前端form表单为:
希望后台能这样接收这样的参数:
解决方案有很多:
方案一:
新建一个类,融合这两个类。如
在后台这样接收参数:
即使用@RequestBody来接受这样的参数。下面还要说说这样做的两个问题,你或许可以试猜一下:
使用form表单来进行提交,运行:
问题一:
首先会遇到415 Unsupported Media Type,如下:
我们的form表单默认是以application/x-www-form-urlencoded方式提交的,而@RequestBody又采用的是RequestResponseBodyMethodProcessor这个HandlerMethodArgumentResolver,RequestResponseBodyMethodProcessor内部的处理原理就是用一系列的HttpMessageConverter来进行数据的转换的。这时候就需要找到支持MediaType类型为application/x-www-form-urlencoded和数据的类型为Father的HttpMessageConverter,当然就找不到了。我们本意是想让MappingJackson2HttpMessageConverter来处理的,但是它仅仅支持的MediaType类型为:
即application/json或者application/*+json。所以此时就需要我们更改提交的content-type。然而form表单目前的仅仅支持三种content-type即application/x-www-form-urlencoded、multipart/form-data、text/plain。所以我们需要更换成ajax提交,如下:
此时又有一个问题,teacher.name这样的形式并不能正确解析成Father。仍然需要变换格式:
这样的json形式才能够被正确解析出来。
所以说方案一有很多的地方要修改,并不是那么优雅。
方案二:
我们仍然使用form表单提交:
服务器端的变化为:
大体上来说就是在解析每个参数时加上前缀限制。下面就要看看这个过程的源码分析:
到底选择哪个HandlerMethodArgumentResolver来解析我们的参数呢?它最终会选择ServletModelAttributeMethodProcessor,看下它的判断条件:
这里说明了它可以支持两种情况,一种情况为含有@ModelAttribute注解的参数,另一种情况就是虽然不含@ModelAttribute注解,但它并不是简单类型,如常用的String、Date等。你会发现spring会注册两个ServletModelAttributeMethodProcessor,一个annotationNotRequired为false,另一个为true。这主要是因为调用HandlerMethodArgumentResolver的解析顺序的原因,如果只有一个ServletModelAttributeMethodProcessor,当它判断参数不含@ModelAttribute注解,那它就把参数作为非简单类型来处理,这样的话,后面很多的HandlerMethodArgumentResolver将无法发挥作用。所以annotationNotRequired=true的ServletModelAttributeMethodProcessor是在最后才调用的。
然后再具体看看ServletModelAttributeMethodProcessor的处理过程:
首先就是获取参数名的过程,String name = ModelFactory.getNameForParameter(parameter);具体内容如下:
这里先尝试从@ModelAttribute注解中获取参数名,若没有则根据参数类型来获取参数名
获取类的简单名称如Teacher,然后再进行处理
有了类的简单名称,如果类的简单名称第一个和第二个字母都大写则不进行处理直接返回类的简单名称,否则仅仅将类的第一个大写变成小写。就此获取到了参数名为teacher。
然后就是获取或者创建我们要绑定的Teacher对象。它首先尝试从要返回的model中能否找到属性名为teacher的model,如找不到,就需要去创建一个:
先尝试从request参数中能否找到teacher这一个参数,找到了就进行绑定和转换。未找到,就需要自己来实例化一个Teacher对象,此时并没有绑定相应的参数值。
有个返回的目标,然后就是创建WebDataBinder实现绑定的过程:
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
这一个过程,我们之前已经分析过。就是调度执行一些@InitBinder方法注册一些PropertyEditor。我们继续要来看看initBinder(dataBinder, webRequest);执行了那些@InitBinder方法:
当@InitBinder指定了value值的时候,只有那些value值含有binder.getObjectName()的才会执行,而此时的binder.getObjectName()就是我们辛辛苦苦找出来的参数名teacher。所以本例中@InitBinder("teacher")会执行,而@InitBinder("student")则不会执行。
之后对四个参数 teacher.name='张三'、teacher.age=88、student.name='李四'、student.age=89 通过前缀进行过滤等其他操作实现了参数绑定。此过程不再分析,有兴趣的可以继续研究。
方案三:
使用自定义的HandlerMethodArgumentResolver:
表单提交的内容为:
其中Teacher和Student做了相应的修改,加大了数据的复杂性。如下:
请求的的处理函数为:
经过测试,通过。
自定义了两个东西,一个就是标签MyForm,另一个就是MyHandlerMethodArgumentResolver,并且我们从上一篇文章中知道如何将自定义HandlerMethodArgumentResolver加入HandlerMethodArgumentResolver大军中。如下:
我们来具体分析下这个过程,首先是注解 MyForm:
只有有一个value属性,用来指定from表单的中字段的前缀,若不指定,我将采取类名首字母小写的规则来默认前缀。如@MyForm Teacher a,默认前缀是teacher。
然后就是MyHandlerMethodArgumentResolver,专门用来解析@MyForm注解的:
其实也挺简单的。对于supportsParameter方法就是看看有没有MyForm注解,若有则处理。
重点就在resolveArgument方法上:targetType就是MyForm所修饰的Teacher类或Student类,这里以Teacher为例。首先就是调用Teacher的无参的构造函数创建一个Teacher对象。然后由绑定工厂创建出绑定类,WebDataBinder binder = binderFactory.createBinder(webRequest, null,prefix);这一过程已在方案二中分析过了,就是执行那些符合的@InitBinder方法,这里我们传的值为prefix,即MyForm的value,若没指定就是类名的首字母小写,在这里就是teacher。也就是说那些@InitBinder的value值中含有teacher或者@InitBinder没有指定value值的方法才会被执行。因此我们这里注册的日期转换CustomDateEditor会被注册进去。然后就是执行绑定的过程。这个过程就是利用已注册的PropertyEditor和Converter来进行Field类型的转换。如下分析
遍历它的Field,如String name,fieldType为String。binder.convertIfNecessary(webRequest.getParameter(prefix+"."+fieldName),fieldType, parameter);这里就是把teacher.name参数值转换成fieldType,都是String,所以就不需要转换器。对于Date date,就是把teacher.date参数的字符串值转换成Date类型,然后就用到了我们注册的CustomDateEditor,成功的进行了转换。对于 List<String> love,就是把teacher.love参数的字符串值转换成List集合,使用的是Spring已经注册的StringToArrayConverter,字符串默认是以','分割。
该方案只能进行简单类型的转换(Teacher中field都是些简单类型),还不支持Teacher中包含复杂类型如包含其他属性类。其实也可以做成支持的,就是再稍加改造些,对于Field的处理先判断是否是简单类型,如Address类,若不是则递归调用上面的处理过程即对Address再次遍历Field来实现Address中简单类型的绑定。关键就是执行个递归调用,其他也没什么,有兴趣的可以自行研究。本例中的自定义文件可在后面下载。
方案四:
根据方案二我们其实就可以想到更改下方案二所用到的ServletModelAttributeMethodProcessor,就可以达到我们想要的结果。即如下:
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);在创建出WebDataBinder后,调用下binder.setFieldDefaultPrefix(prefix);就可以大功告成了。然而,我们会看到该方法是final,不可覆盖的,我就复制粘贴了一份,出来,新建了一个自定义的MyServletModelAttributeMethodProcessor以及它对应的注解标签MyServletModelForm,代码如下:
MyServletModelForm内容为:
MyServletModelAttributeMethodProcessor的主要内容为:
处理函数代码为:
并把MyServletModelAttributeMethodProcessor在xml文件中进行配置:
经测试,可以。附件中含有这两个类供下载。
需求,有一个Teacher类和Student类,他们都有属性name和age:
前端form表单为:
<form action="/test/two" method="post" > <input type="text" name="teacher.name" value="张三"> <input type="text" name="teacher.age" value=88> <input type="text" name="student.name" value="李四"> <input type="text" name="student.age" value=89> <input type="submit" value="提交"> </form>
希望后台能这样接收这样的参数:
解决方案有很多:
方案一:
新建一个类,融合这两个类。如
public class Father { private Teacher teacher; private Student student; public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } }
在后台这样接收参数:
@RequestMapping(value="/test/father",method=RequestMethod.POST) @ResponseBody public Map<String,Object> testFather(@RequestBody Father f){ //略 }
即使用@RequestBody来接受这样的参数。下面还要说说这样做的两个问题,你或许可以试猜一下:
使用form表单来进行提交,运行:
问题一:
首先会遇到415 Unsupported Media Type,如下:
我们的form表单默认是以application/x-www-form-urlencoded方式提交的,而@RequestBody又采用的是RequestResponseBodyMethodProcessor这个HandlerMethodArgumentResolver,RequestResponseBodyMethodProcessor内部的处理原理就是用一系列的HttpMessageConverter来进行数据的转换的。这时候就需要找到支持MediaType类型为application/x-www-form-urlencoded和数据的类型为Father的HttpMessageConverter,当然就找不到了。我们本意是想让MappingJackson2HttpMessageConverter来处理的,但是它仅仅支持的MediaType类型为:
public MappingJackson2HttpMessageConverter() { super(new MediaType("application", "json", DEFAULT_CHARSET), new MediaType("application", "*+json", DEFAULT_CHARSET)); }
即application/json或者application/*+json。所以此时就需要我们更改提交的content-type。然而form表单目前的仅仅支持三种content-type即application/x-www-form-urlencoded、multipart/form-data、text/plain。所以我们需要更换成ajax提交,如下:
function postFather1(){ var url='/test/father'; var data={ 'teacher.name':'张三' , 'teacher.age':88 , 'student.name':'李四' , 'student.age':89 , }; $.ajax({ url:url, type:'POST', data:JSON.stringify(data), dataType:'json', contentType:"application/json;charset=utf-8", success:function(result){ } }); }
此时又有一个问题,teacher.name这样的形式并不能正确解析成Father。仍然需要变换格式:
var data={ 'teacher':{ 'name':'张三', 'age':88 }, 'student':{ 'name':'李四', 'age':89 } };
这样的json形式才能够被正确解析出来。
所以说方案一有很多的地方要修改,并不是那么优雅。
方案二:
我们仍然使用form表单提交:
<form action="/test/two" method="post"> <input type="text" name="teacher.name" value="张三"> <input type="text" name="teacher.age" value=88> <input type="text" name="student.name" value="李四"> <input type="text" name="student.age" value=89> <input type="submit" value="提交"> </form>
服务器端的变化为:
@InitBinder("teacher") public void initBinder1(WebDataBinder binder) throws Exception { binder.setFieldDefaultPrefix("teacher."); } @InitBinder("student") public void initBinder2(WebDataBinder binder) throws Exception { binder.setFieldDefaultPrefix("student."); } @RequestMapping(value="/test/two",method=RequestMethod.POST) @ResponseBody public Map<String,Object> testrequestHeader(Teacher a,Student b){ Map<String,Object> map=new HashMap<String,Object>(); map.put("name","lg"); map.put("age",23); map.put("date",new Date()); return map; }
大体上来说就是在解析每个参数时加上前缀限制。下面就要看看这个过程的源码分析:
到底选择哪个HandlerMethodArgumentResolver来解析我们的参数呢?它最终会选择ServletModelAttributeMethodProcessor,看下它的判断条件:
/** * @return true if the parameter is annotated with {@link ModelAttribute} * or in default resolution mode also if it is not a simple type. */ @Override public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(ModelAttribute.class)) { return true; } else if (this.annotationNotRequired) { return !BeanUtils.isSimpleProperty(parameter.getParameterType()); } else { return false; } }
这里说明了它可以支持两种情况,一种情况为含有@ModelAttribute注解的参数,另一种情况就是虽然不含@ModelAttribute注解,但它并不是简单类型,如常用的String、Date等。你会发现spring会注册两个ServletModelAttributeMethodProcessor,一个annotationNotRequired为false,另一个为true。这主要是因为调用HandlerMethodArgumentResolver的解析顺序的原因,如果只有一个ServletModelAttributeMethodProcessor,当它判断参数不含@ModelAttribute注解,那它就把参数作为非简单类型来处理,这样的话,后面很多的HandlerMethodArgumentResolver将无法发挥作用。所以annotationNotRequired=true的ServletModelAttributeMethodProcessor是在最后才调用的。
然后再具体看看ServletModelAttributeMethodProcessor的处理过程:
public final Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception { String name = ModelFactory.getNameForParameter(parameter); Object attribute = (mavContainer.containsAttribute(name)) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request); WebDataBinder binder = binderFactory.createBinder(request, attribute, name); if (binder.getTarget() != null) { bindRequestParameters(binder, request); validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors()) { if (isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = binder.getBindingResult().getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return binder.getTarget(); }
首先就是获取参数名的过程,String name = ModelFactory.getNameForParameter(parameter);具体内容如下:
public static String getNameForParameter(MethodParameter parameter) { ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); String attrName = (annot != null) ? annot.value() : null; return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter); }
这里先尝试从@ModelAttribute注解中获取参数名,若没有则根据参数类型来获取参数名
public static String getVariableNameForParameter(MethodParameter parameter) { Assert.notNull(parameter, "MethodParameter must not be null"); Class<?> valueClass; boolean pluralize = false; if (parameter.getParameterType().isArray()) { valueClass = parameter.getParameterType().getComponentType(); pluralize = true; } else if (Collection.class.isAssignableFrom(parameter.getParameterType())) { valueClass = GenericCollectionTypeResolver.getCollectionParameterType(parameter); if (valueClass == null) { throw new IllegalArgumentException( "Cannot generate variable name for non-typed Collection parameter type"); } pluralize = true; } else { valueClass = parameter.getParameterType(); } String name = ClassUtils.getShortNameAsProperty(valueClass); return (pluralize ? pluralize(name) : name); }
public static String getShortNameAsProperty(Class<?> clazz) { String shortName = ClassUtils.getShortName(clazz); int dotIndex = shortName.lastIndexOf('.'); shortName = (dotIndex != -1 ? shortName.substring(dotIndex + 1) : shortName); return Introspector.decapitalize(shortName); }
获取类的简单名称如Teacher,然后再进行处理
public static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){ return name; } char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); }
有了类的简单名称,如果类的简单名称第一个和第二个字母都大写则不进行处理直接返回类的简单名称,否则仅仅将类的第一个大写变成小写。就此获取到了参数名为teacher。
然后就是获取或者创建我们要绑定的Teacher对象。它首先尝试从要返回的model中能否找到属性名为teacher的model,如找不到,就需要去创建一个:
protected final Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { String value = getRequestValueForAttribute(attributeName, request); if (value != null) { Object attribute = createAttributeFromRequestValue(value, attributeName, parameter, binderFactory, request); if (attribute != null) { return attribute; } } return super.createAttribute(attributeName, parameter, binderFactory, request); }
先尝试从request参数中能否找到teacher这一个参数,找到了就进行绑定和转换。未找到,就需要自己来实例化一个Teacher对象,此时并没有绑定相应的参数值。
有个返回的目标,然后就是创建WebDataBinder实现绑定的过程:
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
public final WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception { WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest); if (this.initializer != null) { this.initializer.initBinder(dataBinder, webRequest); } initBinder(dataBinder, webRequest); return dataBinder; }
这一个过程,我们之前已经分析过。就是调度执行一些@InitBinder方法注册一些PropertyEditor。我们继续要来看看initBinder(dataBinder, webRequest);执行了那些@InitBinder方法:
public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception { for (InvocableHandlerMethod binderMethod : this.binderMethods) { if (isBinderMethodApplicable(binderMethod, binder)) { Object returnValue = binderMethod.invokeForRequest(request, null, binder); if (returnValue != null) { throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod); } } } } protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder binder) { InitBinder annot = initBinderMethod.getMethodAnnotation(InitBinder.class); Collection<String> names = Arrays.asList(annot.value()); return (names.size() == 0 || names.contains(binder.getObjectName())); }
当@InitBinder指定了value值的时候,只有那些value值含有binder.getObjectName()的才会执行,而此时的binder.getObjectName()就是我们辛辛苦苦找出来的参数名teacher。所以本例中@InitBinder("teacher")会执行,而@InitBinder("student")则不会执行。
之后对四个参数 teacher.name='张三'、teacher.age=88、student.name='李四'、student.age=89 通过前缀进行过滤等其他操作实现了参数绑定。此过程不再分析,有兴趣的可以继续研究。
方案三:
使用自定义的HandlerMethodArgumentResolver:
表单提交的内容为:
<form action="/test/two" method="post"> <input type="text" name="teacher.name" value="张三"> <input type="text" name="teacher.age" value=88> <input type="text" name="teacher.date" value="2014---09---04 05:23:00"> <input type="text" name="teacher.love" value="乒乓球,篮球"> <input type="text" name="student.name" value="李四"> <input type="text" name="student.age" value=89> <input type="text" name="student.date" value="2014---09---05 05:23:00"> <input type="text" name="student.love" value="羽毛球,台球"> <input type="submit" value="提交"> </form>
其中Teacher和Student做了相应的修改,加大了数据的复杂性。如下:
public class Teacher { private String name; private int age; private Date date; private List<String> love; public Teacher() { super(); } public Teacher(String name, int age) { super(); this.name = name; this.age = age; } //get set 省略 }
请求的的处理函数为:
@InitBinder public void initBinder(WebDataBinder binder) throws Exception { DateFormat df = new SimpleDateFormat("yyyy---MM---dd HH:mm:ss"); CustomDateEditor dateEditor = new CustomDateEditor(df, true); binder.registerCustomEditor(Date.class, dateEditor); } @RequestMapping(value="/test/two",method=RequestMethod.POST) @ResponseBody public Map<String,Object> testrequestHeader(@MyForm Teacher a,@MyForm Student b){ Map<String,Object> map=new HashMap<String,Object>(); map.put("name","lg"); map.put("age",23); map.put("date",new Date()); return map; }
经过测试,通过。
自定义了两个东西,一个就是标签MyForm,另一个就是MyHandlerMethodArgumentResolver,并且我们从上一篇文章中知道如何将自定义HandlerMethodArgumentResolver加入HandlerMethodArgumentResolver大军中。如下:
<mvc:annotation-driven > <!--其他省略 --> <mvc:argument-resolvers> <bean class="com.lg.mvc.MyHandlerMethodArgumentResolver"/> </mvc:argument-resolvers> </mvc:annotation-driven>
我们来具体分析下这个过程,首先是注解 MyForm:
package com.lg.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyForm { String value() default ""; }
只有有一个value属性,用来指定from表单的中字段的前缀,若不指定,我将采取类名首字母小写的规则来默认前缀。如@MyForm Teacher a,默认前缀是teacher。
然后就是MyHandlerMethodArgumentResolver,专门用来解析@MyForm注解的:
package com.lg.mvc; import java.lang.reflect.Field; import org.springframework.core.MethodParameter; import org.springframework.util.ClassUtils; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import com.lg.annotation.MyForm; public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver{ @Override public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(MyForm.class)) { return true; } return false; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { if (binderFactory==null) { return null; } Class<?> targetType=parameter.getParameterType(); MyForm myForm=parameter.getParameterAnnotation(MyForm.class); String prefix=getprefix(myForm,targetType); Object arg=null; Field[] fields=targetType.getDeclaredFields(); Object target=targetType.newInstance(); WebDataBinder binder = binderFactory.createBinder(webRequest, null,prefix); for(Field field:fields){ field.setAccessible(true); String fieldName=field.getName(); Class<?> fieldType=field.getType(); arg = binder.convertIfNecessary(webRequest.getParameter(prefix+"."+fieldName),fieldType, parameter); field.set(target,arg); } return target; } private String getprefix(MyForm myForm,Class<?> targetType) { String prefix=myForm.value(); if(prefix.equals("")){ prefix=getDefaultClassName(targetType); } return prefix; } private String getDefaultClassName(Class<?> targetType) { return ClassUtils.getShortNameAsProperty(targetType); } }
其实也挺简单的。对于supportsParameter方法就是看看有没有MyForm注解,若有则处理。
重点就在resolveArgument方法上:targetType就是MyForm所修饰的Teacher类或Student类,这里以Teacher为例。首先就是调用Teacher的无参的构造函数创建一个Teacher对象。然后由绑定工厂创建出绑定类,WebDataBinder binder = binderFactory.createBinder(webRequest, null,prefix);这一过程已在方案二中分析过了,就是执行那些符合的@InitBinder方法,这里我们传的值为prefix,即MyForm的value,若没指定就是类名的首字母小写,在这里就是teacher。也就是说那些@InitBinder的value值中含有teacher或者@InitBinder没有指定value值的方法才会被执行。因此我们这里注册的日期转换CustomDateEditor会被注册进去。然后就是执行绑定的过程。这个过程就是利用已注册的PropertyEditor和Converter来进行Field类型的转换。如下分析
遍历它的Field,如String name,fieldType为String。binder.convertIfNecessary(webRequest.getParameter(prefix+"."+fieldName),fieldType, parameter);这里就是把teacher.name参数值转换成fieldType,都是String,所以就不需要转换器。对于Date date,就是把teacher.date参数的字符串值转换成Date类型,然后就用到了我们注册的CustomDateEditor,成功的进行了转换。对于 List<String> love,就是把teacher.love参数的字符串值转换成List集合,使用的是Spring已经注册的StringToArrayConverter,字符串默认是以','分割。
该方案只能进行简单类型的转换(Teacher中field都是些简单类型),还不支持Teacher中包含复杂类型如包含其他属性类。其实也可以做成支持的,就是再稍加改造些,对于Field的处理先判断是否是简单类型,如Address类,若不是则递归调用上面的处理过程即对Address再次遍历Field来实现Address中简单类型的绑定。关键就是执行个递归调用,其他也没什么,有兴趣的可以自行研究。本例中的自定义文件可在后面下载。
方案四:
根据方案二我们其实就可以想到更改下方案二所用到的ServletModelAttributeMethodProcessor,就可以达到我们想要的结果。即如下:
public final Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception { String name = ModelFactory.getNameForParameter(parameter); Object attribute = (mavContainer.containsAttribute(name)) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request); //重点在这里在这里在这里在这里在这里在这里在这里 WebDataBinder binder = binderFactory.createBinder(request, attribute, name); if (binder.getTarget() != null) { bindRequestParameters(binder, request); validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors()) { if (isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = binder.getBindingResult().getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return binder.getTarget(); }
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);在创建出WebDataBinder后,调用下binder.setFieldDefaultPrefix(prefix);就可以大功告成了。然而,我们会看到该方法是final,不可覆盖的,我就复制粘贴了一份,出来,新建了一个自定义的MyServletModelAttributeMethodProcessor以及它对应的注解标签MyServletModelForm,代码如下:
MyServletModelForm内容为:
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyServletModelForm { String value() default ""; }
MyServletModelAttributeMethodProcessor的主要内容为:
public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(MyServletModelForm.class)) { return true; } return false; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String name = ModelFactory.getNameForParameter(parameter); Object attribute = (mavContainer.containsAttribute(name)) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest); WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); String prefix=getFieldDefaultPrefix(parameter); if(!prefix.equals("")){ binder.setFieldDefaultPrefix(prefix+"."); } if (binder.getTarget() != null) { bindRequestParameters(binder, webRequest); validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors()) { if (isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } } Map<String, Object> bindingResultModel = binder.getBindingResult().getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return binder.getTarget(); }
处理函数代码为:
@RequestMapping(value="/test/two",method=RequestMethod.POST) @ResponseBody public Map<String,Object> testrequestHeader(@MyServletModelForm Teacher a,@MyServletModelForm Student b){ Map<String,Object> map=new HashMap<String,Object>(); map.put("name","lg"); map.put("age",23); map.put("date",new Date()); return map; }
并把MyServletModelAttributeMethodProcessor在xml文件中进行配置:
<mvc:annotation-driven > <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> </bean> </mvc:message-converters> <mvc:argument-resolvers> <bean class="com.lg.mvc.MyHandlerMethodArgumentResolver"/> <bean class="com.lg.mvc.MyServletModelAttributeMethodProcessor"/> </mvc:argument-resolvers> </mvc:annotation-driven>
经测试,可以。附件中含有这两个类供下载。
发表评论
-
SpringMVC源码总结(十二)ViewResolver介绍
2014-09-10 06:43 2263首先我们先看看ModelAndView中重要的View接口。 ... -
SpringMVC源码总结(十一)mvc:interceptors拦截器介绍
2014-09-08 20:21 5781本文章针对mvc:interceptors标签进行介绍,它的注 ... -
SpringMVC源码总结(九)HandlerMethodArgumentResolver介绍
2014-09-02 06:24 12391本文章主要介绍HandlerMethodArgumentRes ... -
SpringMVC源码总结(八)类型转换PropertyEditor的背后
2014-08-30 17:13 4837PropertyEditor是Spring最初 ... -
SpringMVC源码总结(七)mvc:annotation-driven中的HttpMessageConverter
2014-08-27 22:32 6571这一篇文章主要介绍下HttpMessageConverter整 ... -
SpringMVC源码总结(六)mvc:annotation-driven中的HandlerMethodReturnValueHandler
2014-08-26 06:21 6517经过了两篇的乱码说明,要重新回到mvc:annotation- ... -
SpringMVC源码总结(五)Tomcat的URIEncoding、useBodyEncodingForURI和CharacterEncodingFilter
2014-08-22 06:32 7331继续上一章节的乱码问题。上一篇文章仅仅说了设置Tomcat的U ... -
SpringMVC源码总结(四)由StringHttpMessageConverter引出的客户端服务器端之间的乱码过程分析
2014-08-20 22:49 3630继续上一篇文章遗留的乱码问题,引出从客户端数据到服务器端的乱码 ... -
SpringMVC源码总结(三)mvc:annotation-driven和mvc:message-converters简单介绍
2014-08-19 06:58 10124上一篇文章讲述了最简单的mvc:annotation-driv ... -
SpringMVC源码总结(二)mvc:annotation-driven以及@Controller和@RequestMapping的那些事
2014-08-16 22:47 8631上一篇文章让我们了解HandlerMapping和Handle ... -
SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门
2014-08-16 22:42 14695刚接触SpringMVC,对它的xml文件配置一直比较模模糊糊 ...
相关推荐
总的来说,HandlerMethodArgumentResolver是Spring MVC框架中处理控制器方法参数的核心组件,它提供了灵活性和扩展性,使得我们可以自定义参数解析规则,以适应各种复杂的业务场景。通过理解和利用这个接口,开发者...
springMvc源码分析springMvc源码分析springMvc源码分析springMvc源码分析springMvc源码分析springMvc源码分析
分析springmvc源码(2).zip分析springmvc源码(2).zip分析springmvc源码(2).zip分析springmvc源码(2).zip分析springmvc源码(2).zip分析springmvc源码(2).zip分析springmvc源码(2).zip分析springmvc源码(2).zip分析...
分析springmvc源码(5).zip分析springmvc源码(5).zip分析springmvc源码(5).zip分析springmvc源码(5).zip分析springmvc源码(5).zip分析springmvc源码(5).zip分析springmvc源码(5).zip分析springmvc源码(5).zip分析...
1、手写springmvc框架及分析springmvc源码.zip1、手写springmvc框架及分析springmvc源码.zip1、手写springmvc框架及分析springmvc源码.zip1、手写springmvc框架及分析springmvc源码.zip1、手写springmvc框架及分析...
SpringMVC DispatcherServlet重写、自定义拦截器拦截器源码
单图片上传到本地,编辑框回显, 多图片上传 回显。springmvc整合完成项目 详情 https://blog.csdn.net/m0_37946870/article/details/79913803
- `springmvc_hello` 可能是简单的 Hello World 示例,展示了 Spring MVC 的基本使用。 - `spring_user` 可能是一个用户管理模块,涉及到 Hibernate 的实体类、持久化操作以及 Spring MVC 的 Controller。 5. **...
Spring 和 Spring MVC 是两个在Java开发中至关重要的框架,它们为构建企业级应用程序提供了强大的支持。Spring 是一个全面的框架,涵盖了...无论是对于框架的调试、扩展,还是自定义功能,理解源码都是极其有价值的。
这些理解有助于我们定制化SpringMVC的行为,比如自定义拦截器、异常处理器等。 此外,SpringMVC中的AOP(面向切面编程)和IoC(控制反转)概念也值得深入研究。AOP用于实现如日志记录、事务管理等功能,而IoC则是...
在本压缩包 "springmvc源码测试代码" 中,我们可能找到了用于理解和学习 Spring MVC 源码的测试代码。这里我们将深入探讨 Spring MVC 的核心概念、工作流程以及如何通过测试代码来理解其实现。 首先,Spring MVC 的...
在本篇博客“SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门”中,我们将探讨Spring MVC框架的核心组件——HandlerMapping和HandlerAdapter。这两个组件是Spring MVC处理请求的关键,它们负责映射HTTP...
本文将深入探讨Spring、SpringMVC和SpringBoot的源码,帮助你理解和掌握这三大框架的关键知识点,从而在金三银四的Java面试中脱颖而出。 首先,让我们从Spring框架开始。Spring的核心是依赖注入(Dependency ...
最新springmvc源码分析与实战,绝对超值,亲测,高清,完整标签,已读。
本文抛砖引玉,并没有详细的介绍更全面的内容,通过一个例子让初次使用的人能够快速入门,简单的介绍一下。 第一,注解: @Before – 目标
SpringMVC源码jar包,方便查看源码。学习起来更加方便
SpringMVC是Spring框架的...通过深入研究压缩包中的源码,你可以了解这些组件如何协同工作,理解SpringMVC的请求处理流程,以及自定义和扩展其功能的方法。这将有助于提升你的Web开发技能,特别是在大型企业级应用中。