15

In a Spring 3 based web (portlet) application I have a controller with a method like this:

@RenderMapping
public ModelAndView handleRenderRequest(...,@RequestParam MyClass myObject)
{
    ...
}

Now I wonder: How do I tell spring how to convert the request parameter to MyClass. I found information about property editors and about the Converter interface and there seem to be some implications that Converter is the successor of the property editor, but nobody seems to like being explicit about it.

I implemented the converter interface for String to MyClass conversion. But how do I tell Spring about it? I am using annotation based configuration wherever possible, so I checked whether spring will detect the Converter from my classpath automatically, but it does not.

So thought that the part Configuring a ConversionService from the manual wants to tell me that I've got to add the following to my applicationContext.xml which I did:

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="some.package.MyConverter"/>
        </list>
    </property>
</bean>

Bit still:

org.springframework.beans.ConversionNotSupportedException: Failed to convert value [...]

So what am I missing? And is there a way, to just configure a package and let spring scan this package for converters and register them automatically? And say that in one certain method I want to use a different converter than in all other methods. For example I want an integer that has a Luhn-Checksum to be checked and the checksum removed, how can I do that? Something like @RequestParam(converter=some.package.MyConverter.class) would be great.

EDIT

Ok, I just caught in the documentation:

Use the Formatter SPI when you're working in a client environment, such as a web application, and need to parse and print localized field values

So I guess that means I should use the Formatter SPI, yet a third possibility next to property editors and converters (I think I could really to with a comparison table or the like). I did implement the Parser interface as well and tried to register my converter using:

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="some.package.SortOrderEnumConverterSpring"/>
        </set>
    </property>
</bean> 

As you can see I used "set" instead of "list" for specifying the converters. I set a debugging breakpoint in the FormattingConversionServiceFactoryBean.setConverters method which did not fire upon using list, but it did fire on using set.

Additionally I added

<mvc:annotation-driven conversion-service="conversionService"/>

And the namespace for the mvc-prefix to my applicationContext. But still I get the conversion not supported exception.

I also tried going back to the converter approach and changed in my applicationContext.xml file the parameter list for converters from list to set, but that did not change anything either.

EDIT2

As digitaljoel pointed out it is possible to set different converters per controller using an initBinder method. I applied this to my controller:

@Autowired
private ConversionService conversionService;

@InitBinder
public void initBinder(WebDataBinder binder)
{
    binder.setConversionService(conversionService);
}

And in my applicationContext.xml:

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="some.package.with.MyConverter"/>
        </set>
    </property>
</bean>

And all suddenly the conversion works just fine :-). But I am not quite happy about having to apply this to each and every of my controllers. There must be a way to just set it in my applicationContext for everyone, is there not? Good to know that I can override default if I need to (after all I asked for that), but I still want to set defaults.

And what about the Formatter stuff. Shouldn't I be using that instead of Converter?

yankee
  • 38,872
  • 15
  • 103
  • 162

4 Answers4

3

Spring Portlet MVC 3.0 does not support

<mvc:annotation-driven conversion-service="conversionService"/>

Visit https://jira.springsource.org/browse/SPR-6817 for more info about this.

However you can add this to your common applicationContext

<bean
    class="org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="webBindingInitializer">
        <bean
            class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
            <property name="conversionService">
                <list>
                    <ref bean="conversionService" />
                </list>
            </property>
        </bean>
    </property>
</bean>

This way you do not need add @InitBinder to every single controller

and of course

<bean id="conversionService"
    class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <!-- converter implementations here -->
        </list>
    </property>
</bean>
1

Implement a WebArgumentResolver:

public class MyArgumentResolver implements WebArgumentResolver
{
    @Override
    public Object resolveArgument(MethodParameter methodParameter,
            NativeWebRequest webRequest) throws Exception
    {
        Class<?> paramType = methodParameter.getParameterType();
        if (paramType == MyClass.class)
        {
            String parameterName = methodParameter.getParameterName();
            String stringParameter = webRequest.getParameter(parameterName);
            return convert(stringParameter);
        }
        return UNRESOLVED;
    }
}

And register it in your applicationContext.xml:

<bean class="org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="customArgumentResolver">
        <bean class="com.dshs.eakte.util.MyArgumentResolver" />
    </property>
</bean>

This works and even has the advantage of allowing parameter conversion that is based on multiple method parameters.

yankee
  • 38,872
  • 15
  • 103
  • 162
  • Ok, that's a possibility, unfortunately not as simple as just defining a converter globally like it works well for some data types that are built in (Numbers,...) and use it, since it looks like I can only specify one of those WebArgumentResolvers – yankee Jun 16 '12 at 22:46
  • probably a @ModelAttribute option is easier than the above. – skipy Apr 09 '14 at 09:33
1

You are correct that Converter (and ConverterFactory) are the successors to property editors. Your problem may be that you are not accepting the appropriate type as a parameter to your converter, but that's hard to say without seeing the converter code. If you are expecting Long or Integer you may actually be getting a String from Spring and need to perform that key conversion yourself first.

As for configuration, I believe you need to list all of your converters in the bean configuration in your xml. If you annotate your converter implementation with @Component you might be able to reference it by the bean name instead of the fully qualified path, but I have only tried that for a ConverterFactory, not a Converter.

Finally, on specific converters, it looks like you may be able to configure the conversion service at the controller level (see Javi's answer on Setting up a mixed configuration for annotation-based Spring MVC controllers ) and then you could just place that method (and others that require that controller) into a controller that uses a secondary conversion service which you ought to be able to inject by name with the @Resource annotation.

Community
  • 1
  • 1
digitaljoel
  • 26,265
  • 15
  • 89
  • 115
  • Valuable hints that brought me a step further. New findings based on your answers are now in my question above... – yankee Jul 26 '11 at 21:18
  • The formatting conversion service really combines two separate concerns. One for formatting (such as date formats) and one for conversion. I'm not sure why they are combined. Secondly, it looks like the mvc namespace doesn't work in portlets, only in servlets. see the comments in answer to https://jira.springsource.org/browse/SPR-6817 that would explain why you have to specify the @initbinder and mvc:annotation-drive didn't do anything for you. – digitaljoel Jul 26 '11 at 21:46
  • ok, so this is what I need in my applicationContext.xml to get things working: http://pastebin.com/P7DapZyz. Unfortunately if I do that, then I can't use the autowire/initBinder anymore, which will then throw an IllegalStateException because it is already configured. But it's probably better to make a cut here and make a seperate concern/question about that... I suggest you edit your question, include the XML and I'll accept it. – yankee Jul 26 '11 at 22:48
  • Since I'm not sure what the xml is doing, I'm not going to add it to my answer. Perhaps you should edit your question and append it, or answer your own question with the xml and an explanation of what is going on and why it doesn't allow the autowire/initBinder to work anymore. Glad my answers could at least help get you moving again. – digitaljoel Jul 27 '11 at 19:38
-1

i think you need to use something like

public ModelAndView handleRenderRequest(...,@ModelAttribute("myObject") MyClass myObject)
Zeronex
  • 434
  • 1
  • 3
  • 8
  • I once wanted just that: http://stackoverflow.com/questions/8818744/simplest-way-to-use-composed-object-as-requestparam-in-spring. But this issue is a little different – yankee Jun 16 '12 at 21:13
  • The difference is that @ModelAttribute aims at composed objects. But I want something like an URL `http://.../...?date=1970-1-1` that maps to a renderMethod `handleRenderRequest(MyOwnDateClass data)`. (Used "date" here just for simple explanation, actually I am converting different types) – yankee Jun 16 '12 at 21:32