(책) 자바 웹을 다루는 기술

Chap 21 스프링 MVC 기능

EunaSon 2023. 9. 16. 19:22

21.1 스프링 프레임워크 MVC의 특징 802p                              

21.2 SimpleUrlController 이용해 스프링 MVC 실습하기 805p  

21.3 MultiActionController 이용해 스프링 MVC 실습하기 811p

21.4 MultiActionController 이용해 회원 정보 표시하기 816p    

21.5 요청명과 동일한 JSP 이름 표시하기 819p                     


21.1 스프링 프레임워크 MVC 특징

802p

스프링에서 지원하는 MVC 기능의 특징

- 모델2 아키텍처를 지원함

- 스프링과 다른 모듈과의 연계가 쉬움

- 타일즈 tiles 나 사이트메시 sitemesh 같은 View 기술과의 연계가 쉬움

- 태그 라이브러리를 통해 message 출력, theme 적용, 입력폼 구현 등을 쉽게 할 수 있음

 

스프링 프레임워크의 MVC 구조도

▼스프링 프레임워크 MVC 구성요소

구성 요소 설명
DispatcherServlet 클라이언트의 요청을 전달받아서 해당 요청에 대한 컨트롤러를 선택하여
클라이언트의 요청을 전달함.
또한 컨트롤러가 반환한 값을 View에 전달하여 알맞은 응답을 생성함.
HandlerMapping 클라이언트가 요청한 URL을 처리할 컨트롤러를 지정함
Controller 클라이언트의 요청을 처리한 후 그 결과를 DispatcherServlet에 전달함
ModelAndView 컨트롤러가 처리한 결과 및 뷰 선택에 필요한 정보를 저장함
ViewResolver 컨트롤러의 처리 결과를 전달할 뷰를 지정함
View 컨트롤러의 처리 결과 화면을 생성함

 

스프링 프레임워크 MVC 기능 수행 과정

1. 브라우저가 DispatcherServlet에 URL로 접근하여 해당 정보를 요청함

2. 핸들러 매핑에서 해당 요청에 대해 매핑된 컨트롤러가 있는지 요청함

3. 매핑된 컨트롤러에 대해 처리를 요청함

4. 컨트롤러가 클라이언트의 요청을 처리한 결과와 View 이름을 ModelAndVew에 저장해서 DispatcherServlet으로 반환함

5. DispatcherServlet에서는 컨트롤러에서 보내온 View 이름을 ViewResolver로 보내 해당 View를 요청함

6. ViewResolver는 요청을 View로 보냄

7. View의 처리 결과를 DispatcherServlet으로 보냄

8. DispatcherServlet은 최종 결과를 브라우저로 전송함

 

21.2 SimpleUrlController 이용해 스프링 MVC 실습하기

805p

브라우저의 요청 URL에 대해 미리 매핑해놓은 컨트롤러를 호출하여 컨트롤러에서 지정한 JSP를 브라우저로 전송하는 과정을 실습해보자.

1. 브라우저 - http://localhost:8090/pro21/test/index.do 로 요청함

2. DispatcherServlet - 요청에 대해 미리 action-servlet.xml에 매핑된 SimpleUrlController 를 요청함

3. 컨트롤러 - 요청에 대해 test 폴더에 있는 index.jsp를 브라우저로 전송함

 

서블릿에서는 브라우저 요청 처리 시 서블릿에서 제공하는 메서드를 이용해서 요청명을 일일이 가져왔음

-> 스프링에서는 브라우저의 요청을 쉽게 가져올 수 있는 여러 기능을 제공함

그 중 SimpleUrlController를 사용해보자

 

먼저, 스프링 설정을 위해

새 프로젝트 pro21 생서, 책에서 제공하는 스프링 3.0 라이브러리 파일들을 복사해서 /WebContent/WEB-INF/lib 폴더에 붙여넣음  

src 폴더에 com.spring.ex01 패키지 생성 > SimpleUrlController.java를 생성,

WebContent 폴더에 test 폴더 생성 후 index.jsp 파일 생성,

WebContent 폴더의 lib 폴더에 action-servlet.xml, web.xml 을 생성

 

* web.xml : 브라우저에서 *.do 로 요청 시 스프링-DispatcherServlet 클래스가 요청을 받을 수 있게 서블릿 매핑함

* action-servlet.xml : 스프링 프레임워크에서 필요한 빈들을 설정함

