MVC 패턴, 왜 필요한가?
웹 애플리케이션을 처음 만들 때는 모든 코드를 한 파일에 작성하기 쉽습니다. 하지만 프로젝트가 커지면 문제가 생깁니다.
문제 상황
// 모든 것이 섞여있는 코드
public void processRequest() {
// DB 연결 코드 - 데이터베이스 접속 설정
// 비즈니스 로직 - 회원가입, 로그인 등의 실제 기능
// HTML 생성 코드 - 사용자에게 보여줄 화면 만들기
// 다 섞여있어서 수정이 어려움!
// 예: HTML만 수정하려는데 DB 코드까지 건드려야 함
}
MVC로 분리하면?
세 가지 역할로 명확하게 분리됩니다:
- Model: 데이터와 비즈니스 로직만 담당
- View: 화면 표시만 담당
- Controller: 요청을 받아서 Model과 View를 연결
결과: 각자 역할이 명확하니 수정할 때 해당 부분만 찾아서 고치면 됩니다!
Spring MVC란?
Spring Framework에서 제공하는 웹 애플리케이션 개발 프레임워크입니다.
MVC 패턴을 쉽게 구현할 수 있도록 다양한 기능을 제공합니다.
핵심 특징 3가지
- 어노테이션 기반으로 간단한 설정
- 자동으로 요청을 적절한 메서드로 연결
- 데이터 처리, 검증 등 편리한 기능 제공
Spring MVC 동작 흐름 (7단계)
① 사용자가 URL 요청 (예: /member/login)
↓
② DispatcherServlet이 요청을 받음 (프론트 컨트롤러)
↓
③ HandlerMapping이 적절한 Controller 찾음
↓
④ Controller가 비즈니스 로직 수행
↓
⑤ Model에 데이터 담아서 View 이름 반환
↓
⑥ ViewResolver가 실제 View 파일 찾음
↓
⑦ View가 최종 HTML 생성해서 사용자에게 응답
DispatcherServlet이 뭔가요?
모든 요청의 입구 역할을 하는 프론트 컨트롤러입니다.
- 예전: URL마다 서블릿을 따로 만들어야 했음
- 지금: DispatcherServlet 하나가 모든 요청을 받아서 적절한 Controller로 분배
만약 Spring MVC에서 Rest API를 적용한다면
① 사용자가 URL 요청 (예: /member/login)
↓
② DispatcherServlet이 요청을 받음 (프론트 컨트롤러)
↓
③ HandlerMapping이 적절한 Controller 찾음
↓
④ RestController가 비즈니스 로직 수행
↓
⑤ 객체를 직접 반환
↓
⑥ MessageConverter가 객체를 JSON으로 변환
↓
⑦ JSON 데이터 응답 (View 없음!)
실전 코드 예제
1. Controller 작성
// @Controller: 이 클래스가 컨트롤러임을 Spring에게 알려줌
// Spring이 자동으로 이 클래스를 찾아서 Bean으로 등록
@Controller
// @RequestMapping: 이 컨트롤러가 처리할 기본 URL 경로 지정
// /member로 시작하는 모든 요청은 이 컨트롤러가 담당
@RequestMapping("/member")
public class MemberController {
// @Autowired: Spring이 자동으로 MemberService 객체를 주입해줌
// 개발자가 직접 new MemberService()를 하지 않아도 됨
@Autowired
private MemberService memberService;
// 회원 목록 조회
// @GetMapping: GET 방식의 /member/list 요청을 이 메서드가 처리
// 즉, 최종 URL은 /member/list가 됨
@GetMapping("/list")
public String memberList(Model model) {
// Service에서 모든 회원 정보를 가져옴
List<Member> members = memberService.getAllMembers();
// Model에 데이터를 담음 - JSP에서 ${members}로 사용 가능
// "members"는 JSP에서 사용할 변수명
model.addAttribute("members", members);
// "member/list"를 반환 - ViewResolver가 member/list.jsp를 찾아줌
return "member/list";
}
// 회원 가입 폼 보여주기
// GET /member/join 요청 시 실행
@GetMapping("/join")
public String joinForm() {
// 단순히 회원가입 폼 화면만 보여줌
// member/joinForm.jsp로 이동
return "member/joinForm";
}
// 회원 가입 처리
// @PostMapping: POST 방식의 /member/join 요청을 처리
// 폼 제출(submit) 시 실행됨
@PostMapping("/join")
public String join(@ModelAttribute Member member) {
// @ModelAttribute: 폼의 input 데이터를 Member 객체로 자동 변환
// 예: <input name="name"> → member.setName(값)
// <input name="email"> → member.setEmail(값)
// 개발자가 일일이 request.getParameter() 할 필요 없음!
// 실제 회원가입 로직 수행 (DB에 저장)
memberService.registerMember(member);
// redirect: 는 다른 URL로 재요청하라는 의미
// 회원가입 후 목록 페이지로 이동
// 주의: "redirect:" 없이 그냥 반환하면 뷰 이름으로 인식됨
return "redirect:/member/list";
}
// 회원 상세 조회
// @GetMapping에서 {id}는 변수를 의미
// 예: /member/detail/123 → id에 123이 들어감
@GetMapping("/detail/{id}")
public String memberDetail(@PathVariable Long id, Model model) {
// @PathVariable: URL 경로의 {id} 값을 파라미터로 받음
// /member/detail/123 요청 시 → id = 123
// 특정 회원 정보를 id로 조회
Member member = memberService.getMemberById(id);
// 조회한 회원 정보를 Model에 담음
model.addAttribute("member", member);
// member/detail.jsp로 이동
return "member/detail";
}
}
코드 설명 정리
| 어노테이션 | 설명 |
|---|---|
@Controller |
이 클래스가 Controller 역할임을 Spring에게 알림 |
@RequestMapping("/member") |
/member로 시작하는 URL 처리 |
@GetMapping, @PostMapping |
HTTP 메서드별 매핑 |
@ModelAttribute |
폼 데이터를 객체로 자동 변환 (예: 이름, 이메일 → Member 객체) |
@PathVariable |
URL 경로의 값을 파라미터로 받음 (예: /detail/123 → id=123) |
Model |
View로 전달할 데이터를 담는 객체 |
2. Service 계층
// @Service: 이 클래스가 비즈니스 로직을 담당하는 Service임을 표시
// Spring이 자동으로 Bean으로 등록
@Service
public class MemberService {
// @Autowired: Repository를 자동으로 주입받음
// Repository는 실제 DB 작업을 수행하는 계층
@Autowired
private MemberRepository memberRepository;
// 모든 회원 조회
public List<Member> getAllMembers() {
// Repository의 findAll() 메서드로 DB에서 모든 회원 조회
return memberRepository.findAll();
}
// 회원 가입 처리
public void registerMember(Member member) {
// 비즈니스 로직: 이메일 중복 검사
// DB에 이미 같은 이메일이 있는지 확인
if (memberRepository.existsByEmail(member.getEmail())) {
// 중복이면 예외 발생 - 회원가입 실패
throw new RuntimeException("이미 존재하는 이메일입니다.");
}
// 중복이 아니면 DB에 회원 정보 저장
memberRepository.save(member);
}
// 특정 회원 조회
public Member getMemberById(Long id) {
// findById()는 Optional<Member>를 반환
// orElseThrow(): 값이 없으면 예외 발생, 있으면 Member 반환
return memberRepository.findById(id)
.orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다."));
}
}
3. View (JSP 예시)
<!-- member/list.jsp -->
<!-- JSTL 태그 라이브러리 사용 선언 -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h1>회원 목록</h1>
<table>
<thead>
<tr>
<th>번호</th>
<th>이름</th>
<th>이메일</th>
</tr>
</thead>
<tbody>
<c:forEach items="${members}" var="member">
<tr>
<td>${member.id}</td>
<td>${member.name}</td>
<td>${member.email}</td>
</tr>
</c:forEach>
</tbody>
</table>
주요 어노테이션 정리
| 어노테이션 | 역할 |
|---|---|
@Controller |
해당 클래스를 Controller로 등록 |
@RestController |
REST API용 Controller (JSON 반환) |
@RequestMapping |
URL과 메서드를 매핑 |
@GetMapping |
GET 요청 처리 |
@PostMapping |
POST 요청 처리 |
@RequestParam |
쿼리 파라미터 받기 (?name=value) |
@PathVariable |
URL 경로 변수 받기 (/member/{id}) |
@ModelAttribute |
폼 데이터를 객체로 변환 |
@ResponseBody |
반환값을 HTTP 응답 본문으로 전달 |
Spring MVC vs 일반 Servlet
비교: 같은 기능 구현하기
일반 Servlet 방식
// @WebServlet: 이 Servlet이 /member/list URL을 처리
@WebServlet("/member/list")
public class MemberListServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
// 1. 파라미터를 직접 추출해야 함
String page = request.getParameter("page");
// 2. null 체크, 타입 변환 등을 직접 해야 함
int pageNum = 1;
if (page != null) {
pageNum = Integer.parseInt(page);
}
// 3. 비즈니스 로직을 직접 호출
List<Member> members = // DB 조회
// 4. request 객체에 직접 데이터를 설정
request.setAttribute("members", members);
// 5. JSP로 forward를 직접 수행
request.getRequestDispatcher("/WEB-INF/views/list.jsp")
.forward(request, response);
}
}
Spring MVC 방식
@Controller
public class MemberController {
@GetMapping("/member/list")
public String list(
// @RequestParam: 파라미터 자동 추출 및 타입 변환
// defaultValue: page 파라미터가 없으면 기본값 1 사용
@RequestParam(defaultValue="1") int page,
// Model: 자동으로 생성되어 주입됨
Model model) {
// Service를 통해 데이터 조회 (의존성 주입으로 간단)
List<Member> members = memberService.getMembers(page);
// Model에 데이터 추가 - request.setAttribute() 대신
model.addAttribute("members", members);
// View 이름만 반환 - ViewResolver가 자동으로 JSP 찾아줌
// "list" → /WEB-INF/views/list.jsp
return "list";
}
}
무엇이 달라졌나?
| 항목 | 일반 Servlet | Spring MVC |
|---|---|---|
| 파라미터 처리 | 직접 추출, null 체크, 타입 변환 필요 | 자동으로 메서드 인자에 매칭 및 변환 |
| 데이터 전달 | request.setAttribute() | Model 객체 사용 |
| View 연결 | forward() 직접 호출, 전체 경로 필요 | View 이름만 반환 |
| URL 매핑 | @WebServlet 또는 web.xml | 어노테이션으로 간단히 설정 |
Spring MVC의 4가지 장점
1. 생산성 향상
반복적인 코드를 Spring이 자동으로 처리 → 비즈니스 로직에 집중 가능
2. 유지보수 용이
계층이 명확히 분리 → 수정할 때 해당 부분만 찾아서 수정
3. 테스트 편의성
각 계층을 독립적으로 테스트 → 단위 테스트가 쉬움
4. 유연한 구조
Interceptor, Exception Handler 등 다양한 확장 포인트 제공
정리
Spring MVC는?
웹 애플리케이션을 MVC 패턴으로 쉽게 개발할 수 있게 해주는 프레임워크
핵심 4가지
- DispatcherServlet이 모든 요청을 받아서 처리
- Controller가 요청을 처리하고 Model에 데이터를 담음
- ViewResolver가 View를 찾아서 응답 생성
- 어노테이션으로 간단하게 설정 가능
'-- 오늘 있었던 개발 일기' 카테고리의 다른 글
| 더미 데이터에 시퀀스? (1) | 2026.01.29 |
|---|---|
| 테이블 설계... (0) | 2026.01.28 |
| aws에 대해하여! (0) | 2026.01.06 |
| 2026년도 새해가 왔다 (0) | 2026.01.05 |
| RESTful Api란? (0) | 2026.01.04 |