最新版请点击查看
问题描述:
springMVC 数据绑定 多个对象 如何准确绑定?
- <form>
- <input name="student.name" value="Kate" />
- <input name="student.type" value="自费" />
- <input name="teacher.name" value="Gavin" />
- <input name="teacher.level" value="2" />
- </form>
- @RequestMapping("/school.do")
- public String school(Student student, Teacher teacher) {
- return "school";
- }
如果还是想刚才的jsp那些写表单,是不能封装参数的,必须把“student.”和“teacher.”去掉,但是这样封装就不能准确封装了。
这个问题最近老是有人问,所以写一个扩展很容易解决这个问题,springmvc和spring一样,预留的扩展点足够多。
我们都知道struts2默认就是这种方案,这是因为struts2采用了OGNL,并通过栈(根对象)进行操作的,而且栈中默认有action实例,所以很自然的没有这种问题。
springmvc不同,没有根对象的概念,而且本身很难来解决这个问题,因此大家在使用时最好避免这种方式或者使用类似于struts1的FormBean组合对象来解决。
解决方案:
扩展spring的HandlerMethodArgumentResolver以支持自定义的数据绑定方式。
1、请下载附件的代码,放到工程中;
2、在RequestMappingHandlerAdapter添加自定义HandlerMethodArgumentResolver Bean;
- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
- <!--线程安全的访问session-->
- <property name="synchronizeOnSession" value="true"/>
- <property name="customArgumentResolvers">
- <list>
- <bean class="cn.javass.spring.mvc.method.annotation.RequestJsonParamMethodArgumentResolver"/>
- <bean class="cn.javass.spring.mvc.method.annotation.FormModelMethodArgumentResolver"/>
- </list>
- </property>
- </bean>
//customArgumentResolvers用于注入自定义的参数解析器,此处我们注了FormModelMethodArgumentResolver;FormModelMethodArgumentResolver我直接修改的org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
3、使用方式
- public String user(@FormModel("student") Student student, @FormModel("teacher") Teacher teacher)
4、测试控制器
- package cn.javass.chapter6.web.controller.formmodel;
- import java.util.Arrays;
- import java.util.List;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import cn.javass.chapter6.model.UserModel;
- import cn.javass.spring.mvc.bind.annotation.FormModel;
- import cn.javass.spring.mvc.util.MapWapper;
- @Controller
- @RequestMapping("/formmodel")
- public class FormModelController {
- //ok http://localhost:9080/springmvc-chapter6/formmodel/user?user.username=zhang&user.password=123
- @RequestMapping("/user/{user.realname}")
- public String user(@FormModel("user") UserModel user) {
- System.out.println(user);
- return "redirect:/success";
- }
- //ok http://localhost:9080/springmvc-chapter6/formmodel/array1?array[0]=zhang&array[1]=li
- @RequestMapping("/array1")
- public String array1(@FormModel("array") String[] array) {
- System.out.println(Arrays.toString(array));
- return "redirect:/success";
- }
- //ok http://localhost:9080/springmvc-chapter6/formmodel/array2?array[0].username=zhang&array[0].password=123&array[1].username=li
- @RequestMapping("/array2")
- public String array2(@FormModel("array") UserModel[] array) {
- System.out.println(Arrays.toString(array));
- return "redirect:/success";
- }
- //ok http://localhost:9080/springmvc-chapter6/formmodel/list1?list[0]=123&list[1]=234
- @RequestMapping("/list1")
- public String list1(@FormModel("list") List<Integer> list) {
- System.out.println(list);
- return "redirect:/success";
- }
- //ok http://localhost:9080/springmvc-chapter6/formmodel/list2?list[0].username=zhang&list[1].username=li
- @RequestMapping("/list2")
- public String list2(@FormModel("list") List<UserModel> list) {
- System.out.println(list);
- return "redirect:/success";
- }
- //ok http://localhost:9080/springmvc-chapter6/formmodel/map1?map['0']=123&map["1"]=234
- @RequestMapping("/map1")
- public String map1(@FormModel("map") MapWapper<String, Integer> map) {
- System.out.println(map);
- return "redirect:/success";
- }
- //ok http://localhost:9080/springmvc-chapter6/formmodel/map2?map['0'].password=123&map['0'].username=123&map["1"].username=234
- @RequestMapping("/map2")
- public String map2(@FormModel("map") MapWapper<Integer, UserModel> map) {
- System.out.println(map);
- return "redirect:/success";
- }
- }
具体使用可以下载之前springmvc第六章源代码http://jinnianshilongnian.iteye.com/blog/1683388
将附件中的FormModel.rar解压放到src下进行测试。
支持的spring版本:
springmvc 3.1.x,暂不支持3.0。为什么不支持呢?springmvc 3.1 和 3.0 从架构上发生了变化,而且springmvc3.1更容易扩展。
支持绑定的数据:
模型、集合、数组、MapWapper(Map的一个包装器,通过getInnerMap获取真实Map)
缺点:
spring自定义的参数解析器会放在默认解析器之后,不能指定order,因此如果我们@FormModel("map") Map map,此map会变成Model(请参考http://jinnianshilongnian.iteye.com/blog/1698916 第六部分、Model Map ModelMap),希望未来的版本支持自定义顺序来解决这个问题;此处我们使用MapWapper解决,可以通过MapWapper.getInnerMap()拿到我们需要的Map
其他方案:
[SpringMVC]修改源码使之能够更加智能的自动装配request请求参数.(不建议修改源代码解决)
@rainsoft 也给出了类似的方案, http://www.iteye.com/topic/1124433#2357830
如果你使用的是mvc:annotation-driven,请这样配置
- <mvc:annotation-driven>
- <mvc:argument-resolvers>
- <bean class="com.sishuok.es.common.web.bind.method.annotation.FormModelMethodArgumentResolver"/>
- </mvc:argument-resolvers>
- </mvc:annotation-driven>
欢迎大家反馈问题,我会及时修正。
下一个扩展: 绑定请求参数(JSON字符串,如 deptIds=[{"deptId":4,"isPrimary":true}] ) 到 模型对象。
顶
踩
评论
这个顺序可以通过间接的方式更改
@Component
public class RequestMappingHandlerAdapterWrapper {
@Resource
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@Resource
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
@Resource
private List<HandlerMethodReturnValueHandler> customReturnValueResolvers;
//@Resource
private ConfigurableWebBindingInitializer configurableWebBindingInitializer;
@PostConstruct
public void init() {
// 请注意,此种方式只支持spring3(鉴于公司目前都是使用spring3,选择此实现),spring4中getArgumentResolvers返回值为List
List<HandlerMethodArgumentResolver> existingArgumentResolverList = requestMappingHandlerAdapter
.getArgumentResolvers().getResolvers();
requestMappingHandlerAdapter.setArgumentResolvers(customArgumentResolvers);
requestMappingHandlerAdapter.getArgumentResolvers().addResolvers(existingArgumentResolverList);
requestMappingHandlerAdapter.setWebBindingInitializer(configurableWebBindingInitializer);
List<HandlerMethodReturnValueHandler> returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers().getHandlers();
requestMappingHandlerAdapter.setReturnValueHandlers(customReturnValueResolvers);
requestMappingHandlerAdapter.getReturnValueHandlers().addHandlers(returnValueHandlers);
}
}
这种是可以绑的呀?难道是版本差异吗,我用的是3.2.9.RELEASE,
$(input).attr("value",$("#val"+goodsInfo[i]).attr("value"));
$(input).attr("name","goodOrderFormList["+i+"].goodId");
form.append(input);
这样提交,form是顺利绑定的呀,为何student.name、teacher.name就不可以了呢
对于url http://localhost:9080/springmvc-chapter6/formmodel/list2?list[0].username=zhang&list[0].password=1&list[1].username=li&list[1].password 绑定后的list集合大小为4
仔细看了FormModeMethodArgumentResolver中对于请求参数绑定的函数 bindRequestParameters(
ModelAndViewContainer mavContainer,
WebDataBinderFactory binderFactory,
WebDataBinder binder,
NativeWebRequest request,
MethodParameter parameter),
对于前缀的识别太过简单了
String prefixName = getPrefixName((String) key);
并没有过滤重复识别的前缀,这样导致没遇到一个"[..]."类型的前缀就往集合中添加一个对象。
在jquey ajax方式下,data输入复合型的json对象,如{aaa:{bbb:10,xxx:{yyy:99}},fff:{f1:1}} ,后台接口上用@FormModel("aaa"),@FormModel("fff")接收(业务关系,隐藏实际字段名),按FormModelMethodArgumentResolver 448 parameterName.substring(modelPrefixNameLength) 的方式无法解析,大侠是否可以继续补充完整这部分逻辑?
class A{
对象B b,
对象C c,
}
页面表单中就可以b.name,c.name这种方式提交了。
@RequestMapping(value = "/test")
public void binderTest(@Valid @FormModel("user")User user, BindingResult bindingResult)
错误:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the @RequestBody or the @RequestPart arguments to which they apply: public void com.kingray.web.UserController.binderTest(com.xiongyingqi.hibernate.domain.User,org.springframework.validation.BindingResult)
开涛,我也出现这个问题了,单独使用是对的,一起使用就错了
@RequestMapping(value = "/test")
public void binderTest(@Valid @FormModel("user")User user, BindingResult bindingResult)
错误:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the @RequestBody or the @RequestPart arguments to which they apply: public void com.kingray.web.UserController.binderTest(com.xiongyingqi.hibernate.domain.User,org.springframework.validation.BindingResult)
我觉得 spring 应该可以吧
看这篇:http://www.iteye.com/topic/973918
这样的话,如果要在提交一个表单的过程中,给两个平级(不是父子关系)的对象分别绑定自己的表单数据,可以把这两个对象封装到一个对象中,这样就可以使用 "." 来分层级绑定、或精准绑定了?
tmainVo[0].prePolicy=1&tmainVo[1].prePolicy=2 可以装载到 @FormModel("tmainVo") List<PrpTmainVo> tmainVoList;
但是一般多行编辑的时候,序号0,1,不会赋值上去,能不能将tmainVo.prePolicy=1&tmainVo.prePolicy=2 也能自动装载为List
首先 自定义注解ExtModelAttribute
- 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, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface ExtModelAttribute {
- String value() default "";
- }
然后利用方法
- void org.springframework.web.bind.WebDataBinder.setFieldDefaultPrefix(String fieldDefaultPrefix)
改变默认前缀
不过到3.2.8 还是有点小问题 前缀后面要加一个字符 比如“.”
以下是自定义注解对应处理器ExtServletModelAttributeMethodProcessor
- import javax.servlet.ServletRequest;
- import org.springframework.core.MethodParameter;
- import org.springframework.web.bind.ServletRequestDataBinder;
- import org.springframework.web.bind.WebDataBinder;
- import org.springframework.web.bind.annotation.ModelAttribute;
- import org.springframework.web.context.request.NativeWebRequest;
- import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
- import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
- public class ExtServletModelAttributeMethodProcessor extends ServletModelAttributeMethodProcessor
- {
- public ExtServletModelAttributeMethodProcessor(){
- super(false);
- }
- public ExtServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
- super(annotationNotRequired);
- }
- @Override
- public boolean supportsParameter(MethodParameter parameter) {
- if (parameter.hasParameterAnnotation(ExtModelAttribute.class)) {
- return true;
- }
- else{
- return false;
- }
- }
- @Override
- protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
- ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
- ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
- servletBinder.setFieldDefaultPrefix(servletBinder.getObjectName()+".");
- servletBinder.bind(servletRequest);;
- }
加入自定义注解ExtModelAttribute 的处理器ExtServletModelAttributeMethodProcessor
- <mvc:annotation-driven>
- <mvc:argument-resolvers>
- <bean class="ExtServletModelAttributeMethodProcessor"/>
- </mvc:argument-resolvers>
- </mvc:annotation-driven>
控制器中使用
- @RequestMapping(value="/test")
- public String ftl(@ExtModelAttribute("testVo")TestVo testVo){
- System.out.println(testVo.getValue());
- return "test";
- }
页面使用
- <form action="" method="post">
- Date Inpout<Input id="value" name="testVo.value" value="${testVo.value}" />
- <input type="submit" value="submit">
- </form>
首先 自定义注解ExtModelAttribute
- 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, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface ExtModelAttribute {
- String value() default "";
- }
然后利用方法
- void org.springframework.web.bind.WebDataBinder.setFieldDefaultPrefix(String fieldDefaultPrefix)
改变默认前缀
不过到3.2.8 还是有点小问题 前缀后面要加一个字符 比如“.”
以下是自定义注解对应处理器ExtServletModelAttributeMethodProcessor
- import javax.servlet.ServletRequest;
- import org.springframework.core.MethodParameter;
- import org.springframework.web.bind.ServletRequestDataBinder;
- import org.springframework.web.bind.WebDataBinder;
- import org.springframework.web.bind.annotation.ModelAttribute;
- import org.springframework.web.context.request.NativeWebRequest;
- import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
- import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
- public class ExtServletModelAttributeMethodProcessor extends ServletModelAttributeMethodProcessor
- {
- public ExtServletModelAttributeMethodProcessor(){
- super(false);
- }
- public ExtServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
- super(annotationNotRequired);
- }
- @Override
- public boolean supportsParameter(MethodParameter parameter) {
- if (parameter.hasParameterAnnotation(ExtModelAttribute.class)) {
- return true;
- }
- else{
- return false;
- }
- }
- @Override
- protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
- ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
- ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
- servletBinder.setFieldDefaultPrefix(servletBinder.getObjectName()+".");
- servletBinder.bind(servletRequest);;
- }
加入自定义注解ExtModelAttribute 的处理器ExtServletModelAttributeMethodProcessor
- <mvc:annotation-driven>
- <mvc:argument-resolvers>
- <bean class="ExtServletModelAttributeMethodProcessor"/>
- </mvc:argument-resolvers>
- </mvc:annotation-driven>
控制器中使用
- @RequestMapping(value="/test")
- public String ftl(@ExtModelAttribute("testVo")TestVo testVo){
- System.out.println(testVo.getValue());
- return "test";
- }
页面使用
- <form action="" method="post">
- Date Inpout<Input id="value" name="testVo.value" value="${testVo.value}" />
- <input type="submit" value="submit">
- </form>
代码还是有问题 这里获取servletBinder.setFieldDefaultPrefix(servletBinder.getObjectName()+".");是默认的没加注解的name 靠!ModelFacory 搞死了 只支持 ModelAttribute
ServletModelAttributeMethodProcessor
ModelAttributeMethodProcessor 里的 final 方法限制了扩展,只能复制到新类里面了
首先 自定义注解ExtModelAttribute
- 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, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface ExtModelAttribute {
- String value() default "";
- }
然后利用方法
- void org.springframework.web.bind.WebDataBinder.setFieldDefaultPrefix(String fieldDefaultPrefix)
改变默认前缀
不过到3.2.8 还是有点小问题 前缀后面要加一个字符 比如“.”
以下是自定义注解对应处理器ExtServletModelAttributeMethodProcessor
- import javax.servlet.ServletRequest;
- import org.springframework.core.MethodParameter;
- import org.springframework.web.bind.ServletRequestDataBinder;
- import org.springframework.web.bind.WebDataBinder;
- import org.springframework.web.bind.annotation.ModelAttribute;
- import org.springframework.web.context.request.NativeWebRequest;
- import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
- import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
- public class ExtServletModelAttributeMethodProcessor extends ServletModelAttributeMethodProcessor
- {
- public ExtServletModelAttributeMethodProcessor(){
- super(false);
- }
- public ExtServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
- super(annotationNotRequired);
- }
- @Override
- public boolean supportsParameter(MethodParameter parameter) {
- if (parameter.hasParameterAnnotation(ExtModelAttribute.class)) {
- return true;
- }
- else{
- return false;
- }
- }
- @Override
- protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
- ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
- ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
- servletBinder.setFieldDefaultPrefix(servletBinder.getObjectName()+".");
- servletBinder.bind(servletRequest);;
- }
加入自定义注解ExtModelAttribute 的处理器ExtServletModelAttributeMethodProcessor
- <mvc:annotation-driven>
- <mvc:argument-resolvers>
- <bean class="ExtServletModelAttributeMethodProcessor"/>
- </mvc:argument-resolvers>
- </mvc:annotation-driven>
控制器中使用
- @RequestMapping(value="/test")
- public String ftl(@ExtModelAttribute("testVo")TestVo testVo){
- System.out.println(testVo.getValue());
- return "test";
- }
页面使用
- <form action="" method="post">
- Date Inpout<Input id="value" name="testVo.value" value="${testVo.value}" />
- <input type="submit" value="submit">
- </form>



2255

被折叠的 条评论
为什么被折叠?




参考知识库