Java 使用spring反序列化JSON:未解析的前向引用异常

Java 使用spring反序列化JSON:未解析的前向引用异常,java,spring,jackson,json-deserialization,Java,Spring,Jackson,Json Deserialization,我与Spring一起从事API Rest项目。 我有一个服务“createMaterials”,它将JSON作为参数数据: 装备对象的JSON { "agence": 1, "code": "001", "type": "MyType" } “装备”与“代理”之间存在多对一的关系。我放置了一个@JsonIdentityInfo标记来使用代理的Id,而不是代理的对象(在看到之后) 但当我在POST/Materials上发送JSON时,我有一个例外: 2017-05-16 18:00

我与Spring一起从事API Rest项目。 我有一个服务“createMaterials”,它将JSON作为参数数据:

装备对象的JSON

{
  "agence": 1, 
  "code": "001",
  "type": "MyType"
}
“装备”与“代理”之间存在多对一的关系。我放置了一个@JsonIdentityInfo标记来使用代理的Id,而不是代理的对象(在看到之后)

但当我在POST/Materials上发送JSON时,我有一个例外:

2017-05-16 18:00:53.021  WARN 8080 --- [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Unresolved forward references for: 
 at [Source: java.io.PushbackInputStream@767dfd7a; line: 5, column: 1]Object id [1] (for fr.app.domain.Agence) at [Source: java.io.PushbackInputStream@767dfd7a; line: 2, column: 14].; nested exception is com.fasterxml.jackson.databind.deser.UnresolvedForwardReference: Unresolved forward references for: 
 at [Source: java.io.PushbackInputStream@767dfd7a; line: 5, column: 1]Object id [1] (for fr.app.domain.Agence) at [Source: java.io.PushbackInputStream@767dfd7a; line: 2, column: 14].
经过多次研究,我看到了ObjectDresolver在JsonIdentityInfo中的使用。。。 但我认为这不是最好的解决办法。这就是为什么我请求你帮助找出问题的根源。 谢谢

materialcontroller.java

package fr.app.controllers;

import fr.app.domain.Materiel;
import fr.app.services.MaterielService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Collection;

@RestController @RequestMapping(value = "/materiels")
public class MaterielController {
    @Resource
    private MaterielService materielService;

    @RequestMapping(method = RequestMethod.POST)
    public Materiel createMateriel(@RequestBody Materiel materiel) {
        return this.materielService.createMateriel(materiel);
    }

    @RequestMapping(method = RequestMethod.GET)
    public Collection<Materiel> getAllMateriels() {
        return this.materielService.getAllMateriels();
    }

    @RequestMapping(value = "/{code}", method = RequestMethod.GET)
    public Materiel getMaterielByCode(@PathVariable(value = "code") String code) {
        //find materiel by code
        return this.materielService.getMaterielByCode(code);

    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public void deleteMateriel(@PathVariable(value = "id") Long id) {
        this.materielService.deleteMateriel(id);

    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public Materiel updateMateriel(@PathVariable(value = "id") Long id, @RequestBody
            Materiel materiel) {

            materiel.setIdMateriel(id);

        return this.materielService.updateMateriel(materiel);

    }
}
package fr.app.domain;

import com.fasterxml.jackson.annotation.*;

import javax.persistence.*;

@Entity
public class Materiel {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long idMateriel;

    @Column(name = "type_materiel", nullable = false)
    private String type;

    @Column(name = "code_materiel", unique = true, nullable = false)
    private String code;

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "idAgence")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "agence")
    private Agence agence;

    public Materiel() {    }

    public Materiel(String type, String code, String dateScan) {
        this.type = type;
        this.code = code;
        this.dateScan = dateScan;
    }

    public Long getIdMateriel() {
        return idMateriel;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Agence getAgence() {
        return agence;
    }

    public void setAgence(Agence agence) {
        if(this.agence != null)
            this.agence.deleteMateriel(this);
        this.agence = agence;
        this.agence.addMateriel(this);
    }
}
package fr.app.services;

import fr.app.domain.Materiel;
import fr.app.repositories.MaterielRepository;
import org.apache.commons.collections.IteratorUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collection;

@Service(value = "materielService")
public class MaterielServiceImpl implements MaterielService {
    @Resource
    private MaterielRepository materielRepository;

    ...

    @Override
    public Materiel createMateriel(Materiel materiel) {
        return this.materielRepository.save(materiel);
    }

   ...
}
package fr.app.domain;

import com.fasterxml.jackson.annotation.*;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
public class Agence {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long idAgence;

    @Column(name = "nom",unique = true, nullable = false)
    private String nom;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "agence")
    private Set<Materiel> materiels = new HashSet<Materiel>();

    public Agence() { }

    public Agence(String nom) {
        this.nom = nom;
    }

    public Long getIdAgence() {
        return idAgence;
    }

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public Set<Materiel> getMateriels() {
        return materiels;
    }

    public void setMateriels(Set<Materiel> materiels) {
        this.materiels = materiels;
    }

    public void addMateriel(Materiel materiel) {
        this.materiels.add(materiel);
    }

    public void deleteMateriel(Materiel materiel) {
        this.materiels.remove(materiel);
    }

}
materialservice.java

package fr.app.controllers;

import fr.app.domain.Materiel;
import fr.app.services.MaterielService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Collection;

@RestController @RequestMapping(value = "/materiels")
public class MaterielController {
    @Resource
    private MaterielService materielService;

    @RequestMapping(method = RequestMethod.POST)
    public Materiel createMateriel(@RequestBody Materiel materiel) {
        return this.materielService.createMateriel(materiel);
    }

    @RequestMapping(method = RequestMethod.GET)
    public Collection<Materiel> getAllMateriels() {
        return this.materielService.getAllMateriels();
    }

    @RequestMapping(value = "/{code}", method = RequestMethod.GET)
    public Materiel getMaterielByCode(@PathVariable(value = "code") String code) {
        //find materiel by code
        return this.materielService.getMaterielByCode(code);

    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public void deleteMateriel(@PathVariable(value = "id") Long id) {
        this.materielService.deleteMateriel(id);

    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public Materiel updateMateriel(@PathVariable(value = "id") Long id, @RequestBody
            Materiel materiel) {

            materiel.setIdMateriel(id);

        return this.materielService.updateMateriel(materiel);

    }
}
package fr.app.domain;

import com.fasterxml.jackson.annotation.*;

import javax.persistence.*;

@Entity
public class Materiel {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long idMateriel;

    @Column(name = "type_materiel", nullable = false)
    private String type;

    @Column(name = "code_materiel", unique = true, nullable = false)
    private String code;

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "idAgence")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "agence")
    private Agence agence;

    public Materiel() {    }

    public Materiel(String type, String code, String dateScan) {
        this.type = type;
        this.code = code;
        this.dateScan = dateScan;
    }

    public Long getIdMateriel() {
        return idMateriel;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Agence getAgence() {
        return agence;
    }

    public void setAgence(Agence agence) {
        if(this.agence != null)
            this.agence.deleteMateriel(this);
        this.agence = agence;
        this.agence.addMateriel(this);
    }
}
package fr.app.services;

import fr.app.domain.Materiel;
import fr.app.repositories.MaterielRepository;
import org.apache.commons.collections.IteratorUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collection;

@Service(value = "materielService")
public class MaterielServiceImpl implements MaterielService {
    @Resource
    private MaterielRepository materielRepository;

    ...

    @Override
    public Materiel createMateriel(Materiel materiel) {
        return this.materielRepository.save(materiel);
    }

   ...
}
package fr.app.domain;

import com.fasterxml.jackson.annotation.*;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
public class Agence {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long idAgence;

    @Column(name = "nom",unique = true, nullable = false)
    private String nom;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "agence")
    private Set<Materiel> materiels = new HashSet<Materiel>();

    public Agence() { }

    public Agence(String nom) {
        this.nom = nom;
    }

    public Long getIdAgence() {
        return idAgence;
    }

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public Set<Materiel> getMateriels() {
        return materiels;
    }

    public void setMateriels(Set<Materiel> materiels) {
        this.materiels = materiels;
    }

    public void addMateriel(Materiel materiel) {
        this.materiels.add(materiel);
    }

    public void deleteMateriel(Materiel materiel) {
        this.materiels.remove(materiel);
    }

}
agent.java

package fr.app.controllers;

import fr.app.domain.Materiel;
import fr.app.services.MaterielService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Collection;

@RestController @RequestMapping(value = "/materiels")
public class MaterielController {
    @Resource
    private MaterielService materielService;

    @RequestMapping(method = RequestMethod.POST)
    public Materiel createMateriel(@RequestBody Materiel materiel) {
        return this.materielService.createMateriel(materiel);
    }

    @RequestMapping(method = RequestMethod.GET)
    public Collection<Materiel> getAllMateriels() {
        return this.materielService.getAllMateriels();
    }

    @RequestMapping(value = "/{code}", method = RequestMethod.GET)
    public Materiel getMaterielByCode(@PathVariable(value = "code") String code) {
        //find materiel by code
        return this.materielService.getMaterielByCode(code);

    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public void deleteMateriel(@PathVariable(value = "id") Long id) {
        this.materielService.deleteMateriel(id);

    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public Materiel updateMateriel(@PathVariable(value = "id") Long id, @RequestBody
            Materiel materiel) {

            materiel.setIdMateriel(id);

        return this.materielService.updateMateriel(materiel);

    }
}
package fr.app.domain;

import com.fasterxml.jackson.annotation.*;

import javax.persistence.*;

@Entity
public class Materiel {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long idMateriel;

    @Column(name = "type_materiel", nullable = false)
    private String type;

    @Column(name = "code_materiel", unique = true, nullable = false)
    private String code;

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "idAgence")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "agence")
    private Agence agence;

    public Materiel() {    }

    public Materiel(String type, String code, String dateScan) {
        this.type = type;
        this.code = code;
        this.dateScan = dateScan;
    }

    public Long getIdMateriel() {
        return idMateriel;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Agence getAgence() {
        return agence;
    }

    public void setAgence(Agence agence) {
        if(this.agence != null)
            this.agence.deleteMateriel(this);
        this.agence = agence;
        this.agence.addMateriel(this);
    }
}
package fr.app.services;

import fr.app.domain.Materiel;
import fr.app.repositories.MaterielRepository;
import org.apache.commons.collections.IteratorUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collection;

@Service(value = "materielService")
public class MaterielServiceImpl implements MaterielService {
    @Resource
    private MaterielRepository materielRepository;

    ...

    @Override
    public Materiel createMateriel(Materiel materiel) {
        return this.materielRepository.save(materiel);
    }

   ...
}
package fr.app.domain;

import com.fasterxml.jackson.annotation.*;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
public class Agence {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long idAgence;

    @Column(name = "nom",unique = true, nullable = false)
    private String nom;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "agence")
    private Set<Materiel> materiels = new HashSet<Materiel>();

    public Agence() { }

    public Agence(String nom) {
        this.nom = nom;
    }

    public Long getIdAgence() {
        return idAgence;
    }

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public Set<Materiel> getMateriels() {
        return materiels;
    }

    public void setMateriels(Set<Materiel> materiels) {
        this.materiels = materiels;
    }

    public void addMateriel(Materiel materiel) {
        this.materiels.add(materiel);
    }

    public void deleteMateriel(Materiel materiel) {
        this.materiels.remove(materiel);
    }

}
包fr.app.domain;
导入com.fasterxml.jackson.annotation.*;
导入javax.persistence.*;
导入java.util.HashSet;
导入java.util.Set;
@实体
公共类代理{
@身份证
@GeneratedValue(策略=GenerationType.AUTO)
私人长期代理;
@列(name=“nom”,unique=true,nullable=false)
私有字符串名称;
@OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy=“agence”)
私有集合materials=newhashset();
公共机构(){}
公共代理(字符串名称){
this.nom=nom;
}
公共长期代理(){
返回代理;
}
公共字符串getNom(){
返回名称;
}
公共无效集合名(字符串名){
this.nom=nom;
}
公共集getMaterials(){
退货材料;
}
公共空间集合材料(集合材料){
this.materials=materials;
}
公共装备(装备){
本.物料.添加(物料);
}
公共物资(装备){
此。物料。移除(物料);
}
}
pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.0</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.2.9.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.2.9.Final</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.6.1</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.2.2</version>
        <scope>compile</scope>
    </dependency>

    <!-- Needed for JSON View -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.8.6</version>
    </dependency>
