org.springframework.web.bind.ServletRequestDataBinde

时间:2022-07-23 12:15:11
org.springframework.validation

Class DataBinder

at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler.setValue(BeanWrapperImpl.java:344)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:452)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:278)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:95)
at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:810)
at org.springframework.validation.DataBinder.doBind(DataBinder.java:706)
at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:189)
at org.springframework.web.bind.ServletRequestDataBinder.bind(ServletRequestDataBinder.java:106)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.bindRequestParameters(ServletModelAttributeMethodProcessor.java:150)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:110)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:162)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:129)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:111)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:799)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:728)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:969)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:860)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:845)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.global.filter.AddExtraToParamsFilter.doFilter(AddExtraToParamsFilter.java:27)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1521)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1478)
- locked <0x14a4> (a org.apache.tomcat.util.net.NioChannel)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

增加的就是第110行

1 binder.setFieldDefaultPrefix(parameter.getParameterName() + "!");

parameter.getParameterName()返回的是你@Controller里2RequestMapping方法参数的名字

"!"是我区分成员域与对象名的分解符...这个可以自己设置.你想设置成.也可以!也可以#也OK

只要自己能区分就行了.

测试

URL:

1 http://localhost:8080/quick-start/test18?context!stateCode=91&a!name=hehe&context!exception.message=error&a!stateCode=84

后台打印参数:

1 com.labofjet.web.ContextDTO@9344568[stateCode=91,data=<null>,exception=com.labofjet.exception.BaseException: error]
2 com.labofjet.dto.ADTO@814d736[id=<null>,name=hehe,age=<null>,value=<null>,b=0,stateCode=84]

Controller的方法签名:

1     @RequestMapping("/test18")
2 public Object index18(ContextDTO context, ADTO a) throws IOException {
3 System.out.println(context);
4 System.out.println(a);
5 return null;
6 }

原理

先简明说下原理..具体的理论我想后面等我整理下思路,介绍RequestMappingHandlerAdapter的时候再讲(反正没人看...)

SpringMVC里@Controller里的各种参数由各种argumentsResolver来解析.

解析自定义的这种DTO的argumentsResolver是ServletModelAttributeMethodProcessor这个类.

收到参数以后他怎么解析呢?

很简单呀.在我测试例子中,比如我要初始化一个context对象,SpringMVC就先把context包装成BeanWrapperImpl对象..然后把Request里的各种key-value包装成MutablePropertyValues..然后set进去就可以了.利用的是反射的知识,你有一个key,那我就看BeanWrapperImpl warp的那个对象有没有对应的属性.有就设置进去.

既然原理是这样,那怎么达到我们的目的呢?

这里又有至少2种方法:

第一种就是像我那样: binder.setFieldDefaultPrefix(parameter.getParameterName() + "!"); 这样在解析MutablePropertyValues的时候会去掉你set进去的Feild的Prefix.

第二种方法: ServletRequestDataBinder在绑定的参数的时候需要先把request转化成MutablePropertyValues,做法是:

org.springframework.web.bind.ServletRequestDataBinde
1     public void bind(ServletRequest request) {
2 MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
3 MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
4 if (multipartRequest != null) {
5 bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
6 }
7 addBindValues(mpvs, request);
8 doBind(mpvs);
9 }
org.springframework.web.bind.ServletRequestDataBinde

也就是说:

1 MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);

而MutablePropertyValues 其实还有一种构造方法:

1     public ServletRequestParameterPropertyValues(ServletRequest request, String prefix) {
2 this(request, prefix, DEFAULT_PREFIX_SEPARATOR);
3 }

这种方法允许你填入一个prefix...那原理和前面是一样的..

但是这种方法需要改很多类...所以没有方法一简单....

为什么不使用继承

可能会有朋友想问.改进的ArgumentResolver和原本Spring自带的基本一样,只是多了一步,为什么不继承自原本的ServletModelAttributeMethodProcessor? 毕竟修改源码方案不太好.

原因有很多,主要有2个原因:

原因1:

ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor

resolveArgument在ModelAttributeMethodProcessor里的定义是:

public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
...........
}

是final啊!final!!!!!!!!!!!

所以我修改不了.

原因2:

一般父类定义了一个处理流程的话不能修改的话,会在子类给我们留一个扩展接口...

没错,那就是:

1     @Override
2 protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
3 ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
4 ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
5 servletBinder.bind(servletRequest);
6 }

这个是在ServletModelAttributeMethodProcessor里覆盖了父类的方法,我们可以继承ServletModelAttributeMethodProcessor再覆盖这个bindRequestParameters方法..

但是......

这里只有2个参数啊!!!!我们需要的@Controller的参数名称的信息不在这里....我们需要这个变量:

1 public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
2 NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
3 .............
4 }

参数的信息都在这里里面..可是bindRequestParameters方法里没有传入这个参数...所以坑爹的是我们在bindRequestParameters里并不能知道@Controller里参数的名字到底是什么...

所以我们没有办法设置一个通用的绑定方法...

方法:利用其它的HandlerMethodArgumentResolver

具体方法

不改源码最简单的方法可能是不自己写ArgumentResolver,而是利用SpringMVC原有的Resolver了吧..

我们可以利用RequestResponseBodyMethodProcessor这个ArgumentResolver..

具体请参考我的另外一篇文章:

http://www.cnblogs.com/abcwt112/p/5169250.html

原理就是利用HttpMessageConverter和其它的json转化工具将request里的参数转化成java bean.这也是很简单的.

基本只要在参数前加一个@RequestBody...

方法:利用@InitBinder注解

具体:

请大家看这篇文章:

http://jinnianshilongnian.iteye.com/blog/1888474

缺点:

1.原理和方法:改源码是差不多的....都是通过修改binder设置额外属性来达到目的的,但是没传入MethodParameter parameter,所以还是不知道你的@Controller里的参数名字..只能手动指定前缀

2.貌似要绑定的时候每个Controller里都要写@InitBinder,稍微有点麻烦..当然好处是更灵活...

方法N:自己实现HandlerMethodArgumentResolver

这个方法就太多了......

请参考:

http://jinnianshilongnian.iteye.com/blog/1717180

简单总结

方法有太多太多了..不同方法可能适合不同场景,但是我觉得最简单的还是@InitBinder和@RequestBody这2种方案.

http://www.cnblogs.com/abcwt112/p/5302469.html