我有一个基于spring-data-rest的项目,也有一些自定义端点。

为了发送POST数据,我使用json之类的

{
 "action": "REMOVE",
 "customer": "http://localhost:8080/api/rest/customers/7"
}

这对于s​​pring-data-rest来说很好,但不适用于自定义 Controller 。

例如:
public class Action {
    public ActionType action;
    public Customer customer;
}

@RestController
public class ActionController(){
  @Autowired
  private ActionService actionService;

  @RestController
  public class ActionController {
  @Autowired
  private ActionService actionService;

  @RequestMapping(value = "/customer/action", method = RequestMethod.POST)
  public ResponseEntity<ActionResult> doAction(@RequestBody Action action){
    ActionType actionType = action.action;
    Customer customer = action.customer;//<------There is a problem
    ActionResult result = actionService.doCustomerAction(actionType, customer);
    return ResponseEntity.ok(result);
  }
}

当我打电话
curl -v -X POST -H "Content-Type: application/json" -d '{"action": "REMOVE","customer": "http://localhost:8080/api/rest/customers/7"}' http://localhost:8080/customer/action

我有一个答案
{
"timestamp" : "2016-05-12T11:55:41.237+0000",
"status" : 400,
"error" : "Bad Request",
"exception" : "org.springframework.http.converter.HttpMessageNotReadableException",
"message" : "Could not read document: Can not instantiate value of type [simple type, class model.user.Customer] from String value ('http://localhost:8080/api/rest/customers/7'); no single-String constructor/factory method\n at [Source: java.io.PushbackInputStream@73af10c6; line: 1, column: 33] (through reference chain: api.controller.Action[\"customer\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class logic.model.user.Customer] from String value ('http://localhost:8080/api/rest/customers/7'); no single-String constructor/factory method\n at [Source: java.io.PushbackInputStream@73af10c6; line: 1, column: 33] (through reference chain: api.controller.Action[\"customer\"])",
"path" : "/customer/action"
* Closing connection 0
}

因为case spring无法将URI转换为Customer实体。

有什么方法可以使用spring-data-rest机制通过实体的URI来解析实体?

我只有一个主意-将自定义JsonDeserializer与解析URI一起使用以提取entityId并向存储库发出请求。但是,如果我有类似“http://localhost:8080/api/rest/customers/8/product”这样的URI,则这种策略对我没有帮助,在这种情况下,我没有 product.Id 值。

最佳答案

我也很长时间以来一直遇到相同的问题,并通过以下方式解决了它。 @Florian走在正确的轨道上,由于他的建议,我找到了一种使转换自动进行的方法。需要几个部分:

  • 一种转换服务,用于启用从URI到实体的转换(利用框架随附的UriToEntityConverter)
  • 解串器,用于检测何时适合调用转换器(我们不想弄乱默认的SDR行为)
  • 一个自定义的Jackson模块,用于将所有内容推送到SDR

  • 对于第1点,可以将实现范围缩小到以下几点
    import org.springframework.context.ApplicationContext;
    import org.springframework.data.mapping.context.PersistentEntities;
    import org.springframework.data.repository.support.DomainClassConverter;
    import org.springframework.data.rest.core.UriToEntityConverter;
    import org.springframework.format.support.DefaultFormattingConversionService;
    
    public class UriToEntityConversionService extends DefaultFormattingConversionService {
    
       private UriToEntityConverter converter;
    
       public UriToEntityConversionService(ApplicationContext applicationContext, PersistentEntities entities) {
          new DomainClassConverter<>(this).setApplicationContext(applicationContext);
    
           converter = new UriToEntityConverter(entities, this);
    
           addConverter(converter);
       }
    
       public UriToEntityConverter getConverter() {
          return converter;
       }
    }
    

    对于第二点,这是我的解决方案
    import com.fasterxml.jackson.databind.BeanDescription;
    import com.fasterxml.jackson.databind.DeserializationConfig;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
    import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
    import com.fasterxml.jackson.databind.deser.ValueInstantiator;
    import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator;
    import your.domain.RootEntity; // <-- replace this with the import of the root class (or marker interface) of your domain
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.convert.TypeDescriptor;
    import org.springframework.data.mapping.PersistentEntity;
    import org.springframework.data.mapping.context.PersistentEntities;
    import org.springframework.data.rest.core.UriToEntityConverter;
    import org.springframework.util.Assert;
    
    import java.io.IOException;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.util.Optional;
    
    
    public class RootEntityFromUriDeserializer extends BeanDeserializerModifier {
    
       private final UriToEntityConverter converter;
       private final PersistentEntities repositories;
    
       public RootEntityFromUriDeserializer(PersistentEntities repositories, UriToEntityConverter converter) {
    
           Assert.notNull(repositories, "Repositories must not be null!");
           Assert.notNull(converter, "UriToEntityConverter must not be null!");
    
           this.repositories = repositories;
           this.converter = converter;
       }
    
       @Override
       public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
    
           PersistentEntity<?, ?> entity = repositories.getPersistentEntity(beanDesc.getBeanClass());
    
           boolean deserializingARootEntity = entity != null && RootEntity.class.isAssignableFrom(entity.getType());
    
           if (deserializingARootEntity) {
               replaceValueInstantiator(builder, entity);
           }
    
           return builder;
       }
    
       private void replaceValueInstantiator(BeanDeserializerBuilder builder, PersistentEntity<?, ?> entity) {
          ValueInstantiator currentValueInstantiator = builder.getValueInstantiator();
    
           if (currentValueInstantiator instanceof StdValueInstantiator) {
    
              EntityFromUriInstantiator entityFromUriInstantiator =
                    new EntityFromUriInstantiator((StdValueInstantiator) currentValueInstantiator, entity.getType(), converter);
    
              builder.setValueInstantiator(entityFromUriInstantiator);
           }
       }
    
       private class EntityFromUriInstantiator extends StdValueInstantiator {
          private final Class entityType;
          private final UriToEntityConverter converter;
    
          private EntityFromUriInstantiator(StdValueInstantiator src, Class entityType, UriToEntityConverter converter) {
             super(src);
             this.entityType = entityType;
             this.converter = converter;
          }
    
          @Override
          public Object createFromString(DeserializationContext ctxt, String value) throws IOException {
             URI uri;
             try {
                uri = new URI(value);
             } catch (URISyntaxException e) {
                return super.createFromString(ctxt, value);
             }
    
             return converter.convert(uri, TypeDescriptor.valueOf(URI.class), TypeDescriptor.valueOf(entityType));
          }
       }
    }
    

    然后对于第3点,在自定义RepositoryRestConfigurerAdapter中,
    public class MyRepositoryRestConfigurer extends RepositoryRestConfigurerAdapter {
       @Override
       public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
          objectMapper.registerModule(new SimpleModule("URIDeserializationModule"){
    
             @Override
             public void setupModule(SetupContext context) {
                UriToEntityConverter converter = conversionService.getConverter();
    
                RootEntityFromUriDeserializer rootEntityFromUriDeserializer = new RootEntityFromUriDeserializer(persistentEntities, converter);
    
                context.addBeanDeserializerModifier(rootEntityFromUriDeserializer);
             }
          });
       }
    }
    

    这对我来说很顺利,并且不会干扰来自框架的任何转换(我们有许多自定义端点)。在第2点中,目的是仅在以下情况下启用URI的实例化:
  • 要反序列化的实体是根实体(因此没有属性)
  • 提供的字符串是实际的URI(否则,它会退回到默认行为)
  • 07-24 09:38
    查看更多