Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I inject a custom version of WebDataBinder into Spring 3 MVC?

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);
            }
        }
    }

EDIT1

First pass, having trouble with the AsyncSupportConfigurer

@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;
    }

Custom RequestMappingHandlerAdapter

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());
    }
}

Custom InitBinderDataBinderFactory

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);
    }
}
like image 463
Jazzepi Avatar asked Mar 19 '14 23:03

Jazzepi


1 Answers

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.

like image 86
Sotirios Delimanolis Avatar answered Oct 21 '22 17:10

Sotirios Delimanolis