</dependencies>

org.springframework.boot
spring引导启动器数据jpa
mysql
mysql连接器java
5.1.6
org.springframework.boot
弹簧起动试验
测试
org.springframework.boot
SpringBootStarterWeb
公地收藏
公地收藏
3
org.hibernate
冬眠核心
5.2.9.最终版本
org.hibernate
休眠实体管理器
5.2.9.最终版本
伊奥·斯普林福克斯
springfox-Swagger 2
2.6.1
伊奥·斯普林福克斯
springfox招摇过市用户界面
2.2.2
编译
com.fasterxml.jackson.core
杰克逊数据绑定
2.8.6

您有两个域:装备和代理。当Jackson试图反序列化装备时,它调用方法
getAgence
。此方法返回一个代理对象,Jackson也将对该对象进行反序列化。当Jackson调用(Agence的)getMeteries方法时,它返回一组也将反序列化的装备。问题是Jackson将再次尝试反序列化集合中的每种装备。同样的so问题。

对于最后一个异常(utf-8),您可以更改控制器的签名,如下所示:

@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Bean> create(HttpServletRequest  request) throws IOException {
    // getting the posted value
    String body = CharStreams.toString(request.getReader());
    Bean bean = new ObjectMapper().readValue(body, service.getBeanClass());
    bean.setId(null);
    Bean saved = service.save(bean);

    URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(saved.getId()).toUri();
    return ResponseEntity.created(location).body(saved);
}
@RequestMapping(method=RequestMethod.POST,products=MediaType.APPLICATION\u JSON\u VALUE,consumes=MediaType.APPLICATION\u JSON\u VALUE)
公共响应属性创建(HttpServletRequest请求)引发IOException{
//获取过账价值
String body=CharStreams.toString(request.getReader());
Bean=newObjectMapper().readValue(body,service.getBeanClass());
setId(null);
保存的Bean=service.save(Bean);
URI location=ServletUriComponentsBuilder.fromCurrentRequest().path(“/{id}”).buildAndExpand(saved.getId()).toUri();
返回ResponseEntity.created(location).body(saved);
}
使用此解决方案,您可以完全控制映射


