Adds path parameter to @FeignClient.
Fixes gh-966
This commit is contained in:
committed by
Spencer Gibb
parent
654ea7094c
commit
d3cba4d80e
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2016 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.
|
||||
@@ -31,6 +31,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Venil Noronha
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -81,4 +82,11 @@ public @interface FeignClient {
|
||||
* implement the interface annotated by this annotation and be a valid spring bean.
|
||||
*/
|
||||
Class<?> fallback() default void.class;
|
||||
|
||||
/**
|
||||
* Path prefix to be used by all method-level mappings. Can be used with or without
|
||||
* <code>@RibbonClient</code>.
|
||||
*/
|
||||
String path() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2016 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.
|
||||
@@ -44,6 +44,7 @@ import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Venil Noronha
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@@ -70,6 +71,8 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
|
||||
private String url;
|
||||
|
||||
private String path;
|
||||
|
||||
private boolean decode404;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
@@ -170,14 +173,29 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
else {
|
||||
url = this.name;
|
||||
}
|
||||
url += cleanPath();
|
||||
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
|
||||
this.name, url));
|
||||
}
|
||||
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
|
||||
this.url = "http://" + this.url;
|
||||
}
|
||||
String url = this.url + cleanPath();
|
||||
return targeter.target(this, builder, context, new HardCodedTarget<>(
|
||||
this.type, this.name, this.url));
|
||||
this.type, this.name, url));
|
||||
}
|
||||
|
||||
private String cleanPath() {
|
||||
String path = this.path.trim();
|
||||
if (StringUtils.hasLength(path)) {
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2016 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.
|
||||
@@ -55,6 +55,7 @@ import org.springframework.util.StringUtils;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Jakub Narloch
|
||||
* @author Venil Noronha
|
||||
*/
|
||||
public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
ResourceLoaderAware, BeanClassLoaderAware {
|
||||
@@ -171,6 +172,7 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
.genericBeanDefinition(FeignClientFactoryBean.class);
|
||||
validate(attributes);
|
||||
definition.addPropertyValue("url", getUrl(attributes));
|
||||
definition.addPropertyValue("path", getPath(attributes));
|
||||
String name = getName(attributes);
|
||||
definition.addPropertyValue("name", name);
|
||||
definition.addPropertyValue("type", className);
|
||||
@@ -247,6 +249,20 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
return url;
|
||||
}
|
||||
|
||||
private String getPath(Map<String, Object> attributes) {
|
||||
String path = resolve((String) attributes.get("path"));
|
||||
if (StringUtils.hasText(path)) {
|
||||
path = path.trim();
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
protected ClassPathScanningCandidateComponentProvider getScanner() {
|
||||
return new ClassPathScanningCandidateComponentProvider(false) {
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.netflix.feign.ribbon;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.WebIntegrationTest;
|
||||
import org.springframework.cloud.netflix.feign.EnableFeignClients;
|
||||
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.netflix.ribbon.StaticServerList;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* @author Venil Noronha
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = FeignRibbonClientPathTests.Application.class)
|
||||
@WebIntegrationTest(
|
||||
randomPort = true,
|
||||
value = {
|
||||
"spring.application.name=feignribbonclientpathtest",
|
||||
"feign.okhttp.enabled=false",
|
||||
"feign.httpclient.enabled=false",
|
||||
"feign.hystrix.enabled=false",
|
||||
"test.path.prefix=/base/path" // For pathWithPlaceholder test
|
||||
}
|
||||
)
|
||||
@DirtiesContext
|
||||
public class FeignRibbonClientPathTests {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Autowired
|
||||
private TestClient1 testClient1;
|
||||
|
||||
@Autowired
|
||||
private TestClient2 testClient2;
|
||||
|
||||
@Autowired
|
||||
private TestClient3 testClient3;
|
||||
|
||||
@Autowired
|
||||
private TestClient4 testClient4;
|
||||
|
||||
@Autowired
|
||||
private TestClient5 testClient5;
|
||||
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "localapp", path = "/base/path")
|
||||
protected interface TestClient1 extends TestClient { }
|
||||
|
||||
@FeignClient(name = "localapp", path = "base/path")
|
||||
protected interface TestClient2 extends TestClient { }
|
||||
|
||||
@FeignClient(name = "localapp", path = "base/path/")
|
||||
protected interface TestClient3 extends TestClient { }
|
||||
|
||||
@FeignClient(name = "localapp", path = "/base/path/")
|
||||
protected interface TestClient4 extends TestClient { }
|
||||
|
||||
@FeignClient(name = "localapp", path = "${test.path.prefix}")
|
||||
protected interface TestClient5 extends TestClient { }
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@RequestMapping("/base/path")
|
||||
@EnableFeignClients(clients = {
|
||||
TestClient1.class, TestClient2.class, TestClient3.class, TestClient4.class,
|
||||
TestClient5.class
|
||||
})
|
||||
@RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class)
|
||||
public static class Application {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
new SpringApplicationBuilder(Application.class).properties(
|
||||
"spring.application.name=feignribbonclientpathtest",
|
||||
"management.contextPath=/admin"
|
||||
).run(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathWithLeadingButNotTrailingSlash() {
|
||||
testClientPath(this.testClient1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathWithoutLeadingAndTrailingSlash() {
|
||||
testClientPath(this.testClient2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathWithoutLeadingButTrailingSlash() {
|
||||
testClientPath(this.testClient3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathWithLeadingAndTrailingSlash() {
|
||||
testClientPath(this.testClient4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathWithPlaceholder() {
|
||||
testClientPath(this.testClient5);
|
||||
}
|
||||
|
||||
private void testClientPath(TestClient testClient) {
|
||||
Hello hello = testClient.getHello();
|
||||
assertNotNull("Object returned was null", hello);
|
||||
assertEquals("Response object value didn't match", "hello world",
|
||||
hello.getMessage());
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Hello {
|
||||
private String message;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class LocalRibbonClientConfiguration {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server("localhost", this.port));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user