개요
이전에 만들었던 프로젝트의 API path를 변경해야 하는 상황이 발생했다.
하지만 이미 배포를 진행한 프로젝트였기 때문에 기존의 path가 호출되더라도 동일한 응답을 줘야만 했다.
작성했던 Service의 로직을 그대로 호출하는 방법도 있지만 동일한 코드를 호출하는 것은 비효율적이라고 생각했다.
따라서 redirect로 호출된 path의 마지막 부분과 동일한 API를 호출하는 코드를 작성하기로 했다.
RestTemplate
"redirect:/path" 로 변경된 path 를 호출하는 방법도 있지만 성능 저하 및 코드의 복잡성 증가 이슈가 있었다.
그래서 RestTemplate 라이브러리를 사용해서 직접 API를 호출하는 방법을 택했다.
RestTemplate는 Spring Boot 2.4 버전 이상부터는 기본적으로 빈으로 등록되지 않기 때문에 직접 빈을 설정해줘야 한다.
RestTemplate를 빈 등록 없이 직접 호출하게 되면 NullException을 맞닥뜨릴 수 있다.
RestTemplate를 Bean으로 등록하는 코드는 다음과 같다.
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Controller에서 RestTemplate 사용하기
기존 path 가 호출될 경우 새로 생성한 API를 호출하는 코드를 다음과 같이 작성한다.
@RestController
@RequestMapping("/api/example")
@RequiredArgsConstructor
public class TestController {
private final RestTemplate restTemplate;
@PostMapping("hi")
public ResponseEntity<HiDto> hiRedirect() {
String url = "localhost:8080/api/existing/hi";
return restTemplate.exchange(url, HttpMethod.POST, null, HiDto.class);
}
}
RestTemplate 의 exchange() 함수를 사용해서 서버의 API 를 호출한다.
exchange() 함수의 리턴 타입과 매개 변수는 다음과 같다.
<T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType)
만약 위 함수에서 "/api/example/hi"를 호출할 경우 "/api/existing/hi" API의 결과값이 리턴될 것이다.
API가 1개인 경우에는 위 코드를 사용해도 무방하지만, 여러 개 일 경우 path와 class만 다른 동일한 코드가 반복될 것이다.
@RestController
@RequestMapping("/api/example")
@RequiredArgsConstructor
public class TestController {
private final RestTemplate restTemplate;
@PostMapping("hi")
public ResponseEntity<HiDto> hiRedirect() {
String url = "localhost:8080/api/existing/hi";
return restTemplate.exchange(url, HttpMethod.POST, null, HiDto.class);
}
@PostMapping("hello")
public ResponseEntity<HelloDto> helloRedirect() {
String url = "localhost:8080/api/existing/hello";
return restTemplate.exchange(url, HttpMethod.POST, null, HelloDto.class);
}
@PostMapping("annyeong")
public ResponseEntity<AnnyeongDto> annyeongRedirect() {
String url = "localhost:8080/api/existing/annyeong";
return restTemplate.exchange(url, HttpMethod.POST, null, AnnyeongDto.class);
}
...
}
제네릭을 사용한 모든 API 동작 실행
위 코드처럼 중복된 코드가 무분별하게 늘어가는 것을 방지하기 위해 제네릭을 사용해서 반환 타입을 지정해준다.
그리고 모든 "/api/example"가 포함된 API 호출 시 해당 함수가 동작하도록 PostMapping path를 수정해준다.
@RestController
@RequestMapping("/api/example")
@RequiredArgsConstructor
public class TestController {
private final RestTemplate restTemplate;
// '/api/example'이 포함된 모든 path를 받는다.
@PostMapping("**")
public <T> ResponseEntity<T> redirect() {
String url = "localhost:8080/api/existing/hi";
return restTemplate.exchange(url, HttpMethod.POST, null, HiDto.class);
}
}
제네릭을 적용하는 경우 exchange() 함수의 반환 타입도 타입에 따라 유동적으로 변경되기 때문에 변경해줘야 한다.
직접적으로 제네릭 타입을 넘길 수 없기 때문에 ParameterizedTypeReference 객체를 사용해서 class의 타입을 넘겨준다.
@RestController
@RequestMapping("/api/example")
@RequiredArgsConstructor
public class TestController {
private final RestTemplate restTemplate;
// '/api/example'이 포함된 모든 path를 받는다.
@PostMapping("**")
public <T> ResponseEntity<T> redirect() {
String url = "localhost:8080/api/existing/hi";
return restTemplate.exchange(url, HttpMethod.POST, null, new ParameterizedTypeReference<>() {});
}
}
호출된 API의 path 알아내기
만약 호출되는 path의 마지막 부분만 달라진다면 마지막 path를 변경해서 기존 API에 요청을 보내면 될 것이다.
HttpServletRequest 객체를 사용하면 현재 요청의 URL, IP, 헤더 정보 등을 알아낼 수 있다.
따라서 해당 객체를 매개변수에 추가해주고 path의 마지막 부분을 substring()과 lastIndexOf()를 사용해서 알아낸다.
@RestController
@RequestMapping("/api/example")
@RequiredArgsConstructor
public class TestController {
private final RestTemplate restTemplate;
// '/api/example'이 포함된 모든 path를 받는다.
@PostMapping("**")
public <T> ResponseEntity<T> redirect(HttpServletRequest request) {
// API 요청 path의 마지막 부분 찾기
String path = request.getRequestURI().substring(request.getRequestURI().lastIndexOf("/"));
String url = "localhost:8080/api/existing" + path;
return restTemplate.exchange(url, HttpMethod.POST, null, new ParameterizedTypeReference<>() {});
}
}
Body 데이터 받아오기
사용자에게 전달 받은 body 데이터를 받기 위해서는 @RequestBody를 사용해서 데이터를 받아야 한다.
하지만 @RequestBody를 사용하는 함수에 빈 body 요청을 보낼 경우 에러가 발생하게 된다.
이유는 @RequestBody의 required 옵션의 기본값이 true이기 때문이다.
따라서 @RequestBody의 데이터가 없는 경우에도 API를 호출하도록 (require = false) 옵션을 추가한다.
@RestController
@RequestMapping("/api/example")
@RequiredArgsConstructor
public class TestController {
private final RestTemplate restTemplate;
// '/api/example'이 포함된 모든 path를 받는다.
@PostMapping("**")
public <T> ResponseEntity<T> redirect(@RequestBody(required = false) Map<String, String> param, HttpServletRequest request) {
// API 요청 path의 마지막 부분 찾기
String path = request.getRequestURI().substring(request.getRequestURI().lastIndexOf("/"));
String url = "localhost:8080/api/existing" + path;
return restTemplate.exchange(url, HttpMethod.POST, null, new ParameterizedTypeReference<>() {});
}
}
받은 body 데이터를 HttpEntity 객체에 넣어 전송해주면 된다.
exchange()의 HttpEntity<?>는 null 값이 허용(@Nullable)되기 때문에 따로 null 처리를 안해줘도 된다.
@RestController
@RequestMapping("/api/example")
@RequiredArgsConstructor
public class TestController {
private final RestTemplate restTemplate;
// '/api/example'이 포함된 모든 path를 받는다.
@PostMapping("**")
public <T> ResponseEntity<T> redirect(@RequestBody(required = false) Map<String, String> param, HttpServletRequest request) {
// API 요청 path의 마지막 부분 찾기
String path = request.getRequestURI().substring(request.getRequestURI().lastIndexOf("/"));
String url = "localhost:8080/api/existing" + path;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<?> entity = new HttpEntity<>(param, headers);
return restTemplate.exchange(url, HttpMethod.POST, null, new ParameterizedTypeReference<>() {});
}
}
'🛠️Backend > Spring' 카테고리의 다른 글
[Spring Boot] 사용자가 접속한 IP 알아내기 (0) | 2024.12.27 |
---|---|
[Spring Boot] Spring Boot & React 프로젝트 연결하기 (1) | 2024.10.30 |
[Spring Boot] 설정 파일 값 암호화 및 적용하기 (feat. Jasypt) (0) | 2024.06.25 |
[Spring Boot] 프로젝트 실행 시 파일 존재 여부 확인 및 신규 파일 생성 (0) | 2024.03.06 |
[Spring Boot] Launch4j과 jar 파일을 사용해서 실행 파일(.exe) 만들기 (0) | 2024.03.06 |
댓글