Class DataBinder
- java.lang.Object
-
- org.springframework.validation.DataBinder
-
- All Implemented Interfaces:
- PropertyEditorRegistry, TypeConverter
- Direct Known Subclasses:
- WebDataBinder
public class DataBinder extends Object implements PropertyEditorRegistry, TypeConverter
Binder that allows for setting property values onto a target object, including support for validation and binding result analysis. The binding process can be customized through specifying allowed fields, required fields, custom editors, etc.Note that there are potential security implications in failing to set an array of allowed fields. In the case of HTTP form POST data for example, malicious clients can attempt to subvert an application by supplying values for fields or properties that do not exist on the form. In some cases this could lead to illegal data being set on command objects or their nested objects. For this reason, it is highly recommended to specify the
allowedFields
property on the DataBinder.The binding results can be examined via the
BindingResult
interface, extending theErrors
interface: see thegetBindingResult()
method. Missing fields and property access exceptions will be converted toFieldErrors
, collected in the Errors instance, using the following error codes:- Missing field error: "required"
- Type mismatch error: "typeMismatch"
- Method invocation error: "methodInvocation"
By default, binding errors get resolved through the
BindingErrorProcessor
strategy, processing for missing fields and property access exceptions: see thesetBindingErrorProcessor(org.springframework.validation.BindingErrorProcessor)
method. You can override the default strategy if needed, for example to generate different error codes.Custom validation errors can be added afterwards. You will typically want to resolve such error codes into proper user-visible error messages; this can be achieved through resolving each error via a
MessageSource
, which is able to resolve anObjectError
/FieldError
through itsMessageSource.getMessage(org.springframework.context.MessageSourceResolvable, java.util.Locale)
method. The list of message codes can be customized through theMessageCodesResolver
strategy: see thesetMessageCodesResolver(org.springframework.validation.MessageCodesResolver)
method.DefaultMessageCodesResolver
's javadoc states details on the default resolution rules.This generic data binder can be used in any kind of environment. It is typically used by Spring web MVC controllers, via the web-specific subclasses
ServletRequestDataBinder
andPortletRequestDataBinder
.- Author:
- Rod Johnson, Juergen Hoeller, Rob Harrop, Stephane Nicoll
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,做法是:
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 }
也就是說:
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