在 Spring MVC 中使用 Spring 的 DomainClassConverter 的问题

Posted

技术标签:

【中文标题】在 Spring MVC 中使用 Spring 的 DomainClassConverter 的问题【英文标题】:Issues using Spring's DomainClassConverter in Spring MVC 【发布时间】:2014-01-08 19:37:06 【问题描述】:

我正在尝试在我的 Spring MVC 项目中使用 Spring 的 DomainClassConverter 功能。 (我对 Spring MVC 和 Spring 只有非常基本的知识,在这里对任何幼稚的问题提前道歉)。

来自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)

我从上面了解到的是,我不必编写查找器方法,并且 Spring 提供了 User 对象。所以这些是我做的步骤:

applicationcontext.xml 中包含以下 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>

根据Spring Data REST 文档在我的pom.xml 中包含此依赖项:

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

我的控制器如下所示:

@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
public interface NewUserRepository extends JpaRepository<NewUser, Integer> 

这是我的转换器服务:

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

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

当我运行程序时,tomcat 启动成功,但在访问 URL localhost:8080/userservice/api/newuser/1 时出现以下异常:

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.

虽然不是最好的代码,但这是我的控制器:

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

我引用了一个示例here,但这是使用自定义转换器,但似乎没有使用域转换器服务。请指教。如果我想减少编写 CRUD 操作的样板代码,这是要走的路吗?当我可以通过其他方式获取数据时,这个DomainClassConverter 的真正好处是什么?

根据 Oliver Gierke 的建议进行了更新 - 仍然无法正常工作,同样的错误

文档描述:

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

所以我更新了我的applicationcontext.xml 如下,但同样的问题:

    <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>

还是同样的问题。

更新: DomainClassConverter 适用于 Java Config,但不适用于 XML 方式(至少尝试了此处和 Internet 上其他地方建议的许多组合)。仅供其他可能感兴趣并获得一些有用信息的人参考这里使用的代码。

pom.xml(可能需要清理)

<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>

控制器文件(可能需要清理)

@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…


   

使用来自resource1 和resource2 的示例组装的Java 配置文件。 (可能需要清理)

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


在 applicationContext.xml 中添加以下 bean 定义

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

【问题讨论】:

applicationcontext.xml 是什么,它是您的调度程序 servlet 上下文吗? ***.com/questions/3652090/… MVC 标记和转换器可能在调度程序 servlet 使用的上下文中声明 - 而不是根应用程序 Web 应用程序上下文。 不要定义自己的转换服务(或自定义 UserConverter)。删除你的 conversionService bean 定义! @Pavel 你能详细说明一下吗?在这种情况下,我认为转换服务处于错误的上下文中,因此它被调度程序 servlet 中的那个所掩盖,但通常自定义 bean 的显式声明没有任何问题。 @BorisTreukhov 声明自定义 bean 没有错。然而,自定义 UserConverter 正在复制 DomainClassConverter 应该做的事情,它的存在使问题变得更加复杂。根据conversionService,这就是我有点过于鲁莽的地方......肯定需要一个。 由于某种原因,XML 存在问题。我对 Java Config 进行了同样的尝试,并且 DomainClassConverter 工作得非常好。 【参考方案1】:

URL 是String,所以id 也是String。因此,您的服务必须能够将String 转换为NewUser,而不是您的Integer

【讨论】:

该评论是针对 UserConverter 类的吗?即使这样,错误也是一样的: public NewUser convert(String username) return newUserRepository.findOne(Integer.parseInt(username)); @oneworld 不确定是否有必要,但我使用的是FormattingConversionServiceFactoryBean,而不是ConversionServiceFactoryBean【参考方案2】:

请查看relevant section of the reference documentation,了解配置DomainClassConverter 的正确方法。

【讨论】:

我尝试了每个解决方案,但不起作用。我还在这里尝试了您的其他解决方案-jira.springsource.org/si/jira.issueviews:issue-html/… DomainClassConverter 仍然存在问题吗?最新的文档也略有不同docs.spring.io/spring-data/data-commons/docs/current/reference/… 我在互联网上搜索并没有找到好的工作示例代码。每个网站都有自己的做事方式,但只提供零碎的信息。有没有我可以完全参考的示例项目? 这个问题还在徘徊吗? forum.spring.io/forum/spring-projects/data/…【参考方案3】:

ref 说

目前存储库必须实现 CrudRepository 有资格被发现进行转化。

不应该是这个原因吗?

【讨论】:

JpaRepository 扩展了 PagingAndSortingRepository,后者又扩展了 CrudRepository,所以这应该不是问题【参考方案4】:

此配置设置自定义转换服务并将其传递给检测和设置控制器的注释扫描机制:

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

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

这是自定义控制器的代码,客户是像用户这样的域类:

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

试试这个,因为它适用于版本 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>

【讨论】:

这是基于docs.spring.io/spring-data/jpa/docs/1.4.x/reference/html/…,我已经使用它并且可以确认它正在工作 我真的需要为域转换器类编写自定义转换器吗?这不是在贬低目的吗?

以上是关于在 Spring MVC 中使用 Spring 的 DomainClassConverter 的问题的主要内容,如果未能解决你的问题,请参考以下文章

如何在带有注解配置的spring mvc中使用spring数据

我们如何在 Spring MVC 项目中使用 Spring Cloud Sleuth?

spring mvc 是啥

我们可以在一个项目中同时使用 Java Spring mvc 和 Spring Boot 吗?

使用spring mvc 怎么在后台接收空的整型数据。

spring mvc中如何过滤form提交数据中的空格?