* SimpleUrlController.java : 매핑된 요청에 대해 컨트롤러의 기능을 수행함

* index.jsp : 요청에 대해 컨트롤러가 브라우저로 전송하는 JSP임

 

[ web.xml ]

브라우저에서 *.do 로 요청하면 스프링의 DispatcherServlet 클래스가 요청을 받을 수 있게 서블릿 매핑을 설정함,

<load-on-startup> 태그를 이용해서 톰캣 실행 시 미리 DispatcherServlet을 메모리에 로드하도록 함

<web-app ...>
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup> 
        // 값이 1 이상이면 톰캣 실행시 미리 메모리에 로드함(음수 or w지정 안하면 브라우저에서 요청시 메모리에 로드됨)
    </servlet>
    
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

 <servlet-mapping> - 들어오는 요청 

=> <servlet> - 해당 요청을 받을 애 지정함

<servlet>과 <servlet-mapping>은 짝을 짓는데(요청받는애 - 요청), 둘은 <servlet-name> 이 같은 것으로 서로를 인지함

 

[ action-servlet.xml ]

책에서 제공하는 action-servlet.xml에서 <beans> 태그 부분 복붙함,

SimpleUrlController 클래스에 대해 빈 생성,

SimpleUrlHandlerMapping 클래스에 대해 빈 생성 - 요청명 /test/index.do 에 대해 SimpleUrlController가 처리하도록 설정

<bean id="simpleUrlController" class="com.spring.ex01.SimpleUrlController" />
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/test/index.do">simpleUrlController</prop>
        </props>
    </property>
</bean>

SimpleUrlController 빈을 생성,

SimpleUrlHandlerMapping 빈을 생성->mappings 속성에 key="/test/index.do"를, value로 simpleUrlController를 주어서 key의 요청에 대해 value의 컨트롤러가 처리하게 해줌

 

*** 설정 파일 이름은 반드시 web.xml의 서블릿 매핑 시 사용했던 <servlet-name> 태그 값(action)으로 시작해야 함

-> 그래서 지금 설정파일 이름도 'action'-servlet.xml 임. action이 아닌 다른 값으로 설정하면 톰캣 실행 시 오류 발생  

 

 

[ SimpleUrlController 클래스 ]

설정 파일의 요청(/test/index.do)을 처리하기 위해서는 반드시 Controller 인터페이스를 구현해야 함,

해당 컨트롤러로 요청 시 ModelAndView 객체를 생성해서 응답할 JSP 이름인 index.jsp로 설정한 후 반환함

...
public class SimpleUrlController implements Controller {
    // 스프링에서 제공하는 Controller 인터페이스를 반드시 구현해야 함
    @Override
    public ModelAndView handleRequest(...) {
        return new ModelAndView("index.jsp"); // 뷰 이름을 ModelAndView에 index.jsp로 설정후 반환
    }
}

 

=> 컨트롤러에서 ModelAndView의 인자로 설정된 index.jsp가 화면에 출력됨

 

