목차
1. Spring@MVC
2. @Controller
3. @Controller 단위 테스트
4. Handler 메서드의 파라미터
Spring MVC란?
- MVC 기반(model2)의 Web Application을 작성하기 위한 Spring Framework의 하위 모듈
- Front controller pattern : Dispatcherservlet
- / 경로를 처리하므로 모든 요청을 받아들임
- Action parameter방식이 아닌 URL 기반으로 요청 분기
- 주요 특징
- Annotaion 기반 -> spring @MVC
- 다양한 view 지원
- restful 웹 서비스 지원
- 테스트 용
Spring@MVC 구성요소
- DispatcherServlet
- 가장 중추적인 역할
- Front Controller pattern의 적용으로 client의 모든 request 접수
- 주로 다른 객체들 (Infrastructure Component)에게 위임하여 처리
- Spring@MVC Infrastructure Component
- Handler Mapping : 이런 request 받았는데 어떤 Handler가 처리할 수 있나요?
- Handler Adapter : XXHandler에게 request 좀 처리 해달라고 해~
- ViewResolves : 이런 뷰가 필요한데 부탁해요~
- 프로그래머가 작성할 부분
- model 영역(service/dao)
- View page
- Handler
Spring MVC이전 웹 프로그래밍
- 서블릿
/**
* 회원 관련 요청을 처리하는 서블릿 컨트롤러 클래스.
* URL 패턴 "/member" 로 들어오는 모든 요청을 이 클래스가 담당합니다.
*/
@WebServlet("/member")
public class MemberControllerServlet extends HttpServlet {
// 비즈니스 로직을 수행할 Service 객체를 생성 (구현체는 MemberServiceImpl)
private final MemberService mService = new MemberServiceImpl();
/**
* 모든 HTTP 메서드(GET, POST 등)는 service() 메서드를 통해 처리됩니다.
* 요청 파라미터 "action" 에 따라 알맞은 메서드를 호출해 분기 처리합니다.
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 클라이언트 요청으로 전달된 action 값을 읽어옴
String action = req.getParameter("action");
// action 파라미터가 없는 경우 400 Bad Request 에러 전송
if (action == null) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Action parameter is missing.");
return;
}
// action 값에 따라 서로 다른 기능 메서드 호출
switch (action) {
case "member-detail" ->
memberDetail(req, resp); // 회원 상세 조회
// case "member-list" -> memberList(req, resp); // 회원 목록 조회 예시
// case "member-update" -> memberUpdate(req, resp); // 회원 정보 수정 예시
default ->
// 정의되지 않은 action 요청이면 404 Not Found 에러 전송
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Unknown action: " + action);
}
}
/**
* 회원 상세 정보를 조회하여 JSP로 포워딩하는 메서드.
* 요청 파라미터 "email" 을 기준으로 DB를 조회합니다.
*/
private void memberDetail(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 요청 파라미터로 전달된 이메일을 읽어옴
String email = request.getParameter("email");
// 이메일이 없거나 빈 문자열인 경우 오류 메시지를 설정
if (email == null || email.isBlank()) {
request.setAttribute("alertMsg", "Email 파라미터가 없습니다.");
} else {
try {
// Service를 호출해 이메일에 해당하는 회원 정보 조회
Member member = mService.selectDetail(email);
// 조회된 회원 객체를 request 범위에 "member" 속성으로 저장
request.setAttribute("member", member);
} catch (SQLException e) {
// SQL 예외 발생 시 스택 트레이스 출력
e.printStackTrace();
// JSP에서 출력할 수 있도록 alertMsg 속성에 오류 메시지 저장
request.setAttribute("alertMsg", "회원 정보 조회 중 오류 발생: " + e.getMessage());
}
}
// 최종적으로 JSP 페이지로 포워드하여 화면 렌더링
request.getRequestDispatcher("/member/member-detail.jsp")
.forward(request, response);
}
// TODO: 추가적인 액션을 처리할 메서드를 아래에 구현할 수 있습니다.
// 예) 회원 목록 조회, 회원 정보 수정, 회원 삭제 등
}
Model 2 아키텍처 (Servlet + JSP)
Servlet: Controller 역할
JSP: View 역할
Servlet에서 비즈니스 로직(또는 Service/DAO 호출)을 수행하고, 그 결과를 request.setAttribute()에 담아 JSP로 포워드
web.xml 기반 서블릿 매핑
web.xml에 <servlet>과 <servlet-mapping>으로 서블릿과 URL을 일일이 연결
요청이 들어오면 DispatcherServlet 같은 중앙 조정기가 없으므로, 각 서블릿이 직접 request.getParameter("action") 등을 보고 분기 처리
HttpServlet 기반 컨트롤러
수동 분기: switch나 if-else로 파라미터에 따라 메서드 호출
직접 포워드: RequestDispatcher를 사용해 JSP에 전달
JSP 스크립틀릿: <% Member m = (Member) request.getAttribute("member"); %> 형태로 값 출력
단점 및 한계
유지보수 어려움 : 액션이 늘어날수록 service()/doGet() 메서드 안의 분기문이 복잡해짐
반복 코드 : 파라미터 읽기·예외 처리·포워드 코드가 서블릿마다 중복
관심사의 분리(S.o.C) 미흡 : 웹 설정(web.xml), 컨트롤러, 뷰, 비즈니스 로직이 느슨하게만 분리
테스트 불편 : 서블릿 컨테이너 없이 단위 테스트가 거의 불가능
그렇다면 위의 Servlet이 Spring MVC코드로 어떻게 변환되는지 보기 전에 그림을 통해서 어떻게 동작하는지 알아보자.
mvc패턴 동작 방식
서버 시작 시
- 스프링 컨텍스트가 로드되면서 @RequestMapping, XML 설정 등에 정의된 컨트롤러 매핑 정보들이 HandlerMapping에 등록된다.
요청 처리 흐름
- 클라이언트 요청 수신
클라이언트가 URL/파라미터를 포함한 HTTP 요청을 보내면, 스프링의 DispatcherServlet이 가장 먼저 이 요청을 받는다. - 핸들러 조회
DispatcherServlet은 등록되어 있는 HandlerMapping들에게 “이 요청을 처리할 컨트롤러가 어디에 있나요?”라고 묻고, URL·HTTP 메서드에 맞는 핸들러(컨트롤러) 정보를 받아온다. - 핸들러 어댑터 선택
찾은 핸들러를 실행할 수 있는 HandlerAdapter를 DispatcherServlet이 결정한다. (예: RequestMappingHandlerAdapter) - 컨트롤러 호출
선택된 HandlerAdapter가 내부에서 실제 컨트롤러의 메서드(@RequestMapping이 붙은 메서드 등)를 호출한다.- 이때 컨트롤러는 Service → Repository를 차례로 호출해 비즈니스 로직과 데이터 조회/변경을 수행하고,
- 결과 데이터를 Model에 담고 반환할 뷰 이름(view name)을 함께 내놓는다.
- ModelAndView 취합
HandlerAdapter는 컨트롤러가 돌려준 ModelAndView 객체(모델 데이터 + 뷰 이름)를 DispatcherServlet에 전달한다. - 뷰 해석
DispatcherServlet은 ModelAndView에서 꺼낸 뷰 이름을 ViewResolver에 넘겨, 실제 렌더링 가능한 뷰 객체(JSP 뷰, Thymeleaf 뷰 등)를 얻어낸다. - 뷰 렌더링
얻어온 뷰 객체에 모델 데이터를 주입한 뒤 render(model, request, response)를 호출해 HTML/CSS/JS 결과물을 생성한다. - 응답 반환
최종적으로 생성된 HTML이 HTTP 응답 바디에 담겨 클라이언트(브라우저)로 전송되고, 사용자는 화면을 확인할 수 있다.
sevlet에서 spring mvc으로 변환 및 각 어노테이션 설명
Spring MVC 버전
@Controller
@RequestMapping("/member")
@RequiredArgsConstructor
public class MemberController {
private final MemberService mService;
/**
* 회원 상세 조회
* GET /member/detail?email={email}
*/
@GetMapping("/detail")
public String memberDetail(
@RequestParam("email") String email,
Model model) {
if (email.isBlank()) {
model.addAttribute("alertMsg", "Email 파라미터가 없습니다.");
} else {
try {
Member member = mService.selectDetail(email);
model.addAttribute("member", member);
} catch (SQLException e) {
model.addAttribute("alertMsg", "조회 중 오류: " + e.getMessage());
}
}
// View 이름 반환 → /WEB-INF/views/member/member-detail.jsp 로 포워드
return "member/member-detail";
}
}
각 어노테이션 설명
@Controller | MVC의 “Handler” 컴포넌트임을 선언. 스프링이 빈으로 관리하여 DispatcherServlet이 호출할 수 있게 함. |
@RequestMapping | 클래스·메서드 수준에서 URL 매핑 지정. 클래스 위에 쓰면 하위 모든 메서드에 공통 경로 prefix 적용. |
@GetMapping | @RequestMapping(method = GET)의 축약형. 주로 조회용 HTTP GET 요청 처리 메서드에 사용. |
@PostMapping | @RequestMapping(method = POST)의 축약형. 폼 제출 등 HTTP POST 요청 처리에 사용. |
@RequestParam | 쿼리 파라미터(?foo=bar)나 폼 필드 값을 메서드 파라미터로 바인딩. required, defaultValue 지정 가능. |
@PathVariable | URL 경로에 포함된 {id} 같은 변수를 메서드 파라미터로 바인딩. RESTful URI 설계 시 자주 사용. |
Model | 컨트롤러에서 뷰로 넘길 데이터를 담는 컨테이너. model.addAttribute("key", value) 후 뷰 템플릿에서 ${key}로 참조. |
@Autowired | (필드/생성자/세터) 의존성 자동 주입. 요즘은 생성자 주입 + Lombok의 @RequiredArgsConstructor 조합이 권장됨. |
@ResponseBody | 메서드 반환값을 뷰가 아닌 HTTP 응답 바디(JSON·문자열 등)로 바로 직렬화. @RestController안에 기본 포함. |
@RestController | @Controller + @ResponseBody의 합성 어노테이션. REST API용 핸들러임을 선언. |
이렇게 바꾸면…
- 분기 로직(switch, action 파라미터)이 사라지고
- 메서드별 매핑으로 URL과 HTTP 메서드가 직관적으로 드러나며
- 뷰 포워드는 단순히 문자열 리턴으로 대체되어 코드 중복이 크게 줄어든다.
'SpringBoot' 카테고리의 다른 글
AOP(Aspect Oriented Programming) - (2) (0) | 2025.04.25 |
---|---|
AOP(Aspect Oriented Programming) - (1) (0) | 2025.04.25 |
DI(Dependency Injection) - (3) (0) | 2025.04.18 |
DI(Dependency Injection) - (2) (0) | 2025.04.18 |
DI(Dependency Injection) - (1) (1) | 2025.04.18 |