◇ PART · MVC

View · ViewResolver

JSP 가 어디 있는지 어떻게 알까

학습 목표

  • View 의 역할을 안다
  • ViewResolver 가 어떻게 ViewName 을 실제 파일로 매핑하는지 안다
  • prefix / suffix 의 의미를 안다

⚠️ 이상한 짧은 문자열

컨트롤러의 return 값

@GetMapping("/board/list")
public String list(Model model) {
    model.addAttribute("boards", service.findAll());
    return "board/list";        // ← 이 짧은 문자열이 어떻게 JSP 가 됨?
}

"board/list" 라는 문자열이 어떻게 /WEB-INF/views/board/list.jsp 로 변신할까?

🛠️ ViewResolver — 이름을 경로로

prefix + ViewName + suffix = 실제 경로

Spring 은 컨트롤러가 반환한 짧은 문자열(ViewName)에 약속된 prefix·suffix 를 붙여 실제 JSP 파일 경로를 만듭니다.


<!-- servlet-context.xml -->
<bean id="viewResolver" class="...InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>
"board/list" ↓ /WEB-INF/views/ + board/list + .jsp ↓ /WEB-INF/views/board/list.jsp

왜 /WEB-INF/views/ 안에?

/WEB-INF/ 폴더의 특성:

  • 외부에서 직접 URL 접근 불가 — 보안상 보호
  • 내부에서 forward·include 만 가능 (Spring 이 사용)
  • JSP 가 사용자에게 직접 노출되지 않음

→ 사용자가 localhost/WEB-INF/views/board/list.jsp 로 직접 접근 시도하면 — 404. 컨트롤러를 거쳐야만 JSP 가 보임.

실수 시 발생하는 오류

상황증상
JSP 파일 위치 잘못"Could not resolve view with name 'xxx'"
return 의 ViewName 오타404 또는 위 메시지
prefix/suffix 설정 빠짐이상한 경로로 찾으려 함
ViewName 에 .jsp 함께 넣음"...list.jsp.jsp" 가 됨 → 404

다른 ViewResolver 들

종류용도
InternalResourceViewResolverJSP (본 과정)
ThymeleafViewResolverThymeleaf (Boot 에서 자주)
FreeMarkerViewResolverFreeMarker
JsonViewResolverJSON 응답

👉 본 과정은 JSP. 후속 과정은 Thymeleaf 가 일반적.

redirect: 와 forward:


return "board/view";              // 일반 View — JSP 렌더링

return "redirect:/board/list";    // 브라우저에 302 — 다른 URL 로 다시 요청
return "forward:/board/list";     // 서버 내부에서 다른 컨트롤러로

👉 「redirect:」 와 「forward:」 는 ViewResolver 가 특별 처리하는 접두사. JSP 매핑 안 함.

JSP 의 표현 — JSTL + EL


<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!-- EL : Model 의 값 출력 -->
<h1>${board.title}</h1>

<!-- JSTL c:if : 조건 분기 -->
<c:if test="${board.writerId == sessionScope.loginUser.id}">
    <a>수정</a>
</c:if>

<!-- JSTL c:forEach : 반복 -->
<c:forEach var="b" items="${boards}">
    <tr><td>${b.title}</td></tr>
</c:forEach>

Model 의 데이터 접근


// Controller
model.addAttribute("board", b);
model.addAttribute("isOwner", b.getWriterId() == userId);
model.addAttribute("comments", commentService.list(b.getId()));

<!-- JSP 에서 -->
${board.title}              <!-- Board 객체의 getTitle() -->
${isOwner}                  <!-- boolean -->
${comments[0].content}      <!-- List 의 첫 요소 -->
${sessionScope.loginUser}   <!-- 세션의 객체 -->

실수 — JSP 에 자바 코드 직접

옛 스타일 — 권장 X

<%
    Board b = (Board) request.getAttribute("board");
    if (b.getWriterId() == 5) {
        out.print("<a>수정</a>");
    }
%>

2000 년대 초의 스타일. 현대 JSP 는 EL + JSTL 만으로 작성.

🔄 Before / After

전 차시 끝

컨트롤러의 return "board/list" 가 어떻게 JSP 로 도달하는지 막연.

이번 차시 끝

prefix + ViewName + suffix 의 변환 규칙을 안다. JSP 의 EL + JSTL 사용.

이번 차시의 데이터 흐름

Controller
"board/list"
ViewResolver
/WEB-INF/views/
board/list.jsp
HTML 응답
ViewResolver 가 「이름 → 경로」 변환의 다리

정리

오늘 들고 가는 것

  • ViewResolver = ViewName → 실제 View 파일 매핑
  • prefix + ViewName + suffix 의 조립 규칙
  • /WEB-INF/views/ 는 외부 직접 접근 차단
  • JSP 는 EL + JSTL 만으로 작성
  • redirect: / forward: 는 특별 접두사