Переполнение стека Spring Boot & Couchbase - PullRequest
0 голосов
/ 12 января 2020

Привет У меня есть два простых класса в двунаправленной взаимосвязи, которые вызывают бесконечную рекурсию, приводящую к возможному переполнению стека при сериализации в JSON. Я использую Spring Boot 2.2.2 стартовый проект для Couchbase и Spring Web зависимостей. Мой файл maven pom.xml выглядит следующим образом:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.stevie</groupId>
    <artifactId>stack-overflow-error</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>stack-overflow-error</name>
    <description>Reproduce Couchbase API Failure</description>

    <properties>
        <java.version>13</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-couchbase</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Классы моего домена следующие:

Домашнее хозяйство. java


import org.springframework.data.couchbase.core.mapping.Document;

import com.couchbase.client.java.repository.annotation.Field;
import com.couchbase.client.java.repository.annotation.Id;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@Document
@JsonIdentityInfo(
          generator = ObjectIdGenerators.PropertyGenerator.class, 
          property = "@id")
public final  class Household {

    @Id
    private final String key;

    @Field
    private String type = "household";

    @Field
    private final String headSurname;

    @Field
    private final String headForename;

    @Field
    private final List<Member> members;

    public static Household of(String key, String headSurname, String headForename) {
        return new Household(key, headSurname, headForename);
    }

    Household(String key, String headSurname, String headForename) {
        super();
        this.key = key;
        this.headSurname = headSurname;
        this.headForename = headForename;
        this.members = new ArrayList<>();
    }

    public String getKey() {
        return key;
    }

    public String getHeadSurname() {
        return headSurname;
    }

    public String getHeadForename() {
        return headForename;
    }

    @JsonManagedReference
    public List<Member> getMembers() {
        return members;
    }

    @Override
    public String toString() {
        return String.format("Household [key=%s, headSurname=%s, headForename=%s]", key,
                headSurname, headForename);
    }

}

Участник. java


import org.springframework.data.couchbase.core.mapping.Document;

import com.couchbase.client.java.repository.annotation.Field;
import com.couchbase.client.java.repository.annotation.Id;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@Document
@JsonIdentityInfo(
          generator = ObjectIdGenerators.PropertyGenerator.class, 
          property = "@id")
public final class Member {

    @Id
    private final String key;

    @Field
    private final String name;

    @Field
    private final Household household;


    public static Member of(String key, String name, Household household) {
        return new Member(key, name, household);
    }

    Member(String key, String name, Household household) {
        super();
        this.key = key;
        this.name = name;
        this.household = household;
    }

    public String getKey() {
        return key;
    }

    public String getName() {
        return name;
    }

    @JsonBackReference
    public Household getHousehold() {
        return household;
    }

    @Override
    public String toString() {
        return String.format(
                "Member [key=%s, name=%s]", key, name);
    }


}

Мой модульный тест (JUnit 5):


import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.stevie.overflow.StackOverflowErrorApplication;
import org.stevie.overflow.domain.Household;
import org.stevie.overflow.domain.Member;

import com.couchbase.client.java.Bucket;

@SpringBootTest(classes = StackOverflowErrorApplication.class)
class HouseholdRepositoryTest {

    @Autowired
    private HouseholdRepository repo;

    @Autowired
    private CouchbaseTemplate template;

    @BeforeAll
    static void setUpBeforeClass() throws Exception {
    }

    @AfterAll
    static void tearDownAfterClass() throws Exception {
    }

    @BeforeEach
    void setUp() throws Exception {
    }

    @AfterEach
    void tearDown() throws Exception {
    }

    @Test
    void throwsStackOverflowExceptionTest() {
        String key = manuallyGenerateKey("household");
        Household house = Household.of(key, "BLOGGS", "Joe");
        key = manuallyGenerateKey("member");
        Member m1 = Member.of(key, "Joe Bloggs", house);
        house.getMembers().add(m1);
        /** Following code throws Stack overflow exception!!!!! **/
        house = repo.save(house);
    }

    private String manuallyGenerateKey(String prefix) {
        Bucket bucket = template.getCouchbaseBucket();
        Long nextId = bucket.counter("counter::" + prefix, 1, 100).content();
        String key = prefix + "::" + nextId;
        return key;
    }

}

Я хочу, чтобы JSON для домохозяйства содержало его объекты-члены, а член содержал его объект-домохозяйство. Я пробовал аннотации Джексона, такие как @JsonManagedReference и @JsonIdentityInfo, но Spring, кажется, игнорирует их. Благодаря.

...