Java http client - Feign 이용해보기
배경
기존에 RestTemplate
을 이용해서 Spring에서 http client 라이브러리 역할을 많이 했다. 하지만 Spring에서 RestTemplate
을 지속적으로 향상시키기보다 deprecated 한다는 이야기를 들었습니다. 그래서 다른 친구가 없을까 싶어 찾던 중 feign
이라는 친구를 알게되었습니다. 간단한 사용 후기를 작성해보겠습니다.
의존성
ext {
springCloudVersion = 'Hoxton.RELEASE'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
dependencies {
compile("org.springframework.cloud:spring-cloud-starter-openfeign")
}
위 사진은 link 의 Release Trains의 table
에서 가져왔습니다.
위에 내용와 함께해서 ext
에 있는 변수를 잘 설정해주면 된다. 저는 spring boot 2.3 버전을 사용하고 있기 때문에 Hoxton
으로 설정했습니다.
선언
@EnableFeignClients
@SpringBootApplication
public class FeignTestApplication() {
public static void main(String[] args) {
SpringApplication.run(ManagerApplication.class, args);
}
}
---
@EnableFeignClients(basePackages = "com.test.feign")
@Configuration
public class Config() {
}
첫번째 보이는 것처럼 Main
에다가 적용하는 경우에는 특별히 어떤 것들도 하지 않아도 잘 동작하게 된다.
하지만 저 같은 경우는 특정 config 파일을 만들어서 사용하고 싶어서 따로 @EnableFeignClients
어노테이션을 활용했습니다. 그런데 주의할 점은 저렇게 선언한 경우 Main
과 같은 패키지에 존재하지 않은 경우에는 제대로 동작하지 않는다.
만약 저 같이 사용하고 싶은 경우 basePackages
를 활용해야 한다.
기본 활용
i.can.get.properties.data == localhost:8080
@FeignClient(value = "feignTest", url = "${i.can.get.properties.data}")
public interface httpClient {
@GetMapping("/test/{number}")
String getHelloWorldMessage(@PathVariable("number") int number, @RequestParam("firstmsg") String firstMsg, @RequestParam("secondmsg") String secondMsg);
// Call getHelloWorldMessage(1, hello, world);
// GET localhost:8080/test/1?firstmsg=hello&secondmsg=world
@PostMapping
List<TestNode> createNewMessage(RequestNode requestNode);
// POST localhost:8080
// body requestNode
@PostMapping
List<TestNode> getMulitParam(@SpringQueryMap Map<String, Object> param);
// ref: https://cloud.spring.io/spring-cloud-openfeign/reference/html/#feign-querymap-support
@PostMapping
List<TestNode> getMulitParamWithBody(@SpringQueryMap Map<String, Object> param, RequestNode requestNode);
}
- 기본적으로
interface
를 이용해서 만들어야 합니다. - 해당 interface에
@FeignClient
를 사용해서feign
를 사용할 수 있는 친구를 만들 수 있습니다. @FeignClient
안에 여러 값을 넣을 수 있는데value
같은 경우 특정 설정을 할 때 사용할 수도 있고 고유한 값으로 지정해야됩니다.url
같은 경우basePath
를 지정하는 방식으로 앞에prefix
를 붙인다고 생각하면 쉬울 것 같다. (properties
에서 값을 바로 가져올 수 있다.)
선언된 모습들을 보면 SpringMvc
를 생각할 수 있는데 feign
에 존재하는 선언방식 중에 Contract
라는 요소가 존재하는데 그 모습 중에 하나라고 생각하면 된다. SpringMvc
가 spring-boot-cloud
로 선언한 경우 defalut값으로 설정되어 있는데 feign.default
라는 다른 Contract
를 사용하게 되는 경우 다른 방식으로 선언해서 사용해야 합니다!! 저는 잘 몰랐지만 SpringMvc Contract
를 사용하게 되는 경우 annotation도 SpringMvc
를 사용해야되기 때문에 위에서 사용하는 것처럼 사용가능합니다!!!
여러개의 paramater를 주입하고 싶은 경우에는 @SpringQueryMap
이라는 어노테이션을 사용하면 된다. Map 객체를 만들어서 넣어도 되며 Class를 따로 선언해서 만들어도 된다. 참고링크
기존적으로 feign
는 어떤 값을 넣었는데 어떤 어노테이션도 없는 경우 Body
라고 인식합니다. 그래서 만약 여러개 파라미터에 값을 넣는다고 해서 무조건 바디로 가는 것이 아닙니다. 꼭 명시적으로 @PathVariable
인지, @RequestParam
를 통해서 Query String
인지 명시해주지 않으면 Method has too many Body parameters
라는 메시지를 아주 많이 보게 될 것 입니다.
커스텀
properties
를 이용한 방법, code
를 이용한 방법, builder
을 이용한 방법. 세 가지가 존재한다. 참고링크
Code
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
---
public class Foo2Configuration {
@Bean
public Contract feignContract() {
return new SpringMvcContract();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user2", "password2");
}
}
위와 같은 방식으로 진행하는 경우 특별한 feign client
을 지정하지 않고도 모든 feign client
에 대해서 설정이 돤다. 그 이유에 대해서는 Spring cloud
에서 잘 해주는 것 같다…
만약 특정 clinet
에게 적용하고 싶은 밑에 방법을 통해서 사용하면 된다.
@FeignClient(value = "feignTest", url = "${i.can.get.properties.data}")
public interface httpClient1 {
@GetMapping("/test/{number}")
String getHelloWorldMessage(@PathVariable("number") int number, @RequestParam("firstmsg") String firstMsg, @RequestParam("secondmsg") String secondMsg);
}
---
@FeignClient(value = "feignTest2", url = "${i.can.get.properties.data}", configuration = {Foo2Configuration.class})
public interface httpClient2 {
@GetMapping("/test/{number}")
String getHelloWorldMessage(@PathVariable("number") int number, @RequestParam("firstmsg") String firstMsg, @RequestParam("secondmsg") String secondMsg);
}
이런 방식을 통해서 아무 configuration
을 설정하지 않은 경우 @Configuration
으로 설정된 FooConfiguration
class에서 정의된 설정이 될 것이며 그 외로 자기가 지정을 원하는 경우 밑에 방식처럼 annotation안에 있는 configuration
키워드를 통해서 override를 할 수 있다.
Properties
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
feignName
은 @FeignClient(name = "feignName")
에 존재하는 특정 client
를 지정해서 할 수 있다.
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
위와 같은 방식으로 선언하는 경우 defalut
로 전체적으로 정의하여서 사용할 수가 있다.
만약 @Configuration
와 properties
를 이용해서 설정을 했다고 한다면 properties
가 이기게 되는 @Configuration
이 이기도록 하고 싶은 경우 feign.client.default-to-properties
값을 false
로 바꾸면 된다.
builder
참고자료를 보면 어떤 느낌인지 알 수 있을 것이다.
@Import(FeignClientsConfiguration.class)
class FooController {
private FooClient fooClient;
private FooClient adminClient;
@Autowired
public FooController(Decoder decoder, Encoder encoder, Client client, Contract contract) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "https://PROD-SVC");
this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "https://PROD-SVC");
}
}
@FeignClient
를 내부적으로 선언하는 것이 아닌 값을 불러오는 경우에는 설정값을 넣어서 대입하는 경우이다. 변수로 가져오는 FooClient
는 모두 현재 @FeignClient
로 선언되어 있으면 그 전체적인 설정으로 이런 방식으로 따로따로 설정해서 사용할 수도 있다.
정리
RestTemplate
에 조금 익숙해져있었는데 이 친구는 매우 가볍게 설정할 수 있고 사용할 수 있다. 무엇보다 공식 문서가 매우 잘 작성되어 있기에 가볍게 읽고 사용하는대도 사용 중 발생하는 문제들을 금방 해결할 수 있다는 장점이 있다. 공식 문서 외에도 활용성에 대해서 다른 블로그에서도 멋있게 정리해주셔서 참고해서 사용해보면 참 좋을 것 같다.
Leave a comment