Servlet (서블릿)은 자바를 사용하여 웹페이지를 동적으로 생성하는 서버측 프로그램 혹은 그 사양을 말함
결론적으로 말하면 2가지의 장점이 있다.
1. HTTP request, response 처리를 위해 매번 작성해 줘야하는 중복코드들 생략 가능
2. API 이름마다 파일을 만들 필요 없음
1. 중복코드 생략가능
itemDto
import lombok.Getter;
@Getter
public class ItemDto {
private String title;
private String link;
private String image;
private int lprice;
}
main class
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan // @WebServlet 어노테이션이 동작하게 함
@SpringBootApplication
public class SpringcoreApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcoreApplication.class, args);
}
}
Servlet으로 구현할 시
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@WebServlet(urlPatterns = "/api/search")
public class ItemSearchServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1. API Request 의 파라미터 값에서 검색어 추출 -> query 변수
String query = request.getParameter("query");
// 2. 네이버 쇼핑 API 호출에 필요한 Header, Body 정리
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Naver-Client-Id", "각자의 id");
headers.add("X-Naver-Client-Secret", "각자의 password");
String body = "";
HttpEntity<String> requestEntity = new HttpEntity<>(body, headers);
// 3. 네이버 쇼핑 API 호출 결과 -> naverApiResponseJson (JSON 형태)
ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=" + query, HttpMethod.GET, requestEntity, String.class);
String naverApiResponseJson = responseEntity.getBody();
// 4. naverApiResponseJson (JSON 형태) -> itemDtoList (자바 객체 형태)
// - naverApiResponseJson 에서 우리가 사용할 데이터만 추출 -> List<ItemDto> 객체로 변환
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JsonNode itemsNode = objectMapper.readTree(naverApiResponseJson).get("items");
List<ItemDto> itemDtoList = objectMapper
.readerFor(new TypeReference<List<ItemDto>>() {})
.readValue(itemsNode);
// 5. API Response 보내기
// 5.1) response 의 header 설정
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
// 5.2) response 의 body 설정
PrintWriter out = response.getWriter();
// - itemDtoList (자바 객체 형태) -> itemDtoListJson (JSON 형태)
String itemDtoListJson = objectMapper.writeValueAsString(itemDtoList);
out.print(itemDtoListJson);
out.flush();
}
}
Controller 로 구현할 시
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.List;
@Controller
public class ItemSearchController {
// Controller 가 자동으로 해주는 일
// 1. API Request 의 파라미터 값에서 검색어 추출 -> query 변수
// 5. API Response 보내기
// 5.1) response 의 header 설정
// 5.2) response 의 body 설정
@GetMapping("/api/search")
@ResponseBody
public List<ItemDto> getItems(@RequestParam String query) throws IOException {
// 2. 네이버 쇼핑 API 호출에 필요한 Header, Body 정리
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Naver-Client-Id", "각자의 id");
headers.add("X-Naver-Client-Secret", "각자의 password");
String body = "";
HttpEntity<String> requestEntity = new HttpEntity<>(body, headers);
// 3. 네이버 쇼핑 API 호출 결과 -> naverApiResponseJson (JSON 형태)
ResponseEntity<String> responseEntity = rest.exchange("https://openapi.naver.com/v1/search/shop.json?query=" + query, HttpMethod.GET, requestEntity, String.class);
String naverApiResponseJson = responseEntity.getBody();
// 4. naverApiResponseJson (JSON 형태) -> itemDtoList (자바 객체 형태)
// - naverApiResponseJson 에서 우리가 사용할 데이터만 추출 -> List<ItemDto> 객체로 변환
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JsonNode itemsNode = objectMapper.readTree(naverApiResponseJson).get("items");
List<ItemDto> itemDtoList = objectMapper
.readerFor(new TypeReference<List<ItemDto>>() {})
.readValue(itemsNode);
return itemDtoList;
}
}
전체 코드를 보면 힘들 수 있으니 바뀐 부분만 빼서 살펴보도록 하겠습니다.
Servlet으로 구현했을 경우와, Controller로 구현했을 경우의 차이를 아래에서 코드로 확인해보겠습니다.
@WebServlet(urlPatterns = "/api/search")
public class ItemSearchServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String query = request.getParameter("query");
// ...
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String itemDtoListJson = objectMapper.writeValueAsString(itemDtoList);
out.print(itemDtoListJson);
out.flush();
}
}
위와 아래를 비교해보면 확연히 코드량이 줄어든 것을 볼 수 있습니다.
@Controller
public class ItemSearchController {
@GetMapping("/api/search")
@ResponseBody
public List<ItemDto> getItems(@RequestParam String query) throws IOException {
// ...
return itemDtoList;
}
}
2. api이름마다 파일을 만들 필요가 없음
servlet은 HttpServlet을 상속받아 메서드를 override 하는 방식으로 구현을 하고 있기 때문에
api를 구현할 때 메서드명이 강제되는 단점이 있습니다.
로그인 페이지 | GET | /user/login | login 페이지 |
로그아웃 처리 | GET | /user/logout | "/" 으로 redirect |
회원 가입 페이지 | GET | /user/signup | signup 페이지 |
회원 가입 처리 | POST | /user/signup | "/" 으로 redirect |
위 api를 Servlet으로 구현하면 아래와 같습니다.
@WebServlet(urlPatterns = "/user/login")
public class UserLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// ...
}
}
@WebServlet(urlPatterns = "/user/login")
public class UserLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// ...
}
}
@WebServlet(urlPatterns = "/user/login")
public class UserLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// ...
}
}
Servlet으로 구현했던 것을 컨트롤러로 구현하면 아래와 같이 한개의 페이지로 만들 수 있습니다.
@Controller
public class UserController {
@GetMapping("/user/login")
public String login() {
// ...
}
@GetMapping("/user/logout")
public String logout() {
// ...
}
@GetMapping("/user/signup")
public String signup() {
// ...
}
@PostMapping("/user/signup")
public String registerUser(SignupRequestDto requestDto) {
// ...
}
}