I wrote a custom implementation of the WebDataBinder. In the future I'd like to enhance it so that it looks for an annotation on the class itself and figures out if it should be binding data to it or not.
How do I get this class injected into the Spring context in place of the WebDataBinder?
What I want is that if I run this code, my version of the WebDataBinder gets injected instead of the default Spring one.
@Controller
public class MyFormController {
@InitBinder
public void initBinder(WebDataBinder binder) {
// ...
}
// ...
}
My custom implementation of the WebDataBinder. It lets me exclude data binding by class instead of by method names.
package com.companyname.spring;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.WebDataBinder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CustomDataBinder extends WebDataBinder {
List<Class> disallowedClasses = new ArrayList<>();
public CustomDataBinder(Object target) {
super(target);
}
public CustomDataBinder(Object target, String objectName) {
super(target, objectName);
}
public CustomDataBinder disallowClass(Class... classes) {
Collections.addAll(disallowedClasses, classes);
return this;
}
@Override
protected void doBind(MutablePropertyValues mpvs) {
if(disallowedClasses.contains(getTarget().getClass())) {
if (logger.isDebugEnabled()) {
logger.debug("DataBinder will not bind class [" + getTarget().getClass().getSimpleName() + "] because it appears in the list of disallowed classes [" + StringUtils.collectionToCommaDelimitedString(disallowedClasses) + "]");
}
} else {
super.doBind(mpvs);
}
}
}
@Configuration
@ComponentScan(basePackageClasses = RootContextConfig.class)
@EnableTransactionManagement
@EnableWebSecurity
@EnableAsync
@EnableSpringConfigured
@EnableLoadTimeWeaving
public class RootContextConfig extends WebMvcConfigurationSupport {
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
addArgumentResolvers(argumentResolvers);
List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
addReturnValueHandlers(returnValueHandlers);
RequestMappingHandlerAdapter adapter = new CustomRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
adapter.setCustomArgumentResolvers(argumentResolvers);
adapter.setCustomReturnValueHandlers(returnValueHandlers);
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
configureAsyncSupport(configurer);
//All the methods called off of configurer are giving me errors because they have protected level access. I'm not really sure how they're being called in the code I copied this from.
if (configurer.getTaskExecutor() != null) {
adapter.setTaskExecutor(configurer.getTaskExecutor());
}
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}
package com.companyname.dirtylibs.spring;
import org.springframework.web.method.annotation.InitBinderDataBinderFactory;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
import java.util.List;
public class CustomRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
@Override
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods) throws Exception {
return new CustomInitBinderDataBinderFactory(binderMethods, getWebBindingInitializer());
}
}
package com.companyname.dirtylibs.spring;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.bind.support.WebRequestDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.InitBinderDataBinderFactory;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
import java.util.List;
public class CustomInitBinderDataBinderFactory extends InitBinderDataBinderFactory {
/**
* Create a new instance.
*
* @param binderMethods {@code @InitBinder} methods, or {@code null}
* @param initializer for global data binder intialization
*/
public CustomInitBinderDataBinderFactory(List<InvocableHandlerMethod> binderMethods, WebBindingInitializer initializer) {
super(binderMethods, initializer);
}
@Override
protected CustomDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest webRequest) throws Exception {
return new CustomDataBinder(target, objectName);
}
}
This is not a simple task. Spring allows for a lot of customization, but, damn, this change is not fun.
You need to extend the RequestMappingHandlerAdapter
class and override the following method
/**
* Template method to create a new InitBinderDataBinderFactory instance.
* <p>The default implementation creates a ServletRequestDataBinderFactory.
* This can be overridden for custom ServletRequestDataBinder subclasses.
* @param binderMethods {@code @InitBinder} methods
* @return the InitBinderDataBinderFactory instance to use
* @throws Exception in case of invalid state or arguments
*/
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
throws Exception {
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}
Instead of returning a ServletRequestDataBinderFactory
, you'll need to return a custom InitBinderDataBinderFactory
that returns your custom WebDataBinder
instances.
This change means you can't use the default @EnableWebMvc
or <mvc:annotation-driven/>
configuration. That's because they use RequestMappingHandlerAdapter
by default, but you need to register your own class.
You can, however, override the @Bean
annotated WebMvcConfigurationSupport#requestMappingHandlerAdapter()
method and provide your own implementation to return your own type. Look into what that implementation does for hints.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With