일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Medium
- OpenFeign
- ssl프로토콜확인
- mac os git error
- error
- ssl이란?
- LeetCode
- Java 1.8
- java 여러개 버전
- 스프링부트테스트
- springboot
- xcrun: error: invalid active developer path
- springboottest
- No tests found for given includes
- JUnit
- java
- java 버전 변경
- parse
- xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools)
- log error
- java 1.8 11
- statement.executequery() cannot issue statements that do not produce result sets.
- java version
- yum install java
- aws
- java 11
- AWS CLI
- easy
- tls프로토콜확인
- mysql executequery error
- Today
- Total
쩨이엠 개발 블로그
OpenFeign 적용기 ( spring boot 3.0.x ) 본문
RestTemplate 을 대신하여 OpenFeign을 적용해보기로 했다
1. Gradle 적용
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.6'
id 'io.spring.dependency-management' version '1.1.0'
}
...
dependencies {
...
// OpenFeign
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.0.3'
}
ext {
set('springCloudVersion', "2022.0.3")
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
Springboot가 3.0.x 버전이어서 springCloudVersion과 OpenFeign 버전 또한 제일 최신으로 맞춰주었다
아니면 빌드 실패 난다
밑은 그 에러내용
2. OpenFeign 소스 적용
OpenFeign을 적용하기 위해서는 Config 파일과 Client 파일을 만들어줘야한다
OpenFeignConfig.java
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableFeignClients(basePackages = {"com.gate.common"})
public class OpenFeignConfig {
}
basePackages 안의 client에 글로벌하게 Config 설정을 할 수 있다
SearchApi.java
...
import feign.codec.ErrorDecoder;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@FeignClient(value= "search", url = "http://localhost:8080", configuration = SearchApi.FeignConfig.class)
public interface SearchApi {
@GetMapping(value = "/books",produces = MediaType.APPLICATION_JSON_VALUE)
BookResponse getBookItems(@RequestParam("title") String title,
@RequestParam("page") int page,
@RequestParam("size") int size
);
class FeignConfig {
@Bean
ErrorDecoder errorDecoder() {
return new SearchErrorDecoder();
}
}
}
OpenFeign은 client를 interface로 만들어주면 그 안의 url과 실제 RestController mapping을 연결하여 RestTemplate을 사용하듯이 보내주면서도 깔끔하게 소스를 확인할 수 있다
예제 : 외부의 도서리스트 불러오기
- url : http://localhost:8080/books GET
(yaml 파일에서 세팅한 값을 profile마다 다르게 가져올 수도 있다)
- configuration : SearchApi 안에서만의 설정이 필요하다면 직접 밑에 FeignConfig를 구현하는 것도 가능하다
- FeignConfig : ErrorDecoder를 새로 구현하여 검색에서 난 에러를 관리해 줄 수 있다
주의 받는쪽의 MediaType을 꼭 확인할 것
SearchService.java
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class SearchService {
private final SearchApi searchApi;
...
public SearchResponse getBookList(SearchDto dto){
BookResponse res = searchApi.getBookItems(dto.getSearch(), dto.getPage(), dto.getSize());
List<SearchResponse.Item> list = res.getItems().stream()
.map(item -> new SearchResponse.Item(item.getShopName(), item.getTitle(), item.getPrice(), item.getImg())).toList();
return new SearchResponse(res.getTotal(), list);
}
}
실제 사용은 간단하다
위에 선언 후 그대로 SearchApi에 맞게 넘겨주면 끝!
Exception 처리하기
OpenFeign에서는 HttpStatus가 200이 아닐때 FeignException을 던지기 때문에 그에 대한 대비를 해놓으면 좋다
feign.FeignException$BadRequest: [400] during [GET] to [http://URL] [SearchApi#getBookItems(String,int,int)]: [{"code":1,"message":"Not match field","fieldErrors":[{"field":"search","value":"","message":"must not be blank"}],"timestamps":"2023-06-13 13:49:31"}]
외부 API에서는 search가 null일 때 HttpStatus 400과 message를 내려주었다
[{"code":1,"message":"Not match field","fieldErrors":[{"field":"q","value":"","message":"must not be blank"}],"timestamps":"2023-06-13 13:49:31"}]
이 부분을 받아서 client에 전달하기 위해서는 Decoder와 Exception 관련 객체들이 추가되어야한다
+ ExceptionAdvice
SearchErrorResponse.java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
public class SearchErrorResponse {
private int code;
private String message;
private List<FieldError> fieldErrors;
private String timestamps;
@Data
public static class FieldError{
private String field;
private String value;
private String message;
}
}
Exception에 담을 Response를 설정해준다
SearchFeignException.java
import org.springframework.web.server.ResponseStatusException;
public class SearchFeignException extends ResponseStatusException {
private final ApiResponseType apiResponseType;
private final SearchErrorResponse errorResponse;
public SearchFeignException(ApiResponseType apiResponseType) {
super(apiResponseType.getHttpStatus(), apiResponseType.getMessage());
this.apiResponseType = apiResponseType;
this.errorResponse = null;
}
public SearchFeignException(ApiResponseType apiResponseType, SearchErrorResponse errorResponse) {
super(apiResponseType.getHttpStatus(), apiResponseType.getMessage());
this.apiResponseType = apiResponseType;
this.errorResponse = errorResponse;
}
public ApiResponseType getApiResponseType() {
return apiResponseType;
}
public SearchErrorResponse getErrorResponse() {
return errorResponse;
}
}
필요한 Exception을 만들어준다
SearchDecoder.java
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Response;
import feign.codec.ErrorDecoder;
import feign.codec.StringDecoder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SearchErrorDecoder implements ErrorDecoder {
private final StringDecoder stringDecoder = new StringDecoder();
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
@SneakyThrows
public SearchFeignException decode(String methodKey, Response response) {
String message = stringDecoder.decode(response, String.class).toString();
SearchErrorResponse searchErrorResponse = objectMapper.readValue(message, SearchErrorResponse.class);
return new SearchFeignException(ApiResponseType.SEARCH_KEYWORD_NULL, searchErrorResponse);
}
}
ErrorDecoder에서 decode 부분을 override해 변경한다
여러개의 method가 이 decode를 통과한다면 methodKey로 ExceptionType을 변경해 내려줄 수 있다
내 경우에는 한개뿐이라 methodKey는 사용하지 않고 response만 사용했다
위의 stringdecoder와 objectMapper로 바로 ErrorResponse로 변환이 가능하다
그럼 이제 Advice에 적용해본다
ExceptionControllerAdvice.java
@RestControllerAdvice
public class ExceptionControllerAdvice {
...
@ExceptionHandler(SearchFeignException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<ErrorResponse> handleException(SearchFeignException e) {
log.error("global handleException SearchFeignException", e);
ErrorResponse errorResponse = ErrorResponse.of(e.getApiResponseType().getCode(), e.getErrorResponse());
return new ResponseEntity<>(errorResponse, e.getApiResponseType().getHttpStatus());
}
}
SearchFeignException일 때 ExceptionAdvice를 타도록 @ExceptionHandler 어노테이션을 붙여준다
그리고 받아온 e에서 객체를 꺼내서 원하는 ResponseEntity에 담을 수 있도록 가공한 후 내보내면
원하는 대로 보낼 수 있다
'개발 > JAVA' 카테고리의 다른 글
Mysql Error : Statement.executeQuery() cannot issue statements that do not produce result sets. (0) | 2024.05.21 |
---|---|
[Java] ObjectMapper Error - Java 8 date/time type `java.time.LocalDateTime` not supported by default (0) | 2023.07.05 |
[ Java ] 자바 정규식 (Regular expressions) (0) | 2021.01.19 |
[ TCP ] TCP Client 만들기 (3) | 2021.01.06 |
[ TCP ] TCP Server 만들기 (1) | 2021.01.05 |