Spring Data Rest Hateoas Resources 对象在客户端服务中与 Feign 客户端一起使用时为空

Spring Data Rest Hateoas Resources object empty when consuming with Feign client in client service

我最近尝试使用 Spring Boot 1.4.1 和 Spring Cloud Camden SR1 复制此视频和此 github 存储库中的 Spring Cloud 示例,但遇到了问题。当客户端服务使用 Feign 客户端调用生产者服务时,反序列化的资源没有内容或链接。

进一步挖掘后,问题似乎与 MappingJackson2HttpMessageConverter 和它的 Jackson ObjectMapper 有关。当我查看伪装客户端的 SpringDecoder 使用的 HttpMessageConverters 时,我看到了 2 个 MappingJackson2HttpMessageConverters 但转换器的 ObjectMappers 都没有注册 Jackson2HalModule。

Spring Data Rest 预约服务应用类:

1
2
3
4
5
6
7
@EnableDiscoveryClient
@SpringBootApplication
public class ReservationServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReservationServiceApplication.class, args);
    }
}

预订舱位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
public class Reservation extends BaseEntity {
    private String reservationName;

    public Reservation(String reservationName) {
        this.reservationName = reservationName;
    }

    protected Reservation() {}

    public String getReservationName() {
        return reservationName;
    }
}

BaseEntity 类具有 ID 和版本。

ReservationRepository 类:

1
2
3
@RepositoryRestResource
public interface ReservationRepository extends CrudRepository<Reservation, Integer> {
}

预订服务 pom.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    reservation-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>reservation-service</name>
    <description>CRUD Service for Reservations</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        spring-boot-starter-parent</artifactId>
        <version>1.4.1.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <jackson.version>2.8.4</jackson.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                spring-cloud-dependencies</artifactId>
                <version>Camden.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Spring Dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-zipkin</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-hateoas</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-data-jpa</artifactId>
            <exclusions>
                <exclusion>
                    tomcat-jdbc</artifactId>
                    <groupId>org.apache.tomcat</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-eureka</artifactId>
        </dependency>

        <!-- Jackson Dependencies -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            jackson-annotations</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            jackson-datatype-jdk8</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            jackson-module-parameter-names</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <!-- Other Dependencies -->
        <dependency>
            <groupId>javax.inject</groupId>
            javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            commons-collections4</artifactId>
            <version>4.1</version>
        </dependency>
        <dependency>
            <groupId>org.flywaydb</groupId>
            flyway-core</artifactId>
            <version>4.0.3</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            HikariCP</artifactId>
            <version>2.5.0</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            hibernate-java8</artifactId>
            <version>5.0.9.Final</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

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

预订客户端应用程序类:

1
2
3
4
5
6
7
8
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ReservationClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReservationClientApplication.class, args);
    }
}

Reservation Client Feign Client 类:

1
2
3
4
5
@FeignClient("reservation-service")
public interface ReservationServiceClient {
    @RequestMapping(method = RequestMethod.GET, value ="reservations")
    Resources<Reservation> getReservations();
}

Reservation Client ReservationApiGateway 控制器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping("reservations")
public class ReservationApiGateway {
    private final ReservationServiceClient client;

    public ReservationApiGateway(ReservationServiceClient client) {
        this.client = client;
    }

    @GetMapping
    public Collection<Reservation> reservations() {
        return client.getReservations().getContent();
    }
}

预订客户端 pom.xml

http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
    <groupId>com.example</groupId>
    reservation-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>reservation-client</name>
    <description>Example reservation client</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        spring-boot-starter-parent</artifactId>
        <version>1.4.1.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <jackson.version>2.8.4</jackson.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                spring-cloud-dependencies</artifactId>
                <version>Camden.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-zipkin</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-hateoas</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-feign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-zuul</artifactId>
        </dependency>

        <!-- Jackson Dependencies -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            jackson-annotations</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            jackson-datatype-jdk8</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            jackson-module-parameter-names</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <!-- Other Dependencies -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            commons-collections4</artifactId>
            <version>4.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

当我点击客户端上的 /reservations 端点时,我得到一个空的 JSON 数组 ([])。当我在 feign 客户端上启用 FULL 登录时,我会从预订服务中看到正确的预订。

最后,我尝试添加:

1
@EnableHypermediaSupport(type=EnableHypermediaSupport.HypermediaType.HAL)

到 Reservation Client 应用程序类。这导致保留资源被客户端反序列化并正确返回。不幸的是,我尝试了一些其他类,它们具有 Java 8 LocalDateTime 字段,这些字段需要在 ObjectMapper 上注册 Java 8 Jackson 模块,但是当我查看正在使用的 ObjectMapper 时,唯一注册的模块是 Jackson2HalModule。

如果我这样做不正确或是否存在错误,我将非常感谢任何建议。谢谢!


经过更多的挖掘,我能够解决这个问题。客户端应用程序需要 spring-data-rest 依赖项来正确设置消息转换器。不幸的是,考虑到客户端应用程序不需要 Spring 数据存储库,这似乎有点违反直觉。