Fix GH-127 add support for HATEOAS (#140)

* Fix gh-127, HATEOAS support

* Fix gh-127, HATEOAS support tests

* Add DefaultCurieProvider for HAL Jackson http converter
This commit is contained in:
hectorespert
2019-06-14 02:04:25 +02:00
committed by Ryan Baxter
parent f76a81fe6b
commit 2e8a5833a9
13 changed files with 622 additions and 2 deletions

View File

@@ -482,7 +482,7 @@ The following feign client uses the `Params` class by using the `@SpringQueryMap
[source,java,indent=0]
----
@FeignClient("demo")
public class DemoTemplate {
public interface DemoTemplate {
@GetMapping(path = "/demo")
String demoEndpoint(@SpringQueryMap Params params);
@@ -490,3 +490,23 @@ public class DemoTemplate {
----
If you need more control over the generated query parameter map, you can implement a custom `QueryMapEncoder` bean.
=== HATEOAS support
Spring provides some APIs to create REST representations that follow the https://en.wikipedia.org/wiki/HATEOAS[HATEOAS] principle, https://spring.io/projects/spring-hateoas[Spring Hateoas] and https://spring.io/projects/spring-data-rest[Spring Data REST].
If your project use the `org.springframework.boot:spring-boot-starter-hateoas` starter
or the `org.springframework.boot:spring-boot-starter-data-rest` starter, Feign HATEOAS support is enabled by default.
When HATEOAS support is enabled, Feign clients are allowed to serialize
and deserialize HATEOAS representation models: https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/EntityModel.html[EntityModel], https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/CollectionModel.html[CollectionModel] and https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/PagedModel.html[PagedModel].
[source,java,indent=0]
----
@FeignClient("demo")
public interface DemoTemplate {
@GetMapping(path = "/stores")
CollectionModel<Store> getStores();
}
----

View File

@@ -50,6 +50,11 @@
<artifactId>spring-boot-starter-reactor-netty</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
@@ -208,6 +213,11 @@
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hateoas;
import java.util.Arrays;
import java.util.Collections;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.mediatype.hal.CurieProvider;
import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider;
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
import static org.springframework.hateoas.MediaTypes.HAL_JSON;
import static org.springframework.hateoas.MediaTypes.HAL_JSON_UTF8;
/**
* @author Hector Espert
*/
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(RepresentationModel.class)
@AutoConfigureAfter({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
RepositoryRestMvcAutoConfiguration.class })
@AutoConfigureBefore(HypermediaAutoConfiguration.class)
public class FeignHalAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter(
ObjectProvider<ObjectMapper> objectMapper,
ObjectProvider<HalConfiguration> halConfiguration,
ObjectProvider<LinkRelationProvider> relProvider,
ObjectProvider<CurieProvider> curieProvider,
ObjectProvider<MessageSourceAccessor> linkRelationMessageSource) {
ObjectMapper mapper = objectMapper.getIfAvailable(ObjectMapper::new).copy();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
HalConfiguration configuration = halConfiguration
.getIfAvailable(HalConfiguration::new);
CurieProvider curieProviderInstance = curieProvider
.getIfAvailable(() -> new DefaultCurieProvider(Collections.emptyMap()));
Jackson2HalModule.HalHandlerInstantiator halHandlerInstantiator = new Jackson2HalModule.HalHandlerInstantiator(
relProvider.getObject(), curieProviderInstance,
linkRelationMessageSource.getObject(), configuration);
mapper.setHandlerInstantiator(halHandlerInstantiator);
if (!Jackson2HalModule.isAlreadyRegisteredIn(mapper)) {
Jackson2HalModule halModule = new Jackson2HalModule();
mapper.registerModule(halModule);
}
TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter(
RepresentationModel.class);
converter.setSupportedMediaTypes(Arrays.asList(HAL_JSON, HAL_JSON_UTF8));
converter.setObjectMapper(mapper);
return converter;
}
}

View File

@@ -1,5 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

View File

@@ -27,6 +27,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@@ -86,7 +87,8 @@ public class FeignPageableEncodingTests {
@EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@SpringBootApplication(
scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
scanBasePackages = "org.springframework.cloud.openfeign.encoding.app",
exclude = { RepositoryRestMvcAutoConfiguration.class })
@EnableSpringDataWebSupport
@Import({ NoSecurityConfiguration.class, FeignClientsConfiguration.class })
public static class Application {

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hateoas;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.RepresentationModel;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Hector Espert
*/
public class FeignHalAutoConfigurationContextTests {
private WebApplicationContextRunner contextRunner;
@Before
public void setUp() {
contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
HypermediaAutoConfiguration.class,
RepositoryRestMvcAutoConfiguration.class,
FeignHalAutoConfiguration.class))
.withPropertyValues("debug=true");
}
@Test
public void testHalJacksonHttpMessageConverterIsNotLoaded() {
FilteredClassLoader filteredClassLoader = new FilteredClassLoader(
RepositoryRestMvcConfiguration.class, RepresentationModel.class);
contextRunner.withClassLoader(filteredClassLoader)
.run(context -> assertThat(context)
.doesNotHaveBean("halJacksonHttpMessageConverter"));
}
@Test
public void testHalJacksonHttpMessageConverterIsLoaded() {
FilteredClassLoader filteredClassLoader = new FilteredClassLoader(
RepositoryRestMvcConfiguration.class);
contextRunner.withClassLoader(filteredClassLoader).run(
context -> assertThat(context).hasBean("halJacksonHttpMessageConverter"));
}
@Test
public void testHalJacksonHttpMessageConverterIsNotLoadedUseRestDataMessageConverterInstead() {
contextRunner.run(
context -> assertThat(context).hasBean("halJacksonHttpMessageConverter"));
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hateoas;
import java.util.Arrays;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.hateoas.mediatype.hal.CurieProvider;
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.hateoas.MediaTypes.HAL_JSON;
import static org.springframework.hateoas.MediaTypes.HAL_JSON_UTF8;
/**
* @author Hector Espert
*/
@RunWith(MockitoJUnitRunner.class)
public class FeignHalAutoConfigurationTests {
@Mock
private ObjectProvider<ObjectMapper> objectMapper;
@Mock
private ObjectProvider<HalConfiguration> halConfiguration;
@Mock
private ObjectProvider<LinkRelationProvider> relProvider;
@Mock
private ObjectProvider<CurieProvider> curieProvider;
@Mock
private ObjectProvider<MessageSourceAccessor> linkRelationMessageSource;
@InjectMocks
private FeignHalAutoConfiguration feignHalAutoConfiguration;
@Test
public void halJacksonHttpMessageConverter() {
ObjectMapper mapper = new ObjectMapper();
when(objectMapper.getIfAvailable(any())).thenReturn(mapper);
when(halConfiguration.getIfAvailable(any()))
.thenReturn(mock(HalConfiguration.class));
when(relProvider.getObject()).thenReturn(mock(LinkRelationProvider.class));
when(curieProvider.getIfAvailable(any())).thenReturn(mock(CurieProvider.class));
when(linkRelationMessageSource.getObject())
.thenReturn(mock(MessageSourceAccessor.class));
TypeConstrainedMappingJackson2HttpMessageConverter converter = feignHalAutoConfiguration
.halJacksonHttpMessageConverter(objectMapper, halConfiguration,
relProvider, curieProvider, linkRelationMessageSource);
assertThat(converter).isNotNull();
assertThat(converter.getObjectMapper()).isNotNull();
assertThat(converter.getSupportedMediaTypes())
.isEqualTo(Arrays.asList(HAL_JSON, HAL_JSON_UTF8));
assertThat(Jackson2HalModule.isAlreadyRegisteredIn(converter.getObjectMapper()))
.isTrue();
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hateoas;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.hateoas.app.FeignHalApplication;
import org.springframework.cloud.openfeign.hateoas.app.FeignHalClient;
import org.springframework.cloud.openfeign.hateoas.app.MarsRover;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedModel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* Test HATEOAS support.
*
* @author Hector Espert
*/
@SpringBootTest(classes = FeignHalApplication.class, webEnvironment = RANDOM_PORT,
value = "debug=true")
@RunWith(SpringRunner.class)
@DirtiesContext
public class FeignHalTests {
@Autowired
private FeignHalClient feignHalClient;
@Test
public void testEntityModel() {
EntityModel<MarsRover> entity = feignHalClient.entity();
assertThat(entity).isNotNull();
assertThat(entity.hasLinks()).isTrue();
assertThat(entity.hasLink("self")).isTrue();
assertThat(entity.getLink("self")).map(Link::getHref).contains("/entity");
MarsRover marsRover = entity.getContent();
assertThat(marsRover).isNotNull();
assertThat(marsRover.getName()).isEqualTo("Sojourner");
}
@Test
public void testCollectionModel() {
CollectionModel<MarsRover> collectionModel = feignHalClient.collection();
assertThat(collectionModel).isNotNull();
assertThat(collectionModel).isNotEmpty();
assertThat(collectionModel.hasLinks()).isTrue();
assertThat(collectionModel.hasLink("self")).isTrue();
assertThat(collectionModel.getLink("self")).map(Link::getHref)
.contains("/collection");
Collection<MarsRover> collection = collectionModel.getContent();
assertThat(collection).isNotEmpty();
MarsRover marsRover = collection.stream().findAny().orElse(null);
assertThat(marsRover).isNotNull();
assertThat(marsRover.getName()).isEqualTo("Opportunity");
}
@Test
public void testPagedModel() {
PagedModel<MarsRover> paged = feignHalClient.paged();
assertThat(paged).isNotNull();
assertThat(paged).isNotEmpty();
assertThat(paged.hasLinks()).isTrue();
assertThat(paged.hasLink("self")).isTrue();
assertThat(paged.getLink("self")).map(Link::getHref).contains("/paged");
Collection<MarsRover> collection = paged.getContent();
assertThat(collection).isNotEmpty();
MarsRover marsRover = collection.stream().findAny().orElse(null);
assertThat(marsRover).isNotNull();
assertThat(marsRover.getName()).isEqualTo("Curiosity");
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hateoas.app;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
import org.springframework.context.annotation.Import;
/**
* Test HATEOAS application.
*
* @author Hector Espert
*/
@EnableFeignClients(clients = FeignHalClient.class)
@RibbonClient(name = "local", configuration = FeignHalRibbonConfiguration.class)
@SpringBootApplication(
scanBasePackages = "org.springframework.cloud.openfeign.hateoas.app",
exclude = RepositoryRestMvcAutoConfiguration.class)
@Import(NoSecurityConfiguration.class)
public class FeignHalApplication {
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hateoas.app;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.PagedModel;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author Hector Espert
*/
@FeignClient("local")
public interface FeignHalClient {
@GetMapping("entity")
EntityModel<MarsRover> entity();
@GetMapping("collection")
CollectionModel<MarsRover> collection();
@GetMapping("paged")
PagedModel<MarsRover> paged();
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hateoas.app;
import java.util.Collections;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Hector Espert
*/
@RestController
public class FeignHalController {
@GetMapping("/entity")
public EntityModel<MarsRover> getEntity() {
MarsRover marsRover = new MarsRover();
marsRover.setName("Sojourner");
Link link = new Link("/entity", "self");
return new EntityModel<>(marsRover, link);
}
@GetMapping("/collection")
public CollectionModel<MarsRover> getCollection() {
MarsRover marsRover = new MarsRover();
marsRover.setName("Opportunity");
Link link = new Link("/collection", "self");
return new CollectionModel<>(Collections.singleton(marsRover), link);
}
@GetMapping("/paged")
public CollectionModel<MarsRover> getPaged() {
MarsRover marsRover = new MarsRover();
marsRover.setName("Curiosity");
Link link = new Link("/paged", "self");
PagedModel.PageMetadata metadata = new PagedModel.PageMetadata(1, 1, 1);
return new PagedModel<>(Collections.singleton(marsRover), metadata, link);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hateoas.app;
import java.util.Collections;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
/**
* @author Hector Espert
*/
public class FeignHalRibbonConfiguration {
@Value("${local.server.port}")
private int serverPort = 0;
@Bean
public ILoadBalancer ribbonLoadBalancer() {
Server server = new Server("localhost", serverPort);
BaseLoadBalancer balancer = new BaseLoadBalancer();
balancer.setServersList(Collections.singletonList(server));
return balancer;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hateoas.app;
/**
* @author Hector Espert
*/
public class MarsRover {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}