享受

好的,我发现了问题

最后,我需要添加解析器。因此,我找到了implement
ObjectdResolver
的一个示例:

import com.fasterxml.jackson.annotation.ObjectIdGenerator;
import com.fasterxml.jackson.annotation.ObjectIdResolver;

import javax.persistence.EntityManager;

/**
 * @author fta on 20.12.15.
 */

public class EntityIdResolver
        implements ObjectIdResolver {

    private EntityManager entityManager;

    public EntityIdResolver(
            final EntityManager entityManager) {

        this.entityManager = entityManager;

    }

    @Override
    public void bindItem(
            final ObjectIdGenerator.IdKey id,
            final Object pojo) {

    }

    @Override
    public Object resolveId(final ObjectIdGenerator.IdKey id) {

        return this.entityManager.find(id.scope, id.key);
    }

    @Override
    public ObjectIdResolver newForDeserialization(final Object context) {

        return this;
    }

    @Override
    public boolean canUseFor(final ObjectIdResolver resolverType) {

        return false;
    }

}
接下来我添加
@JsonIdentityInfo

@Entity
@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "idAgence",
        resolver = EntityIdResolver.class,
        scope=Agence.class)
public class Agence {
    // ... 
}

由于一对多关系,您似乎有一个循环对象图。对于那些您可以使用

它可以处理循环对象图的序列化和反序列化(自Jackson 2.5.1以来)


Matiriel
引用了
Agency
Agency
引用了
Matiriel
,导致循环引用。您需要向其中一个添加
@JsonIgnore