브라우저-요청 (http://localhost:8090/pro21/test/index.do)

-> DispatcherServlet이 받음 (web.xml - *.do 로 들어오는 요청은 DispatcherServlet이 받도록 설정됨)

-> SimpleUrlController로 전달 (action-servlet.xml - /test/index.do 요청을 SimpleServletController가 처리하도록 설정)

-> index.jsp (컨트롤러 - ModelAndView의 인자로 index.jsp를 설정 후 반환함)

 

 

21.3 MulitActionController 이용해 스프링 MVC 실습하기

811p

SimpleUrlController : 요청명1 -- 컨트롤러1 필요 (각 요청명에 대해 Controller 인터페이스 구현한 각각의 컨트롤러 필요)

MultiActionController : 요청명 여러개 -- 컨트롤러1(메서드 여러개) 필요

(컨트롤러 하나 안에 구현된 각 메서드로 요청 처리)

 

▼스프링에서 제공하는 클래스들

클래스 설명
MultiActionController URL 요청명으로 바로 컨트롤러를 지정해서 사용
PropertiesMethodNameResolver URL 요청명으로 컨트롤러의 설정 파일에서 미리 설정된 메서드를 바로 호출해서 사용할 수 있음
InternalResourceViewResolver JSP나 HTML 파일 등 웹 애플리케이션의 내부 자원을 이용해 뷰를 생성함. 기본적으로 사용하는 View 클래스이며 prefix와 suffix 프로퍼티를 이용해 경로를 지정할 수 있음 

 

실습에 필요한 파일

web.xml : 브라우저에서 *.do로 요청 시 스프링의 DispatcherServlet이 요청을 받도록 서블릿 매핑을 설정함

action-servlet.xml : 스프링에서 필요한 빈들을 설정함

UserController.java : 매핑된 요청에 대해 컨트롤러의 기능을 수행함

loginForm.jsp : 로그인 창

result.jsp : 로그인 결과를 보여주는 JSP

 

com.spring.ex02 패키시 생성-UserController 클래스 생성

loginForm.jsp, result.sp를 test폴더에 추가

 

[ action-servlet.xml ] - 수정

...
// 1.
<bean id="viewResolver" 
      class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframeword.web.servlet.view.JstlView"/>
    <property name="prefix" value="/test/" />
    <property name="suffix" value=".jsp" />
</bean>

// 2.
<bean id="userUrlMapping" 
      class="org.springframework.web.servlet.handler.SimpleUrlHanderMapping">
    <property name="mappings">
        <props>
            <prop key="/test/*.do">userController</prop>
        </props>
    </property>
</bean>

// 3.
<bean id="userController" class="com.spring.ex02.UserController">
    <property name="methodNameResolver">
        <ref local="userMethodNameResolver"/>
    </property>
</bean>

// 4.
<bean id="userMethodNameResolver" 
      class=""org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver>
    <property name="mappings">
        <props>
            <prop key="/test/login.do">login</prop>
        </props>
    </property>
</bean>

// 1. viewResolver 빈 (InternalResourceViewResolver)

컨트롤러에서 ModelAndView에 인자로 담아주는 뷰 이름 앞 뒤에 prefix와 suffix를 붙여서 해당 이름을 가진 파일 찾아서 dispatcherservlet으로 보냄 (test 폴더의 ###.jsp 파일)

 

// 2. userUrlMapping 빈 (SimpleUrlHandlerMapping)

/test/*.do 이름으로 들어오는 오는 요청을 userController (만들어준 컨트롤러) 빈이 처리하도록 함

 

// 3. userController 빈 (UserController)

methodNameResolver 속성에 userMethodNameResolver 빈을 주입함

 

// 4. userMethodNameResolver 빈 (PropertiesMethodNameResolver)

/test/login.do 이름으로 들어오는 요청을 주입된 빈(userController) 안의 login 메서드가 처리하도록 함

 

[ UserController 클래스 ]

MultiActionController 를 상속받아서 지정한 요청명에 대해 바로 메서드를 호출할 수 있도록 함

(methodNameResolver 속성 이용함),

ModelAndView 객체에 setViewName() 메서드로 JSP 이름을 설정 후 반환하도록 함

...
public class UserController extends MultiActionController {
    // 설정파일-userMethodNameResolver 속성을 사용하려면 MultiActionController를 상속 필수임
    public ModelAndView login (...) {
        String userID = "";
        String passwd = "";
        ModelAndView mav = new ModelAndView();
        request.setCharacterEncoding("utf-8");
        userID = request.getParameter("userID");
        passwd = request.getParameter("passwd"); // 로그인창에서 넘어온 매개변수 받아와 저장
        mav.addObject("userID", userID);
        mav.addObject("passwd", passwd); // ModelAndView 객체에 로그인 정보를 바인딩함(addObject())
        mav.setViewName("result"); // ModelAndView 객체에다 포워딩할 jsp 이름을 설정해줌
        return mav;
    }
}

 

[ loginForm.jsp ]

로그인 창, id와 비밀번호 입력받아서 ${contextPath}/test/login.do 로 요청함

 

[ result.jsp ]

로그인 결과 창, 로그인 창에서 전송된 id와 비밀번호를 컨트롤러에서 바인딩 해 넘겼음(addObject() 메서드 이용했음), 받아와서 출력함

 

=> DispatcherServlet 클래스 - 컨트롤러에서 ModelAndView 객체에 설정한 뷰 이름 result 와 action-servlet.xml의 InternalResourceViewResolver 에 설정한 뷰 이름 (prefix - /test/ , suffix - .jsp) 를 조합해서 ' /test/result.jsp ' 를 찾은 후 브라우저로 전송함

 

 

21.4 MultiActionController 이용해 회원 정보 표시하기

816p

이번에는 로그인 창이 아니라 회원 정보 입력창에서 회원 정보를 입력 후 전송된 회원 정보를 표시해보자.

 

[ action-servlet.xml ] - 추가

회원 정보 요청 URL(/test/memberInfo.do)에 대해 처리할 메서드(memberInfo - 컨트롤러에 추가됨) 를 추가함

 

...

<bean id="userMethodNameResolver" 
      class=""org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver>
    <property name="mappings">
        <props>
            <prop key="/test/login.do">login</prop>

            <prop key="/test/memberInfo.do">memberInfo</prop> // 이 줄만 추가하면 됨
        </props>
    </property>
</bean>

...

 

[ UserController 클래스 ] - 추가

회원가입창에서 전송된 회원 정보를 가져와서 ModelAndView에 회원 정보를 바인딩해서 memberInfo.jsp로 전달하는 memberInfo 메서드를 추가함

...

public ModelAndView memberInfo(...) {

  request.인코딩설정

  ModelAndView 객체 mav 생성

  request.getParameter()로 회원가입창에서 넘어온 id, pwd, name, email 변수에 저장함

  mav.addObject("id", id) 등으로 ModelAndView에 id, pwd, name, email을 바인딩함,

  mav.setViewName("memberInfo"); 로 memberInfo.jsp로 포워딩되게 함

  return mav;  

}

...

 

[ memberForm.jsp ] 

회원가입창, 회원정보 입력 후 action 속성의 /test/memberInfo.do 로 요청함

...

<form method="post" action="${contextPath}/test/memberInfo.do">

...

 

[ memberInfo.jsp ]

회원정보창, UserController의 memberInfo() 메서드를 통해 ModelAndView에 바인딩 되어 넘어온 회원 정보를 table 태그로 출력함

 

 

21.5 요청명과 동일한 JSP 이름 표시하기

819p

21.4에서는 요청명과 동일한 이름의 메서드를 호출했음,

이번에는 요청명과 동일한 이름의 JSP를 호출해보자.

 

[ UserController 클래스 ] - 수정

URL 요청명에서 .do 를 제외한 요청명을 가져오는 getViewName() 메서드를 추가함,

request를 getViewName() 의 인자로 전달해서 URL 요청명에서 .do를 제외한 뷰 이름을 가져옴(/test/login.do -> login 을 가져옴). setViewName() 으로 뷰 이름을 전달하면 자동적으로 해당 요청명의 이름으로 jsp 파일이 설정됨

 

public ModelAndView login (...) {

String userID = "";

String passwd = "";

String viewName = getViewName(request); // 요청명에서 / 앞과 . 뒤를 뗀 뷰 이름을 가져옴

 

ModelAndView 객체 mav 생성,

request의 인코딩 설정,

request.getParameter() 로 userID, passwd 가져와서 변수에 저장,

mav에 addObject() 로 설정 후

mav.setViewName(viewName); // 으로 뷰 이름 직접 쓰지 않고 요청명으로 바로 jsp 설정함

return mav;

}

 

private String getViewName(HttpServletRequest request) ... {

String contextPath = request.getContextPath();

String uri = (String)request.getAttribute("javax.servlet.include.request_uri"); // 요청 uri를 문자열로 가져옴

 

if (uri == null || uri.trim().equals("")) {

  uri = request.getRequestURI();

}

 

int begin = 0;

if (!((contextPath == null) || ("".equals(contextPath)))) {

  begin = contextPath.length();

}

 

int end = 0;

if (uri,indexOf(";") != -1) { // uri에 ; 가 있으면

  end = uri.indexOf(";");

} else if (uri.indexOf("?") != -1) { // uri에 ?가 있으면

  end = uri.indexOf("?");

} else {

  end = uri.length();

}

 

String fileName = uri.substring(begin, end); // pro21 뒤에서 끝까지 잘라옴 -> /test/login.do

if (fileName.indexOf(".") != -1) { // 파일명에 . 이 있으면

  fileName = fileName.substring(0, fileName.lastIndexOf(".")); // 처음부터 . 앞까지 잘라옴 -> /test/login

} // 확장명 잘라냄

if (fileName.lastIndexOf("/") != -1) {

  fileName = fileName.substring(fileName.lastIndexOf("/"), fileName.length()); // 경로명 잘라냄

}

return fileName;

}

 

??? 마지막 경로명 잘라낼 때 fileName.substring(fileName.lastIndexOf("/")+1 , fileName.length()) 여야되지 않아??