Issues using Spring's DomainClassConverter in

2020-03-01 18:39发布

I am trying to use Spring's DomainClassConverter feature with my Spring MVC project. (I have only very basic knowledge of Spring MVC and Spring, apologies in advance for any naive question here).

From the API docs:

The DomainClassConverter allows you to use domain types in your Spring MVC controller 
method signatures directly, so that you don't have to manually lookup the instances via 
the repository: (PS: Example 1.20)

What I understood from the above is that I don't have to write a finder method and the Spring supplies the User object. So these are the steps I did:

Included the below line of XML in applicationcontext.xml.

<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
    <list>
        <bean class="com.rl.userservice.controller.UserConverter"/>
    </list>
</property>

Included this dependency in my pom.xml per the Spring Data REST doc:

<dependencies>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-rest-webmvc</artifactId>
        <version>2.0.0.BUILD-SNAPSHOT</version>
    </dependency>
</dependencies>

My controller looks like the below:

@Controller
@RequestMapping(value = "/api/newuser")
public class NewUserServiceController {
      @Autowired
  NewUserRepository newUserRepository;

  @RequestMapping("/{id}")
    public String showUserForm(@PathVariable("id") NewUser newUser, Model model) {

      model.addAttribute("newUser", newUser);
      return "userForm";
    } 
}

Repository is like this:

@Repository
public interface NewUserRepository extends JpaRepository<NewUser, Integer> {
}

This is my converter service:

final class UserConverter implements Converter<Integer, NewUser> {
    NewUserRepository newUserRepository;

    public NewUser convert(Integer username) {
        return newUserRepository.findOne(username);
    } 
}

When I run the program tomcat starts successful, but when accessing the URL localhost:8080/userservice/api/newuser/1 I get the below exception:

type Exception report
    message
    description The server encountered an internal error () that prevented it from fulfilling this request.
    exception
    org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type
'com.mpp.userservice.domain.NewUser'; nested exception is
java.lang.IllegalStateException: Cannot convert value of type  
[java.lang.String] to required type
[com.mpp.userservice.domain.NewUser]: no matching editors or
conversion strategy found
      org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:71)
      org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:45)
      org.springframework.validation.DataBinder.convertIfNecessary(DataBinder.java:595)
      org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:101)
      org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77)
      org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:162)
      org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:123)
      org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
      org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
      org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
      org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
      org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
      org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
      org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
      org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:827)
      javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
      org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
      javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

    root cause
    java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type
[com.mpp.userservice.domain.NewUser]: no matching editors or
conversion strategy found
      org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:264)
      org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:93)
      org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:61)
      org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:45)
      org.springframework.validation.DataBinder.convertIfNecessary(DataBinder.java:595)
      org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:101)
      org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77)
      org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:162)
      org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:123)
      org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
      org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
      org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
      org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
      org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
      org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
      org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
      org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:827)
      javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
      org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
      javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

    note The full stack trace of the root cause is available in the Apache Tomcat/6.0.29 logs.
    mpp.

Though not the best code, here's my controller:

public @ResponseBody ResponseEntity<ModelMap> getUserTypeJSON(@PathVariable("userID" String userID, HttpServletResponse response)  { 
    UserType UserType = UserTypeRepository.findOne(id);
    model.addAttribute("Name",UserType.getName());  
    ...
} 

There is an example here that I referenced, but this is using custom converter but does not seem to be using the domain converter service. Please advise. Is this the way to go if I want to reduce boilerplate code of writing CRUD operations? What is the real benefit of this DomainClassConverter when I can get the data in in the other way?

Updated per Oliver Gierke suggestion - still does not work, same error

The document describes:

<mvc:annotation-driven conversion-service="conversionService" />
<bean class="org.springframework.data.repository.support.DomainClassConverter">
  <constructor-arg ref="conversionService" />
</bean>

So I updated my applicationcontext.xml as below, but the same issue:

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

  <bean class="org.springframework.data.repository.support.DomainClassConverter">
    <constructor-arg ref="conversionService" />
  </bean>

    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
          <bean class="com.rl.userservice.controller.UserConverter"/>
        </list>
    </property>
  </bean>

Still the same issue.

Update: DomainClassConverter works with Java Config, but not the XML way (at least experimented with a lot of combinations suggested here and else where on the internet). Just for the others who might be interested and get some useful info here's the code used.

pom.xml (Might require clean-up)

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-rest-webmvc</artifactId>
    <version>2.0.0.M1</version>
</dependency>

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.4.3.RELEASE</version>
</dependency>

The controller file (Might require clean-up)

@RequestMapping("/domain/{id}")
  public @ResponseBody ResponseEntity<ModelMap> showDomainUserForm(@PathVariable("id") User userMatch, HttpServletResponse response) {

  // some code omitted…  

    ModelMap model = new ModelMap();        
    model.addAttribute("DOMAIN-MAP","Domain Controller Service");
    model.addAttribute("Name",userMatch.getName());
    model.addAttribute("Phone",userMatch.getPhone());                                           

    // some code omitted…


  } 

The Java Config file assembled using examples from resource1 and resource2. (Might require clean-up)

package com.rl.userservice.controller;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@Configuration
@ComponentScan
@EnableWebMvc
public class WebConfig extends WebMvcConfigurationSupport{
     @Bean
     public RequestMappingHandlerMapping requestMappingHandlerMapping() {
      RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
      handlerMapping.setUseSuffixPatternMatch(false);
      handlerMapping.setUseTrailingSlashMatch(false);
      return handlerMapping;
     } 


     @Bean
     public DomainClassConverter<?> domainClassConverter() {
         return new DomainClassConverter<FormattingConversionService>(mvcConversionService());
     }

     @Override
     public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
         configurer.enable();
     }

}

Add the below bean definition in the applicationContext.xml

<bean class="com.rl.userservice.controller.WebConfig"/>

4条回答
够拽才男人
2楼-- · 2020-03-01 19:04

The ref says

Currently the repository has to implement CrudRepository to be eligible to be discovered for conversion.

Shouldn't that be the reason?

查看更多
We Are One
3楼-- · 2020-03-01 19:08

A URL is a String, so {id} is a String too. Therefore your service must be able to convert a String to NewUser, not an Integer as yours does.

查看更多
一纸荒年 Trace。
4楼-- · 2020-03-01 19:13

This configuration sets up a custom conversion service and passes it to annotation scanning mechanism that detects and sets up the controllers:

<bean name="conversionService" class="rest.gateway.services.MyConversionService"/>

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

And this is the code for the custom controller, customer being a domain class like User:

public class MyConversionService extends DefaultConversionService {

    public MyConversionService() {
        super();
        addConverter(String.class, Customer.class, new Converter<String, Customer>() {
            @Override
            public Customer convert(String source) {
                return new Customer("123456","Doe","John");
            }
        });
    }
}

Have a try with this because this is working for version 2.0.0.M1:

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-rest-webmvc</artifactId>
        <version>2.0.0.M1</version>
    </dependency>

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>http://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>
查看更多
倾城 Initia
5楼-- · 2020-03-01 19:28

Please have a look at the relevant section of the reference documentation to find out about the correct way to configure the DomainClassConverter.

查看更多
登录 后发表回答