
서블릿(Servlet)은 “HTTP 요청/응답을 처리하기 위한 저수준 표준 인터페이스”이고, 스프링의 @Controller는 내부적으로 서블릿 기반 위에서 더 편하게 라우팅/바인딩/응답 생성을 할 수 있도록 추상화한 계층이다.
웹 서버에서 클라이언트 요청이 들어오면 결국 해야 할 일은 정해져 있다: 요청(Request)을 읽고, 응답(Response)을 만들어서 반환하는 것. 서블릿이 없었다면 개발자가 이 과정을 저수준에서 직접 조립해야 해서, 비즈니스 로직 외적인 코드(파싱, 인코딩, 응답 헤더/바디 구성 등)가 과도하게 늘어나는 문제가 생긴다.
서블릿(Servlet)은 HttpServletRequest, HttpServletResponse라는 표준 객체를 통해 HTTP 요청/응답을 다루는 공통 규격을 제공하고, 개발자는 핵심 로직에 더 집중할 수 있게 된다. 그리고 이후 등장한 Spring MVC의 Controller는 “서블릿을 기반으로” 더 많은 편의 기능을 얹어준 형태라고 보면 된다.
서블릿 기반의 요청 흐름은 다음처럼 이해하면 된다.
doGet(), doPost() 같은 메서드가 실행된다.HttpServletRequest로 요청 데이터를 꺼내고, HttpServletResponse로 응답을 구성한다.여기서 핵심은, 서블릿은 메서드 단위가 아니라 클래스 단위로 URL 매핑을 잡는다는 점이다.
아래 코드는 /servlet/get으로 들어오는 GET 요청을 처리한다. 예를 들어 http://localhost:8080/servlet/get?id=1로 요청하면 req.getParameter("id")로 쿼리 파라미터를 꺼내고, 응답은 JSON으로 내려준다.
포인트는 “응답을 개발자가 직접 세팅”한다는 것이다.
resp.setContentType("application/json")resp.setCharacterEncoding("UTF-8")resp.getWriter()로 바디를 직접 작성ObjectMapper로 객체를 JSON 문자열로 변환@Slf4j
@WebServlet("/servlet/get")
public class ServletReqGet extends HttpServlet {
private final ObjectMapper objectMapper;
@Autowired
public ServletReqGet(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String id = req.getParameter("id");
log.info(id);
Member member = new Member("hong1", "hong1@naver.com");
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
PrintWriter printWriter = resp.getWriter();
printWriter.print(objectMapper.writeValueAsString(member));
printWriter.flush();
}
}
이 예제는 “컨트롤러가 해주던 일”을 서블릿에서 직접 처리하는 감각을 익히기에 좋다. Spring MVC에서는 이런 작업을 @ResponseBody나 ResponseEntity, 메시지 컨버터 등이 대부분 대신 처리해준다.
POST 요청의 body가 application/x-www-form-urlencoded인 경우에도 서블릿에서는 req.getParameter()로 값을 꺼낼 수 있다. 예제에서는 name, email을 받고, 응답은 단순 텍스트 "OK"로 내려준다.
@WebServlet("/servlet/post")
public class ServletReqPost extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = req.getParameter("name");
String email = req.getParameter("email");
System.out.println(name);
System.out.println(email);
resp.setContentType("text/plain");
resp.setCharacterEncoding("UTF-8");
PrintWriter printWriter = resp.getWriter();
printWriter.print("OK");
printWriter.flush();
}
}
여기서도 마찬가지로 응답 헤더/인코딩/바디를 직접 조립한다는 점이 서블릿의 특징이다.
서블릿 3.0 이후부터는 web.xml 대신 @WebServlet 같은 애노테이션으로도 서블릿 등록이 가능하다. 다만 스프링 부트 프로젝트에서 @WebServlet을 자동 등록하려면, 서블릿 컴포넌트를 스캔하도록 설정이 필요하다.
그래서 메인 클래스에 @ServletComponentScan을 붙여 @WebServlet 기반 구성요소를 스캔/등록한다.
@SpringBootApplication
@EnableScheduling
@ServletComponentScan
public class BasicApplication {
public static void main(String[] args) {
SpringApplication.run(BasicApplication.class, args);
}
}
여기서 @EnableScheduling은 “스케줄러 기능 활성화” 용도고, 서블릿과는 별개의 관심사다. 즉, 이 프로젝트는 “서블릿 실습도 하고 + 스케줄링도 켜둔 상태”라고 이해하면 된다.
서블릿과 컨트롤러는 경쟁 관계라기보다 “기반 기술과 추상화 계층” 관계로 보는 게 정확하다.
@WebServletHttpServletRequest/Response를 직접 다루며, 응답도 직접 조립하는 코드가 많아짐@Controller / @RestController@GetMapping, @PostMapping)정리하면, 서블릿은 Spring MVC/REST의 근간이고, 컨트롤러는 그 위에서 생산성을 높여주는 고수준 API다. 서블릿을 이해하면 “스프링이 내부에서 뭘 대신해주는지”가 더 명확해져서, 디버깅이나 성능/동작 원리 이해에도 도움이 된다.