[JSP] 커넥션 풀

커넥션 풀(Connection Pool)


1. 커넥션 풀(DBCP)

데이터베이스와 연결된 커넥션을 미리 만들어서 풀(pool) 속에 저장해 두고 있다가 필요할 때 커넥션을 풀에서 쓰고 다시 풀에 반환하는 기법을 말한다.

웹 프로그램에서는 데이터베이스의 환경설정과 연결 관리 등을 따로 XML파일이나 속성 파일을 사용해서 관리하고, 이렇게 설정된 정보를 이름을 사용하여 획득하는 방법을 사용한다.

- 웹 컨테이너가 실행되면서 커넥션(connection) 객체를 미리 풀(pool)에 생성해 둡니다.

- DB와 연결된 커넥션(connection)을 미리 생성해서 풀(pool) 속에 저장해 두고 있다가 필요할 때에 가져다 쓰고 반환한다.

- 미리 생성해두기 때문에 데이터베이스에 부하를 줄이고 유동적으로 연결을 관리 할 수 있다.

이렇게 풀 속에 미리 생성되어 있는 커넥션을 가져다가 사용하고, 사용이 끝나면 커넥션을 풀에 반환한다.


2. 커넥션풀(DBCP) 사용 이유


만약 한명의 접속자가 웹 사이트에 접속했다고 가정한다. 해당 웹 사이트에서 접속자는 게시판을 확인하고 자신이 쓴 게시물을 수정하고 또 새로운 게시글을 등록한다고 가정해보자.

그럼 이 한명의 접속자로 인해 DB접속은 아래와 같이 발생한다.

1) 데이터 취득

2) 검색 후 데이터 취득

3) 데이터 갱신

4) 데이터 새등록


즉 한명의 접속자로 인해 단 시간에 4번의 DB 접속이 일어난다.

그럼 웹상에서 아주 짧은 시간에 몇번의 DB 접속이 일어날까? 만약 접속자가 1000명 이라면? 즉 커넥션 풀이란 미리 커넥션 객체를 생성하고 해당 커넥션 객체를 관리하는것을 의미한다.

즉 '커넥션 풀에 DB와 연결을 해 놓은 객체를 두고 필요할 때마다 커넥션 풀에서 빌려온다' 라고 이해하면 개념잡기에 쉬울듯 하다.

그리고 연결이 끝나면 다시 풀에 돌려준다.

커넥션 풀을 너무 크게 해놓으면 당연히 메모리 소모가 클것이고, 적게 해놓으면 커넥션이 많이 발생할 경우 대기시간이 발생할 것이다. 즉 웹 사이트 동시 접속자수 등 서버 부하에 따라 크기를 조정해야 할것이다.


cp.) 

서버는 동시에 사용할 수 있는 사람의 수라는 개념이 존재합니다.일반적인 커넥션을 이용하면 동시 접속자 수를 벗어나게 될 경우 에러(예외)가 발생하게 됩니다.

예외가 발생하면 그 접속자는 더이상 처리를 하지 못하므로, 사이트 이용자는 다시 접속을 시도해야하는 불편함이 있습니다.이를 해결하기 위해 탄생한 것이 커넥션 풀 입니다.

 커넥션 풀이란 동시 접속자가 가질 수 있는 커넥션을 하나로 모아놓고 관리한다는 개념입니다. 누군가 접속하면 자신이 관리하는 풀에서 남아있는 커넥션을 제공합니다.

하지만 남아있는 커넥션이 없는 경우라면 해당 클라이언트는 대기 상태로 전환시킵니다. 그리고 커넥션이 다시 풀에 들어오면 대기 상태에 있는 클라이언트에게 순서대로 제공합니다.

- JDBC를 통하여 DB에 연결하기 위해서는 드라이버(Driver)를 로드하고 커넥션(connection) 객체를 받아와야 한다.

- JDBC를 사용하면 사용자가 요청을 할 때마다 매번 드라이버를 로드하고 커넥션 객체를 생성하여 연결하고 종료하기 때문에 매우 비효율적이다.

- 이런 문제를 해결하기 위해서 커넥션풀(DBCP)를 사용한다.


3. 커넥션풀(DBCP)의 특징

 - 풀 속에 미리 커넥션이 생성되어 있기 때문에 커넥션을 생성하는 데 드는 연결 시간이 소비되지 않는다.

 - 커넥션을 계속해서 재사용하기 때문에 생성되는 커넥션 수가 많지 않다.

--> 커넥션 풀을 사용하면 커넥션을 생성하고 닫는 시간이 소모되지 않기 때문에 그만큼 어플리케이션의 실행 속도가 빨라지며, 또한 한 번에 생성될 수 있는 커넥션 수를 제어하기 때문에 동시 접속자 수가 몰려도 웹 어플리케이션이 쉽게 다운되지 않는다.


4. 그렇다면 동시 접속자 처리는..?

커넥션 풀에서 생성되어 있는 커넥션의 갯수는 한정적이다. 그렇다면 동시 접속자가 많아지면 어떻게 될까?

커넥션 풀은 누군자 접속하면 커넥션 풀에 남아 있는 커넥션을 제공하는 식이다. 하지만 남아있는 커넥션이 없을 경우 해당 클라이언트는 대기 상태로 전환이 되고, 커넥션이 반환되면 대기하고 있는 순서대로 커넥션이 제공된다.


5. 환경설정

(1) DBCP 라이브러리 추가

- 이전버전 commons-dbcp-1.4jar, commons-pool-1.6.jar, commons-collections-3.2.1-bin.zip 3개의 라이브러리는 톰캣 6.0 부터 tomcat-dbcp.jar 파일로 하나로 통합되었다.

- 톰캣설치폴더 lib 폴더에 있는 tomcat-dbcp.jar 파일을 웹프로젝트\WebContent\WEB-INF\lib 경로에 복사한다.



(2) DB 라이브러리 추가(오라클 - ojdbc.jar)



(3) \Servers\Tomcat v6.0 Server at localhost-config\context.xml 파일에 Resource 태그 추가


※ 커넥션 풀의 속성

  속성

 설명

 maxActive 

 커넥션 풀이 제공할 최대 커넥션 갯수 

 whenExhaustedAction

 커넥션 풀에서 가져올 수 있는 커넥션이 없을 때 어떻게 동작할지를 지정.

0일 경우 에러 발생

1일 경우 maxWait 속성에서 지정한 시간만큼 커넥션을 구할때까지 기다림.

2일 경우 일시적으로 커넥션을 생성해서 사용

 maxWait

whenExhaustedAction 속성의 값이 1일 때 사용되는 대기 시간.

단위는 1/1000초, 0보다 작을 경우 무한히 대기

 maxIdle 

 사용되지 않고 풀에 저장될 수 있는 최대 커넥션 갯수.

음수일 경우 제한이 없음

 minIdle

사용되지 않고 풀에 저장될 수 있는 최소 커넥션 갯수. 

 testOnBorrow 

 true일 경우 커넥션 풀에서 커넥션을 가져올 때 커넥션이 유효한지의 여부를 검사 

 testOnReturn 

 true일 경우 커넥션 풀에 커넥션을 반환할 때 커넥션이 유효한지의 여부를 검사 

 timeBetweenEvctionRunsMillis 

사용되지 않는 커넥션을 추출하는 쓰레드의 실행 주기 지정.

양수가 아닐 경우 실행되지 않는다.

시간 단위는 1/1000초. 

 numTestsPerEvictionRun 

사용되지 않는 커넥션을 몇 개 검사할 지 지정 

 minEvictableIdleTimeMillis 

사용되지 않는 커넥션을 추출할 때 이 속석에서 지정한 시간 이상 비활성화 상태인 커넥션만 추출.

양수가 아닌 경우 비활성화된 시간으로는 풀에서 제거되지 않음.

시간 단위는 1/1000초 

 testWhileIdle

 true일 경우 비활성화 커넥션을 추출할 때 커넥션이 유효한지의 여부를 검사해서 유효하지 않은 커넥션은 풀에서 제거.



(4) 웹프로젝트\WebContent\WEB-INF\web.xml 파일에 <resource-ref> 태그 추가

- <res-ref-name> 태그의 이름은 contex.xml 파일 <resource>태그의 name 속성의 이름과 같아야한다.

- (4) 작업은 톰캣 6.0 이상부터는 생략 가능하다.



(5) JAVA 코드로 연결

- 서블릿에서 작성한다. (MVC 구조로 구현할 경우)

- 예외를 가지고 있으므로 예외처리를 해준다.

import java.sql.Connection;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.sql.DataSource;

 

public class DBCP {

    /*

     * 이 코드를 아래와 같이 줄여서 작성가능하다.

    Context initContext = new InitialContext();

    Context envContext  = (Context) initContext.lookup("java:/comp/env");

    // ("java:comp/env"): JNDI 서비스에 접근하기 위한 기본 이름(이 자원을 찾겠다.--> web.xml의 <res-ref-name>

    DataSource dataSource = (DataSource) envContext.lookup("jdbc/oracle");

    Connection conn = dataSource.getConnection();

    */

     

    Context context = new InitialContext();

    DataSource dataSource = (DataSource) context.lookup("java:comp/env/jdbc/oracle");

    Connection con = dataSource.getConnection();        

}



(6) servers > context.xml 파일 복사

- 서버가 변경되거나 서버에서 프로젝트를 제거하는 경우에 다시 추가해서 실행하는 번거로움이 있다.

- \Servers\Tomcat v6.0 Server at localhost-config\context.xml 파일을 복사한다.

- 웹프로젝트\WebContent\WEB-INF\ 디렉터리에 붙여넣기 한다.


6. JNDI(Java Naming and Directory Interface) 

설정된 정보를 이름으로 획득하려면 자바의 네이밍 API를 사용해야 한다. 네이밍 패키지의 클래스를 가지고 이름으로 객체를 획득하는 것을 JNDI(Java Naming and Directory Interface)라고 한다.

- 서비스가 다른 서비스를 탐색할 때 유용하게 사용된다. (분산된 자원)

- 분산된 자원 끼리의 탐색을 원할하게 하기 위한 type casting 임. (DNS 도 이에 속함) 

- JNDI에 설정해 놓았다는것은 Context.xml에 리소스를 생성해놓은 것을 말한다.

cf.) 

이름을 이용해서 원하는 정보 혹은 자원(Connection)을 찾을 수 있는 서비스

javax.naming 서비스

initialContext 클래스


(1) META-INF/context.xml

리소스 등록하는 설정파일

<Context> 

<!-- Resource를 등록하여 웹에서 JNDI로 호출할 이름과 정보를 설정한다. -->

   <Resource name="jdbc/myconn" auth="Container" type="javax.sql.DataSource"

   factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"

   driverClassName="org.gjt.mm.mysql.Driver"

   url="jdbc:mysql://localhost:3306/web_java?autoReconnect=true"

   username="root" password="12345678" 

   maxActive="100" maxIdle="30" maxWait="10000"

   removeAbandoned="true" removeAbandonedTimeout="60"/> 

   <!-- 

1. name : JNDI로 호출될 이름을 설정한다. (접근 -> java:comp/env/jdbc/myconn)

   2. auth : DBCP를 관리할 관리자 (Container or Application)

   3. type : 해당 resource의 return type 

      (DataSource는 Connection 객체를 반환할 수 있다.)

   4. factory : dbcp를 유용하는 관리 클래스 (Tomcat 5.x에 기본으로 존재하는 클래스)

      (직접 DBCP 클래스를 지정해도 동작하는데 문제가 없다.)

      (그러나, Factory 클래스를 이용하면 좀더 안정적으로 관리할 수 있다.)

   5. driverClassName : JDBC를 이용하기 위한 드라이버 클래스

   6. url : DB의 접속 URL (속성으로 자동 재 접속을 선택했다.)

   7. username : DB의 계정 명

   8. password : 계정에 대한 비밀번호

   9. maxActive : 최대 접속 허용 개수

   10. maxIdle : DB Pool에 여분으로 남겨질 최대 Connection 개수

   11. maxWait : DB 연결이 반환되는 Timeout의 최대 시간 (-1은 무한 대기)

   12. removeAbandoned : Connection이 잘못 관리되어 버려진 연결을 찾아 재활용할 것인지의 여부 설정

       (true 설정일 때 현재 DB 연결이 적으면 버려진 연결을 찾아 재활용)

   13. removeAbandonedTimeout : 버려진 연결로 인식할 기본 시간 설정

       (초 단위로 해당 시간이 지나면 버려진 연결로 인식한다.)

   -->   

</Context>


(2) WEB-INF/web.xml
<servlet-mapping>
<servlet-name>My_06</servlet-name>
<url-pattern>/Servlet_06</url-pattern>
</servlet-mapping>
<resource-ref>
<description>My SQL Resource</description><!-- 리소스 설명 -->
<res-ref-name>jdbc/myconn</res-ref-name><!-- 리소스 이름(JNDI명) -->
<res-type>javax.sql.DataSource</res-type><!-- 리턴 Type -->
<res-auth>Container</res-auth><!-- 관리 계층 -->
</resource-ref>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>


(3) 자바코드

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import java.sql.*;

import javax.sql.*;//DataSource 클래스를 위해 사용

import javax.naming.*;//JNDI를 위해 사용


public class Round16_06_Servlet extends HttpServlet {

public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

request.setCharacterEncoding("euc-kr");

String subject = request.getParameter("subject");

String author = request.getParameter("author");

String contents = request.getParameter("contents");

response.setContentType("text/html;charset=euc-kr");

PrintWriter out = response.getWriter();

out.println("<html><body><center><h3>");

Connection conn = null;

PreparedStatement pstmt = null;

String query = "insert into Round16_Table_01 values (null, ?, ?, ?)";

try {

Context context = new InitialContext();

//JNDI를 이용하기 위한 객체 생성

DataSource source = (DataSource)context.lookup("java:comp/env/jdbc/myconn");

  // lookup(): 등록된 naming 서비스로부터 자원을 찾고자할 때 사용하는 메서드

//context 객체를 통해 이름으로 Resource를 획득한다. 

  //("jdbc/myconn"): JNDI 서비스에 접근하기 위한 기본 이름(이 자원을 찾겠다.--> web.xml의 <res-ref-name>

//JNDI의 모든 이름은 기본적으로 java:comp/env에 등록되어 있다.

//해당 영역에서 jdbc/myconn으로 설정된 이름을 획득한다.

conn = source.getConnection();

//source로 부터 Connection 객체를 획득한다. 

//이 객체는 이제 Container의 DBCP에 의해 관리된다. 

}catch(Exception e) {}

try {

pstmt = conn.prepareStatement(query);

pstmt.setString(1, subject);

pstmt.setString(2, author);

pstmt.setString(3, contents);

int res = pstmt.executeUpdate();

if(res > 0)

out.println("Success Save!!");

pstmt.close();

conn.close();

}catch(Exception e) {

out.println("SQL Process Error : " + e.getMessage());

}

out.println("</h3></center></body></html>");

}

}



'Programing > JSP/Servlet' 카테고리의 다른 글

[JSP] HTTP 헤더  (0) 2014.12.25
[Servlet] 서블릿 이벤트  (0) 2014.12.18
[Servlet] 서블릿 필터  (0) 2014.12.10
[Servlet] 서블릿 기초  (0) 2014.12.10
[Servlet] 데이터 저장 영역  (0) 2014.12.10

[Spring] ViewResolver 설정


ViewResolver 설정


뷰 영역 구현

컨트롤러는 최종적으로 결과를 출력할 뷰와 뷰에 전달할 객체를 담고 있는 ModelAndView 객체를 리턴한다.

DispatherServlet은 ViewResolver를 사용하여 결과를 출력할 View 객체를 구하고, 구한 View 객체를 이용하여 내용을 생성한다.


1. 컨트롤러 구현 및 설정 추가

컨트롤러를 구현하려면 먼저 @Contoller 어노테이션을 클래스에 적용한다. 그리고, @RequestMapping 어노테이션을 이용해서 클라이언트의 요청을 처리할 메서드를 지정한다.

package madvirus.spring.chap06.controller;

import java.util.Calendar;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.servlet.ModelAndView;


// @Controller 어노테이션은 해당 클래스가 스프링 MVC의 컨트롤러를 구현한 클래스라는 것을 지정한다. 

@Controller

public class HelloController {


// @RequestMapping 어노테이션은 값으로 지정한 요청 경로를 처리한 메서드를 설정한다. 

// 이 경우 http://host:port[/컨텍스트 경로]/hello.do 요청을 HelloController 클래스의 hello() 메서드가 처리하게 된다.

@RequestMapping("/hello.do")

public ModelAndView hello() {

//ModelAndView는 컨트롤러의 처리 결과를 보여줄 뷰와 뷰에서 출력할 모델을 지정할 때 사용된다.

ModelAndView mav = new ModelAndView();

// 컨트롤러의 처리 결과를 보여줄 뷰의 이름을 'hello'를 지정한다.

mav.setViewName("hello");

// 모델에 'greeting'이라는 이름으로 String 타입의 값을 추가하였다.

mav.addObject("greeting", getGreeting());

return mav;

}

// 스프링은 ModelAndView 뿐만 아니라 String이나 modelMap, 또는 Map과 같은 타입을 이용해서 뷰 이름과 모델 정보를 설정할 수 있도록 하고 있다.

private String getGreeting() {

int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);

if (hour >= 6 && hour <= 10) {

return "좋은 아침입니다.";

} else if (hour >= 12 && hour <= 15) {

return "점심 식사는 하셨나요?";

} else if (hour >= 18 && hour <= 22) {

return "좋은 밤 되세요";

}

return "안녕하세요";

}

}


DispatherServlet은 스프링 컨테이너에서 컨트롤러 객체를 검색하기 때문에 스프링 설정 파일에 컨트롤러를 빈으로 등록해 주어야 한다.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

      http://www.springframework.org/schema/context

        http://www.springframework.org/schema/context/spring-context-3.0.xsd">


<bean id="helloController" class="madvirus.spring.chap06.controller.HelloController" />

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/view/" />

<property name="suffix" value=".jsp" />

</bean>

</beans>

스프링 2.5 버전까지는 @Controller 어노테이션이 적용된 클래스를 컨트롤러로 사용하려면 별도의 DefaultAnnoationHandlerMapping과 AnnotationHandlerAdapter를 스프링 설정 파일에 등록해 주어야 한다. 하지만 스프링 3.0부터는 이 두 빈 객체를 기본 구현체로 사용하기 때문에 별도의 설정을 하지 않을 경우 @Controller 어노테이션이 적용된 클래스를 컨트롤러 구현체로 사용할 수 있게 된다.


2. 설정 파일에 ViewResolver 설정 추가

컨트롤러 클래스는 직접 또는 간접적으로 ModelAndView 객체를 생성하게 된다.

예를 들어 앞서 작성한 HelloController 클래스는 다음과 같이 ModelAndView 객체를 생성해서 리턴하였다.


public ModelAndView hello() {

ModelAndView mav = new ModelAndView();

// 뷰이름

mav.setViewName("hello");// setViewName() 메서드를 이용하여 뷰 이름을 지정한 모습

mav.addObject("greeting", getGreeting());

return mav;

}

컨트롤러의 처리 결과를 보여줄 뷰의 이름을 'hello'를 지정하였는데, DispatherServlet은 이 뷰 이름과 매칭되는 뷰 구현체를 찾기 위해 ViewResolver를 사용한다.


JSP를 뷰 기술로 사용할 경우 다음과 같이 InternalResourceViewResolver 구현체를 빈으로 등록해주면 된다.

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix" value="WEB-INF/view"/>

<property name="suffix" value=".jsp/>

// 이는 ViewResolver가 "WEB-INF/view/뷰이름.jsp"를 뷰 JSP로 사용한다는 것을 의미한다. 

// 즉, 앞의 예에서 HelloController는 뷰 이름으로 "hello"를 리턴하므로, 실제로 사용되는 뷰 파일은 "WEB-INF/view/hello.jsp"파일이 된다.

</bean>


InternalResourceViewResolver는 컨트롤러가 지정한 뷰 이름으로부터 실제로 사용될 뷰를 선택하는데, 이 때 컨트롤러가 지정한 뷰 이름 앞뒤로 prefix 프로퍼티와 suffix 프로퍼티를 추가한 값이 실제로 사용될 자원의 경로가 된다.


※ ViewResolver 구현 클래스

스프링 컨트롤러는 뷰에 의존적이지 않다. 컨트롤러는 아래 코드와 같이 결과를 생성할 뷰의 이름만 지정할 뿐이다.

컨트롤러가 지정한 뷰 이름으로부터 응답 결과 화면을 생성하는 View 객체는 ViewResolver가 구한다.

스프링은 몇 가지 ViewResolver 구현 클래스를 제공하고 있는데, 이중 주료 ViewResolver 구현 클래스는 다음과 같다.


3. ViewResolver 인터페이스


package org.springframework.web.servlet;


import java.util.Locale;

public interface ViewResolver{

View resolveViewName(String viewName, Locale locale) throws Exception;

}

ViewResolver는 뷰 이름과 지역화를 위한 Locale을 파라미터로 전달받으며, 매핑되는 View 객체를 리턴한다. 만약, 매핑되는 View 객체가 존재하지 않으면 null을 리턴한다.


4. View 객체

ViewResolver는 응답 결과를 생성할 뷰 객체를 리턴한다. 모든 뷰 클래스는 View 인터페이스를 구현하고 있으며, View 인터페이스는 다음과 같이 정의되어 있다.


public interface View {

    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";

    String getContentType();

    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

}


getContentType() 메서드는 "text/html"과 같은 응답 결과의 컨텐츠 타입을 리턴한다. render() 메서드는 실제로 응답 결과를 생성한다. render() 메서드의 첫 번째 파라미터인 model에는

컨트롤러가 리턴한 ModelAndView 객체의 모델 데이터가 전달된다. 각각의 View 객체는 이 모델 데이터로부터 응답 결과를 생성하는데 필요한 정보를 구한다.


'Programing > Spring' 카테고리의 다른 글

[Spring] AOP 개요  (0) 2014.12.22
[Spring] Locale 처리  (0) 2014.12.21
[Spring] @RequestBody 어노테이션과 @ReponseBody 어노테이션의 사용  (1) 2014.12.18
[Spring] 파일업로드 처리  (2) 2014.12.18
[Spring] 요청 URI 매칭  (0) 2014.12.18

[Spring] @RequestBody 어노테이션과 @ReponseBody 어노테이션의 사용


@RequestBody 어노테이션과 @ReponseBody 어노테이션의 사용


웹 서비스와 REST 방식이 시스템을 구성하는 주요 요소로 자리 잡으면서 웹 시스템간에 XML이나 JSON 등의 형식으로 데이터를 주고 받는 경우가 증가하고 있다.

이에 따라 스프링 MVC도 클라이언트에서 전송한 XML 데이터나 JSON 또는 기타 데이터를 컨트롤러에서 DOM 객체나 자바 객체로 변환해서 받을 수있는 기능(수신)을 제공하고 있으며,

비슷하게 자바 객체를 XML이나 JSON 또는 기타 형식으로 변환해서 전송할 수 있는 기능(송신)을 제공하고 있다.


@RequestBody 어노테이션과 @ResponseBody 어노테이션은 각각 HTTP 요청 몸체를 자바 객체로 변환하고 자바 객체를 HTTP 응답 몸체로 변환하는 데 사용된다.

@RequestBody 어노테이션을 이용하면 HTTP 요청 몸체를 자바 객체로 전달받을 수 있다. 비슷하게 @ResponseBody 어노테이션을 이용하면 자바 객체를 HTTP 응답 몸체로 전송할 수 있다.


import org.springframework.stereotype.Controller;

import org.springframework.util.MultiValueMap;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.ResponseBody;


@Controller

public class SimpleConverterController {


@RequestMapping(value = "/test/simpleTest.do", method = RequestMethod.GET)

public String simpleTestForm() {

return "test/simpleTestForm";

}


@RequestMapping(method = RequestMethod.POST)

// @RequestBody 어노테이션은 @RequestMapping에 의해 POST 방식으로 전송된 HTTP 요청 데이터를 String 타입의 body 파라미터로 전달된다.(수신)

// 그리고 @ResponseBody 어노테이션이 @RequestMapping 메서드에서 적용되면 해당 메서드의 리턴 값을 HTTP 응답 데이터로 사용한다.

// simpleTest() 메서드의 리턴 값이 String 타입이므로 String 데이터를 HTTP 응답 데이터로 전송한다.(송신)

@ResponseBody

public String simpleTest(@RequestBody String body) {

return body;

}

}


SimpleConverterController에 GET 방식으로 요청이 전달되면 simpleTestForm 뷰가 사용되는데, 이 뷰가 아래와 같은 폼을 생성한다고 해보자

<form method="POST">

이름: <input type="text" name="name" /> <br/>

나이: <input type="text" name="age" />

<input type="submit" />

</form>


이때 HTTP 몸체로 전송되는 데이터는 다음과 같은 형식을 취한다.

name=%C3%D6%B9%FC%B1%D5%$age=34


SimpleConverterController 컨트롤러는 POST 방식으로 요청이 들어오는 경우 @RequestBody 어노테이션이 적용되어 있으므로 그대로 결과 값으로 리턴한다.

그런데, @ResponseBody 어노테이션이 적용되어 있으므로 결과적으로 HTTP 요청 몸체 데이터가 HTTP 응답 몸체로 전송된다.


스프링 MVC는 HttpMessageConverter를 이용해서 자바 객체와 HTTP 요청/응답 몸체 사이의 변환을 처리하는데, @ResponseBody 어노테이션 적용 메서드의 리턴 타입이 String인 경우

HTTP 응답 데이터의 컨텐츠 타입은 "text/plain;charset=ISO-8859-1"이 된다.


1. HttpMessageConverter를 이용한 변환 처리

AnnotationMethodHandlerAdapter 클래스는 @RequestBody 어노테이션이 적용된 파라미터나 @ResponseBody 어노테이션이 적용된 메서드에 대해 HttpMessageConverter를 사용해서 변환을 처리한다. 주요 HttpMessageConverter 구현 클래스는 다음과 같다.


 구현 클래스

 설 명

 ByteArrayHttpMessageConverter(*)

 HTTP 메시지와 byte 배열 사이의 변환을 처리한다. 컨텐츠 타입은  application/octet-stream이다.

 StringHttpMessageConverter(*)

 HTTP 메시지와 String 사이의 변환을 처리한다. 컨텐츠 타입은  text/plain;charset=ISO-8859-1이다.

 FormHttpMessageConverter(*)

 HTML 폼 데이터를 MultiValueMap으로 전달받을 때 사용된다. 지원하는 컨텐  츠 타입은 application-x-www-form-urlencorded이다.

 SourceHttpMessageConverter(*)

 HTTP 메시지와 javax.xml.transform.Source 사이 변환을 처리한다. 컨텐츠 타  입은 application/xml 또는 text/xml이다.

 MarshallingHttpMessageConverter(*)

 스프링의 Marshaller와 unMarshaller를 이용해서 XML HTTP 메시지와 객체 사  이의 변환을 처리한다. 컨텐츠 타입은 application/xml 또는 text/xml이다.

 MappingJacksonHttpMessageConverter(*)

 Jackson 라이브러리를 이용해서 JSON HTTP 메시지와 객체 사이의 변환을 처  리한다. 컨텐츠 타입은 applicaion/json이다.


만약 기본 구현 클래스가 아닌 다른 구현 클래스를 사용하려면 다음과 같이 AnnotationMethodHandlerAdapter에 명시적으로 구현 클래스 목록을 지정해 주어야 한다.

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">

<property name="cacheSeconds" value="0" />

<property name="alwaysUseFullPath" value="true" />

<property name="webBindingInitializer">

<bean class="madvirus.spring.chap06.binder.CustomWebBindingInitializer" />

</property>

<property name="messageConverters">

<list>

<ref bean="byteArrayHttpMessageConverter" />

<ref bean="marshallingHttpMessageConverter" />

<!--<ref bean="stringHttpMessageConverter" />-->

<!--<ref bean="formHttpMessageConverter" />-->

<!--<ref bean="sourceHttpMessageConverter" />-->

<!--<ref bean="jsonHttpMessageConverter" />-->

</list>

</property>

</bean>


<bean id="byteArrayHttpMessageConverter" class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />

<bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">

<property name="marshaller" ref="jaxb2Marshaller" />

<property name="unmarshaller" ref="jaxb2Marshaller" />

</bean>


예를 들어, 다음과 같이 JAXB 2의 어노테이션이 적용된 자바 객체가 있다고 하자

public class WriteArticleServiceImpl{@XmlAccessorType(XmlAccessType.FIELD)

@XmlRootElement(name="message-list")

public class GuestMessageList{

@XmlElement(name="message")

private List<GuestMessage> message;

...

}


@XmlAccessorType(XmlAccessType.FIELD)

@XmlType(name="", propOrder={"id", "message", "creationTime"})

public class GuestMessage{

private Integer id;

private String message;

private Date creationTime;

}


컨트롤러에서는 아래 코드와 같이 @ResponseBody 어노테이션을 이용해서 GuestMessageList를 리턴하도록 구현할 수 있을 것이다.

@Controller

public class GuestMessageController {


@RequestMapping(value = "/guestmessage/xml.do", method = RequestMethod.GET)

@ResponseBody

public GuestMessageList listXml() {

GuestMessageList list = ...;

return list;


}

...

}


JAXB2를 이용해서 자바 객체를 XML 응답으로 전송하려면 MarshallingHttpMessageConverter의 marshaller와 unmarshaller로 스프링 OXM 모듈의 Jaxb2Marshaller를 사용하면 된다.

<bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">

<property name="marshaller" ref="jaxb2Marshaller" />

<property name="unmarshaller" ref="jaxb2Marshaller" />

</bean>


<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">

<property name="classesToBeBound">

<list>

<value>madvirus.spring.chap06.model.GuestMessageList</value>

</list>

</property>

</bean>

이제 스프링 MVC는 MarshallingHttpMessageConverter를 이용해서 GuestMessageList 객체를 XML로 변환할 수 있게 된다.


2. Content-Type과 Accept 헤더 기반의 변환 처리

AnnotationMethodHandlerAdapter가 HttpMessageConverter를 이용해서 요청 몸체 데이터를 @RequestBody 어노테이션이 적용된 자바 객체로 변환할 때에는 HTTP 요청 헤더의 Content-Type 헤더에 명시된 미디어 타입(MIME)을 지원하는 HttpMessageConverter 구현체를 선택한다.

예를 들어, 요청 미디어 타입이 application/xml 이고 @RequestBody 어노테이션이 적용된 파라미터가 Source 타입인 경우 SourceHttpMessageConverter가 선택된다.

비슷하게 @ResponseBody 어노테이션을 이용해서 리턴한 객체를 HTTP 응답 객체로 변환할 때에는 HTTP 요청 헤더의 Accept 헤더에 명시된 미디어 타입을 지원하는 HttpMessageConveter 구현체를 선택한다.

예를 들어, Accept 헤더에 명시된 값이 application/json이고 @ResponseBody 어노테이션이 적용된 메서드의 리턴 타입이 자바 객체인 경우

MappingJacksonHttpMessageConverter가 선택된다. 예를 들어, 자바스크립트레서 아래와 같은 코드를 이용해서 Accept 헤더의 값으로 "application/json"을 지정했다고 하자.


<script type="text/javascript">

...

xmlhttp.open("GET","json.do",true);

xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

xmlhttp.setRequestHeader("Accept", "application/json");

xmlhttp.send(null);

...

</script>


이 경우 다음과 같이 @ResponseBody 어노테이션이 적용된 메서드의 리턴 객체를 변환할 때 MappingJacksonHttpMessageConverter를 사용하게 된다.

@RequestMapping(value = "/guestmessage/json.do", method = RequestMethod.GET, headers = "accept=application/json")

@ResponseBody

public GuestMessageList listJson() {

...

}


3. MappingJacksonJsonView를 이용한 JSON 응답 생성

스프링3 버전은 자바 객체를 JSON으로 변환해서 보여주는 뷰 구현 클래스인 MappingJacksonView를 제공하고 있다. 


아래 코드는 MappingJacksonJsonView의 설정 예를 보여주고 있다.

<bean id="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver" />


<bean id="pageJsonReport" class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />


컨트롤러에서는 다음과 같이 뷰 이름으로 MappingJacksonJsonView 타입의 빈을 설정해주면 된다.

@RequestMapping("/pageReport")

public ModelAndView pdfReport() {

List<PageRank> pageRanks = new ArrayList<PageRank>();

pageRanks.add(new PageRank(1, "/bbs/mir2/list"));

pageRanks.add(new PageRank(2, "/bbs/mir3/list"));

pageRanks.add(new PageRank(3, "/bbs/changchun2/list"));

return new ModelAndView("pageReport", "pageRanks", pageRanks);

}


MappingJacksonJsonView는 모델에 저장된 모든 객체를 JSON 형식으로 변환해준다.

{"report":{"pageRanks":["rank":1,"page"."/bbs/mir2/list"},{"rank":2,"page":"/bbs/mir3/list},{"rank":3,"page":"/bbs/changchun2/list"}]}}


예를 들어,  컨틀롤러에서 모델에 다음과 같이 값을 설정했다고 해보자

public String controllerMethod(Model model){

...

model.addAttribute("report", report);

model.addAttribute("summary", summary);

...

}


 컨트롤러에서 위와 같이 모델을 설정한 경우 MappingJacksonJsonView는 다음과 같은 형식의 JSON 응답 결과를 생성한다.

{"report":.... "summary":...}



'Programing > Spring' 카테고리의 다른 글

[Spring] Locale 처리  (0) 2014.12.21
[Spring] ViewResolver 설정  (0) 2014.12.18
[Spring] 파일업로드 처리  (2) 2014.12.18
[Spring] 요청 URI 매칭  (0) 2014.12.18
[Spring] 모델 생성하기  (1) 2014.12.18

[Spring] 파일업로드 처리


파일 업로드 처리


인코딩 타입이 Multipart인 경우 파라미터나 업로드한 파일을 구하려면 전송 데이터를 알맞게 처리해 주어야 한다.

스프링은 Multipart 지원 기능을 제공하고 있기 때문에, 이 기능을 이용하면 추가적인 처리없이 Multipart 형식으로 전송된 파라미터와 파일 정보를 쉽게 구할 수 있다.


1. MultipartResolver 설정

Multipart 지원 기능을 이용하려면 먼저 MultipartResolver를 스프링 설정 파일에 등록해 주어야 한다. MultipartResolver는 Multipart 형식으로 데이터가 전송된 경우, 해당 데이터를 스프링 MVC에서 사용할 수 있도록

변환해준다. 예를 들어, @PathVariable 어노테이션을 이용해서 Multipart로 전송된 파라미터와 파일을 사용할 수 있도록 해준다.


스프링이 기본으로 제공하는 MultipartResolver는 CommmnosMultipartResolver이다. CommmnsMultipartResolver는 Commons FileUpload API를 이용해서 Multipart를 처리해준다.

CommmonsMultipartResolver를 MultipartResolver로 사용하려면 다음과 같이 빈 이름으로 "multipartResolver"를 사용해서 등록하면 된다.


<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

cf.) DispatcherServlet은 이름이 "multipartResolver"인 빈을 사용하기 때문에 다른 이름을 지정할 경우 MultipartResolver로 사용되지 않는다.


※ CommonsMultipartResolver 클래스의 프로퍼티

 프로퍼티

 타입

설명 

 maxUploadSize

 long

 최대 업로드 가능한 바이트 크기, -1은 제한이 없음을 의미한다. 기본 값은 -1이다.

 maxInMemorysize

 int 

 디스크에 임시 파일을 생성하기 전에 메모리에 보관할 수 있는 최대 바이트 크기, 

 기본 값은 10240 바이트이다.

 defaultEncording

 String 

 요청을 파싱할 때 사용할 캐릭터 인코딩, 지정하지 않은 경  우 HttpServletRequest.setEncording() 메서드로 지정한 캐릭터 셋이 사용된다. 

 아무 값도 없을 경우 ISO-8859-1을 사용한다.


2. @RequestParam 어노테이션을 이용한 업로드 파일 접근

업로드한 파일을 전달받는 첫 번째 방법은 @RequestParam 어노테이션이 적용된 MultipartFile 타입의 파라미터를 사용하는 것이다. 예를 들어, HTML 입력폼이 다음과 같이 작성되어 있다고 해보자

<form action="submitReport1.do" method="post" enctype="multipart/form-data">

학번: <input type="text" name="studentNumber" />

<br/>

리포트파일: <input type="file" name="report" />

<br/>

<input type="submit" />

</form>

위 HTML 코드에서 파일은 report 파라미터를 통해서 전달된다. 


이 경우 다음 코드와 같이 @RequestParam 어노테이션과 MultipartFile 타입의 파라미터를 이용해서 업로드 파일 데이터를 전달받을 수 있다.

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.multipart.MultipartFile;


@Controller

public class ReportSubmissionController {


@RequestMapping(value = "/report/submitReport1.do", method = RequestMethod.POST)

public String submitReport1(

@RequestParam("studentNumber") String studentNumber,

// MultipartFile이 제공하는 메서드를 이용해서 업로드 데이터 접근

@RequestParam("report") MultipartFile report) {

printInfo(studentNumber, report);

return "report/submissionComplete";

}

...

}

MultipartFile 인터페이스는 스프링에서 업로드 한 파일을 표현할 때 사용되는 인터페이스로서, MultipartFile 인터페이스를 이용해서 업로드 한 파일의 이름, 실제 데이터, 파일 크기 등을 구할 수 있다.


3. MultipartHttpServletRequest를 이용한 업로드 파일 접근


업로드한 파일을 전달받는 두 번째 방법은 MultipartHttpServletRequest 인터페이스를 이용하는 것이다.

import org.springframework.web.multipart.MultipartFile;

import org.springframework.web.multipart.MultipartHttpServletRequest;


@Controller

public class ReportSubmissionController {

@RequestMapping(value = "/report/submitReport2.do", method = RequestMethod.POST)

public String submitReport2(MultipartHttpServletRequest request) {

String studentNumber = request.getParameter("studentNumber");

MultipartFile report = request.getFile("report");

printInfo(studentNumber, report);

return "report/submissionComplete";

}

}

MultopartHttpServletRequest 인터페이스는 스프링이 제공하는 인터페이스로서, Multipart 요청이 들어올 때 내부적으로 원본 HttpServletRequest 대신 사용되는 Multipart 요청이 들어올 때 

내부적으로 원본 HttpServletRequest 대신 사용되는 인터페이스이다. MultipartHttpServletRequest 인터페이스는 실제로는 어떤 메서드도 선언하고 있지 않으며,  HttpServletRequest 인터페이스와 MultipartRequest 인터페이스를 상속받고 있다. MultipartHttpServletRequest 인터페이스는 javax.servlet.HttpServletRequest 인터페이스를 상속받기 때문에 웹 요청 정보를 구하기 위한 getParameter()나 getHeader()와 같은 메서드를 사용할 수 있으며, 추가로 MultipartRequest 인터페이스가 제공하는 Multipart 관련 메서드를 사용할 수 있다.


※ MultipartRequest 인터페이스의 파일 관련 주요 메서드

 메서드

설 명 

 Iterator<String> getFileNames()

 업로드 된 파일들의 이름 목록을 제공하는 Iterator를 구한다.

 MultipartFile getfile(String name)

 파라미터 이름이 name이 업로드 파일 정보를 구한다.

 List<MultipartFile> getFiles(String name)

 파라미터 이름이 name인 업로드 파일 정보 목록을 구한다.

 Map<String, MultipartFile> getFileMap()

 파라미터 이름을 키로 파라미터에 해당하는 파일 정보를 값으로 하는 Map을 구한다.


4. 커맨드 객체를 통한 업로드 파일 접근


커맨드 객체를 이용해도 업로드 한 파일을 전달받을 수 있다. 단지 커맨드 클래스에 파라미터와 동일한 이름의 MultipartFile 타입 프로퍼티를 추가해주기만 하면 된다.

예를 들어, 업로드 파일의 파라미터 이름이 "report"인 경우, 다음과 같이 "report" 프로퍼티를 커맨드 클래스에 추가해 주면 된다.


import org.springframework.web.multipart.MultipartFile;


public class ReportCommand {


private String studentNumber;

private MultipartFile report;


public String getStudentNumber() {

return studentNumber;

}


public void setStudentNumber(String studentNumber) {

this.studentNumber = studentNumber;

}


public MultipartFile getReport() {

return report;

}


public void setReport(MultipartFile report) {

this.report = report;

}

}


위 코드와 같이 MultipartFile 타입의 프로퍼티를 커맨드 클래스에 추가해주었다면, @RequestMapping 메서드의 커맨드 객체로 사용함으로써 업로드 파일 정보를 커맨드 객체를 통해서 전달받을 수 있게 된다.

@Controller

public class ReportSubmissionController{

...

@RequestMapping(value="/report/submitReport3.do",medhod = RequestMethod.POST)

public String submitReport3(ReportCommand reportCommand){

...

}

}


5. MultupartFile 인터페이스 사용


org.springframework.web.MutipartFile 인터페이스는 업로드 한 파일 및 파일 데이터를 표현하기 위한 용도로 사용된다.

MultipartFile 인터페이스가 제공하는 주요 메서드는 다음과 같다.


※ MulripartFile 인터페이스의 주요 메서드

 메서드

 설 명

 String getName()

 파라미터 이름을 구한다.

 String getOriginalFilename()

 업로드 한 파일의 이름을 구한다.

 String isEmpty()

 업로드 한 파일이 존재하지 않는 경우 true를 리턴한다.

 long getSize()

 업로드 한 파일의 크기를 구한다.

 byte[ ] getBytes() throws IOExcetion

 업로드 한 파일 데이터를 구한다.

 InputStream getInputStream() throws  IOException

 업로드한 파일 데이터를 읽어노는 InputStream을 구한다. InputStream의 사용  이 끝나면 알맞게 종료해  주어야 한다.

 void transferTo(File dest) throws IOException

 업로드 한 파일 데이터를 지정한 파일에 저장한다.


업로드 한 파일 데이터를 구하는 가장 단순한 방법은 MultipartFile.getByte() 메서드를 이용하는 것이다. 바이트 배열을 구한 뒤에 파일이나 DB등에 저장하면된다.

if(mutipartFile.isEmpty()){

byte[ ] fileData = multipartFile.getBytes();

//byte 배열을 파일/DB/네트워크 등으로 전송

...

}


업로드 한 파일 데이터를 특정 파일로 저장하고 싶다면 MultipartFile.transferTo() 메서드를 사용하는 것이 편리하다.

if(mutipartFile.isEmpty()){

File file = new File(fileName);

multipartFile.transferTo(file);

...

}



[Spring] 요청 URI 매칭


요청 URI 매칭


클라이언트가 요청한 URL과 @RequestMapping 어노테이션의 값을 이용해서 클라이언트의 요청을 어떤 컨트롤러의 어떤 메서드가 처리할 지가 결정된다.

매칭이 어떻게 이루어지는 지 알지 못할 경우 클라이언트의 요청 URL과 매칭되는 메서드를 설정할 수 없기 때문에, 어떻게 매칭이 이루어지는 지 아는 것은 작지만 매우 중요하다.

본 절에서는 매칭과 관련된 기본 내용인 경로와 @RequestMapping 어노테이션 간의 관계 그리고 @PathVariable을 이용한 URI 템플릿에 대해서 살펴보자.


1. 전체 경로와 서블릿 기반 경로 매칭 설정

    <servlet>

        <servlet-name>dispatcher</servlet-name>

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <load-on-startup>1</load-on-startup>

    </servlet>

    

    <servlet-mapping>

        <servlet-name>dispatcher</servlet-name>

        <url-pattern>*.do</url-pattern>

        <url-pattern>/game/*</url-pattern>

    </servlet-mapping>

위 코드는 *.do로 오는 요청과 /game/으로 오는 요청을 dispatcher 서블릿이 처리하도록 설정하고 있다. 이 경우 다음의 두 @RequestMapping은 어떻게 할까?


@RequestMapping("/search/game.do")

public String search(...){

...

}

 /search/game.do 요청은 <url-pattern>이 ".do"인 확장자 기반의 매칭이 되므로 서블릿 경로가 ""가 된다. 따라서, /search/game.do"이므로 요청은 값이 "search/game.do"인 @RequestMapping 어노테이션과 매칭된다.


@RequestMapping("/game/info")

public String info(...){

...

}

info() 메서드는 /game/info요청을 처리하지 않는다. 서블릿 매핑 설정에서 <url-pattern> 의 값을 "/game/*" 으로 설정했기 때문이다.

<url-pattern>의 값으로 디렉터리를 포함한 패턴을 지정하게 되면 서블릿 경로는 "/game"이 되며, 서블릿 경로를 제외한 나머지  경로를 이용해서 @RequestMapping 어노테이션의 값과 매칭 여부를 판단하게 되므로,

실제 비교할 때 사용되는 요청 URI는 "/info"가 된다. @RequestMapping 어노테이션의 값은 "/game/info" 이므로 비교할 요청 URI인 "info"와는 매칭되지 않는다.


코드만 보면 /search/game.do에 대한 요청은 search()메서드가 처리하고 /game/info에 대한 요청은 info() 메서드가 처리한다고 생각하기 쉽다.

하지만 search() 메서드만 /search/game.do에 대한 요청을 처리하고, info() 메서드는 /game/info요청을 처리하지 않는다.


서블릿 경로를 제외한 나머지 경로를 사용하는 이유는 DispatcherServlet이 기본적으로 사용하는 HandlerMapping 구현체(DefalutAnnotationHandlerMapping)와 HandlerAdapter 구현체(AnnotationMethodAdapter)

가 전체 경로를 사용하지 않도록 설정되어 있기 때문이다. 

DispatherServlet은 DispatherServlet이 사용하는 스프링 설정 파일에 HandlerMapping 구현 클래스와 HandlerAdapter 구현 클래스가 빈으로 등록되어 있으면, 기본 구현체를 사용하지 않고 설정 파일에 등록된 구현체를 사용하게 된다.(기본구현체: DefaultAnnotationServlet 과 AnnotationMethodHandlerAdapter)


2. @PathVariable 어노테이션을 이용한 URI 템플릿


RESTful 서비스가 유행하면서 URI를 다음과 같이 REST 방식으로 구성하는 경우가 늘어나고 있다.

- http://somehost/users/madvius

- http://somehost/games

- http://somehost/forun/board1/10

기본에는 http://somehost/users/userinfo?id=madvius와 같이 파라미터를 이용해서 아이디나 이름을 전달받았다면, 이제는 URI에 아이디나 이름 등이 포함되도록 URL을 구성하고 있다.

URI 템플릿을 이용하면 REST 방식의 URL 매칭을 쉽게 처리할 수 있다. URI 템플릿을 사용하는 방법은 매우 간단하며, 다음과 같이 두 가지만 추가로 작업해주면 된다.


- @RequestMapping: 어노테이션의 값으로(템플릿 변수)

- @PathVariable: 어노테이션을 이용해서{템플릿 변수}와 동일한 이름을 갖는 파라미터를 추가한다.


import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;


@Controller

public class CharacterInfoController {

@RequestMapping("/game/users/{userId}/chracters/{chracterId}")

public String characterInfo(@PathVariable String userId/*madvirus*/, @PathVariable int characterId /*1*/, ModelMap model) {

model.addAttribute("userId", userId);

model.addAttribute("characterId", characterId);

return "game/character/info";

}

}

위 코드를 보면 @RequestMapping 어노테이션은 이름이 uesrId와 characterId인 변수를 포함하고 있다. 이들 변수는 @PathVariable 어노테이션이 적용된 동일한 이름을 갖는 파라미터에 매칭된다. 따라서 요청 URI가 "/game/users/madivirus/character/1"이면, characterInfo() 메서드의 userId 파라미터와 chracterId 파라미터의 값은 각각 "madvirus"와 1이 된다.


만약 파라미터 이름과 URI 템플릿의 변수 이름이 동일하지 않다면, 다음과 같이 @PathVariable에 매칭될 템플릿 변수 이름을 지정해주면 된다.

@RequestMapping("/game/users/{userId}")

public String userInfo(@PathVariable("userId") String id, ModelMap model){ //userId != id

...

}


3. @RequestMapping 어노테이션의 추가 설정 방법

@RequestMapping 어노테이션을 클래스와 메서드에 함께 적용할 경우, 메서드에 적용한 @RequestMapping 어노테이션의 값은 클래스에 적용한 @RequestMapping 어노테이션의 값은 클래스에 적용한 @RequestMapping 어노테이션의 값을 기본 경로로 사용하게 된다.


@Controller

@RequestMapping("/game/users/{userId}")

public class CharacterInfoController {


@RequestMapping("/characters/{characterId}")

public String characterInfo(@PathVariable String userId,

@PathVariable int characterId, ModelMap model) {

model.addAttribute("userId", userId);

model.addAttribute("characterId", characterId);

return "game/character/info";

}

}

위 코드에서 characterInfo() 메서드에 적용된 @RequestMapping 어노테이션의 값은 "/chracters/{characterId}"인데, 실제로 매칭되는 값은 클래스에 적용된 @RequestMapping 어노테이션의 값을 포함한

"/game/users/{userId}/chracters/{chracterId}"가 된다.


cf.) @RequestMapping 어노테이션은 Ant 스타일의 패턴을 지원한다. 따라서, "*"나 "**"를 값으로 사용할 수 있다.

- ?: 1개의 문자와 매칭

- *: 0개 이상의 문자와 매칭

- **: 0개 이상의 디렉터리와 매칭



[Servlet] 서블릿 이벤트


서블릿 이벤트

서블릿은 다양한 시점에 발생되는 이벤트와 이벤트를 처리하기 위한 인터페이스를 정의하고 있다. 이들 이벤트와 인터페이스를 이용하면 웹 어플리케이션에서 필요로 하는 데이터의 초기화나 요청 처리 등을 추적할 수 있게 된다. 서블릿 규약은 다양한 이벤트를 처리할 수 있는 인터페이스를 정의하고 있는데, 이 장에서는 그 중에서 ServletContextListener이다. 데이터 저장 영역(application, session, request)에 데이터가 들어가고 나가는 혹은 그 객체가 생성되고 소멸되는 일련의 작업들에 대해 컨트롤한다.


1. 이벤트가 사용되는 경우

1) 컨텍스트가 초기화되는 경우

2) 세션이 생기거나 소멸되는 경우

3) 속성이 바뀌는 경우

--> 이런 이벤트에 대해서 미리 web.xml 파일에 등록해두면 웹 서버는 자동으로 이벤트를 감지하고 우리가 지정한 클래스 내의 메서드를 실행해준다. 이벤트는 외부의 변화보다는 내부의 변화를 처리하는 경우가 많다. 서블릿에서 이벤트 클래스를 실행되게 하려면 무엇보다 web.xml 파일의 설정이 중요하다. 초기에 웹 서버가 구동하면서 해당 이벤트를 대기 상태로 두기 때문이다. 


2. ServletContextListener를 이용한 이벤트 처리

웹 컨테이너는 웹 어플리케이션(컨텍스트)이 시작되거나 종료되는 시점에 특정 클래스의 메서드를 실행할 수 있는 기능을 제공하고 있다.

이 기능을 사용하면 웹 어플리케이션을 실행하는 데 필요한 초기화 작업이나 웹 애플리케이션이 종료된 후 사용된 자원을 반환하는 등의 작업을 수행할 수 있다.

웹 어플리케이션이 시작되고 종료될 때 특정한 기능을 실행하려면 다음과 같이 코드를 작성해야 한다.

1 javax.servlet.ServletContextListener 인터페이스를 구현한 클래스를 작성한다.

2 web.xml 파일에 1에서 작성한 클래스를 등록한다.


javax.servlet.ServletContextListener 인터페이스는 웹 어플리케이션이 시작되거나 종료될 때 호출되는 메서드를 정의한 인터페이스로서, 다음과 같은 두 개의 메서드가 정의되어 있다.

public void contextInitialized(SerlvetContextEvent sce) 웹 애플리케이션이 초기화될 때 호출된다.

public void contextDestoryed(ServletContextEvent sce) 웹 애플리케이션이 종료될 때 호출된다.


ServletContextListener 인터페이스를 구현한 클래스가 웹 애플리케이션이 시작되거나 종료될 때 실행되도록 하려면 web.xml <listener> 태그와 </listener-class> 태그를 사용해서 완전한 클래스 이름을 명시해주면 된다.
<web-app>
<listener>
<listener-class>mvcjsp.jdbc.loader.DBCPInitiListener</listener-class>
</listener>
<listener>
<!-- 웹 애플리케이션의 시작/종료 이벤트를 처리할 리스너 클래스의 완전한 이름을 값으로 갖는다. -->
<listener-class>mvcjsp.jdbc.chap24.CodeInitListener</listener-class>
</listener>
</web-app>

ServletContextListener 인터페이스에 정의된 두 메서드는 모두 파라미터로 javax.servlet.ServletContextEvent 타입의 객체를 전달 받는데, ServletContextEvent 클래스는 시작되거나 종료된 웹 애플리케이션 컨텍스트를 구할 수 있는 getServletContext() 메서드를 제공하고 있다.
public ServletContext getServletContext()

<web-app>
<context-param>
<param-name>jdbcdriver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</context-param>
</web-app>

cf.) getServletContext() 메서드가 리턴하는 ServletContext 객체는 JSP의 application 기본 객체와 동일한 객체로서, ServletContext 객체를 이용하면 web.xml 파일에 설정된 어플리케이션 초기화 파라미터를 
구할 수 있다. 어플리케이션 초기화 파라미터는 <context-param> 태그를 사용해서 설정한다.

(1) ServletContext가 제공하는 초기화 파라미터 관련 메서드
1) String getInitParameter(String name)
지정한 이름을 갖는 초기화 파라미터의 값을 리턴한다. 존재하지 않을 경우 null을 리턴한다. name 파라미터에는 <param-name> 태그로 지정한 이름을 입력한다.

2) java.util.Enumeration.getInitParameterNames()
web.xml 파일에 정의된 초기화 파라미터의 이름 목록을 Enumeration 타입으로 리턴한다.
--> 초기화 파라미터는 주로 웹 어플리케이션을 구동하는 데 필요한 초기화 작업을 수행하는데 필요한 값을 설저하는 데 사용한다.

※ 리스너의 실행 순서
<listener>
<listener-class>mvcjsp.jdbc.loader.AListener</listener-class>
</listener>
<listener>
<listener-class>mvcjsp.jdbc.loader.BListener</listener-class>
</listener>
web.xml에 한 개이상의 리스너가 등록되어 있는 경우, contextInitialized() 메서드는 등록된 순서대로 실행되고 contextDestoryed() 메서드는 등록된 반대 순서대로 실행된다. 즉, 위 코드의 경우 웹 애플리케이션이 시작될 때는 AListener가 먼저 실행되고 그 다음에 BListenter가 실행된다. 반대로 웹 어플리케이션이 종료될 때는 BListener가 실행되고 그 다음에 AListener가 실행된다.

3. web.xml에서 사용되는 태그

(1) <listener> 태그는 서블릿의 이벤트를 대기 상태로 둔다.

cp.) <listener> 태그를 가지고 설정할 수 있는 이벤트는 다음과 같다.

- ServletContextListener 이벤트 

ServletContext 객체가 초기화되거나 소멸될 때 발생하는 이벤트이다. contextInitialized(),contextDestoryed() 메서드가 있다.


- ServletContextattributeListener 이벤트

ServletContext 객체에 속성이 추가되거나 삭제되거나, 수정될 때 발생하는 이벤트이다. attributeAdded(), attributeRemoved(), attributereplaced() 메서드가 있다.


- HttpSessionListener 이벤트

HttpSession 객체가 생성되거나 소멸될 때 발생하는 이벤트이다. sessionCreated(), sessionDestroyed() 메서드가 있다.


- HttpSessionAttributeListener 이벤트

ServletRequest 객체가 초기화되거나 소멸될 때 발생하는 이벤트이다. requestInitialzed(), requestDestroyed() 메서드가 있다.


- ServletRequestAttributedListener 이벤트

ServletRequest 객체에 속성이 추가되거나 삭제되거나, 수정될 때 발생하는 이벤트이다. attributeAdded(), attributeRemoved(), attributeReplaced() 메서드가 있다.


(2) <context-param> 태그는 초기화 매개 변수가 된다.


(3) 예제 1

1) web.xml

<display-name>Round18</display-name>

<context-param>

<param-name>co_name</param-name>

<param-value>승현 주식회사</param-value>

</context-param>

<context-param>

<param-name>co_tel</param-name>

<param-value>02-1234-1234</param-value>

</context-param>

<context-param>

<param-name>admin_email</param-name>

<param-value>kimsh@sh.com</param-value>

</context-param>


2) 자바코드

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;


public class Round18_03_Servlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

ServletContext context = this.getServletContext();

String co_name = context.getInitParameter("co_name");

String co_tel = context.getInitParameter("co_tel");

String admin_email = context.getInitParameter("admin_email");

response.setContentType("text/html;charset=euc-kr");

PrintWriter out = response.getWriter();

out.println("<html><head>");

out.println("<style type='text/css'>");

out.println(".n_bo { border:none }");

out.println("</style>");

out.println("</head><body><center>");

out.println("회사상호 : ");

out.println("<input type='text' class='n_bo' value='" + co_name + "'/><br/>");

out.println("회사전번 : ");

out.println("<input type='text' class='n_bo' value='" + co_tel + "'/><br/>");

out.println("대표메일 : ");

out.println("<input type='text' class='n_bo' value='" + admin_email + "'/><br/>");

out.println("</center></body></html>");

}

}

화면 출력)

회사상호: 승현 주식회사

회사전번: 02-1234-1234

대표메일: kimsh@sh.com


이렇게 ServletContext 객체를 가지고 web.xml 파일에 등록된 초기화 매개변수들을 가져와서 사용할 수 있다. 그러나 ServletContext 객체와 관련된 이벤트에서는 이들 값의 추가, 삭제, 수정에 대한 정보를 확인할 수 없다. 이벤트가 관리하는 값들은 오직 해당 객체의 속성들뿐이며, 매개변수는 관리 대상에서 제외된다.


(4) 예제2

이제 ServletContext 객체와 관련된 이벤트를 처리해 보도록 하자. ServletContext 객체의 변화에 대해 반응하는 메서드들은 ServletContextListener와 ServletContextAttributeListener 인터페이스에 정의되어 있기 때문에 이를 구현해 주어야 한다.

1) web.xml : 태그 순서 주의!

<listener>

<display-name>Context Listener</display-name>

<listener-class>Round18_04_Servlet_Listener</listener-class>

</listener>

<context-param>

<param-name>co_name</param-name>

<param-value>승현 주식회사</param-value>

</context-param>


2) 자바코드

import javax.servlet.*;


public class Round18_04_Servlet_Listener implements ServletContextListener, ServletContextAttributeListener {


public void contextInitialized(ServletContextEvent e) {

  // 톰캣이 구동될 때 실행된다.

System.out.println("ServletContext 가 초기화 되었습니다.");

System.out.println("init context = " + e.getServletContext());

}

public void contextDestroyed(ServletContextEvent e) {

  // 톰캣이 종료될 때 실행된다.

System.out.println("ServletContext 가 소멸 되었습니다.");

System.out.println("dest context = " + e.getServletContext());

}

public void attributeAdded(ServletContextAttributeEvent e) {

        // ServletContext 객체에 속성이 새로 추가될 때 실행된다.

System.out.println("Context 영역에 값이 추가 되었습니다.");

System.out.println("added = " + e.getName() + " : " + e.getValue());

}

public void attributeRemoved(ServletContextAttributeEvent e) {

        // ServletContext 객체의 속성이 삭제될 때 실행된다.

System.out.println("Context 영역에 값이 삭제 되었습니다.");

System.out.println("removed = " + e.getName() + " : " + e.getValue());

}

public void attributeReplaced(ServletContextAttributeEvent e) {

        // ServletContext 객체의 속성이 수정될 때 수정 직전에 실행된다.

System.out.println("Context 영역에 값이 변경 되었습니다.");

System.out.println("replaced = " + e.getName() + " : " + e.getValue());

}

}


(5) 예제 3

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;


public class Round18_04_Servlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

response.setContentType("text/html;charset=euc-kr");

PrintWriter out = response.getWriter();

out.println("<html><body><center>");

out.println("<form method='post'>");

out.println("<input type='submit' value='Context 값 할당 하기'/>");

out.println("</form></center></body></html>");

}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

ServletContext context = this.getServletContext();

context.setAttribute("my_name", "김승현");

context.setAttribute("my_name", "승현");

context.removeAttribute("my_name");

response.setContentType("text/html;charset=euc-kr");

PrintWriter out = response.getWriter();

out.println("<html><body><center>");

out.println("Context 값 추가, 삭제, 변경 성공!");

out.println("</center></body></html>");

}

}

ServletContext 객체의 속성을 바꾸는 서블릿 코드이다.

웹서버가 구동시점에는 최초로 contextInitialzed() 메서드가 실행되어 ServletContext 객체가 초기화된다.

URL 패턴에 맞추어 서블릿을 실행하면  페이지가 열리고 아무런 이벤트도 발생하지 않는다. 여기에서 Context 값 할당 하기 단추를 누르면 doPost() 메서드가 실행되면서 ServletContextAttributeListener 이벤트가 발생하여 차례로 추가, 삭제, 수정 메시지를 확인할 수 있다.

이상과 같이 이벤트를 web.xml 파일에 등록하면 웹에서 특정 동작에 이벤트가 발생해서 해당 메서드를 실행하게 된다.


'Programing > JSP/Servlet' 카테고리의 다른 글

[JSP] HTTP 헤더  (0) 2014.12.25
[JSP] 커넥션 풀  (2) 2014.12.19
[Servlet] 서블릿 필터  (0) 2014.12.10
[Servlet] 서블릿 기초  (0) 2014.12.10
[Servlet] 데이터 저장 영역  (0) 2014.12.10

[Spring] 모델 생성하기


모델 생성하기


@RequestMapping 어노테이션이 적용된 메서드의 파라미터의 리턴 타입으로 ModelAndView, Model, ModelMap, Map, 커맨드 객체 등을 이용해서 모델을 뷰에 전달하게 된다.

본 절에서는 컨트롤러가 이들 클래스를 이용해서 모델을 어떻게 뷰에 전달하는 지 살펴보자.


1. 뷰에 전달되는 모델 데이터

@RequestMapping 메서드가 ModelAndView, Model, Map을 리턴하는 경우 이들에 담긴 모델 데이터가 뷰에 전달된다. 또한, 추가로 다음의 항목도 뷰에 함께 모델로 전달된다.

- 커맨드 객체

- @ModelAttribute 어노테이션이 적용된 메서드가 리턴한 객체

- 메서드의 Map, Model, ModelMap 타입의 파라미터를 통해 설정된 모델


import org.springframework.stereotype.Controller;

import org.springframework.ui,ModelMap;

import org.springframework.web.bind.annotation.ModelAttribute;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.servlet.ModelAndView;


@Controller

public class GameSearchController {

private SearchService searchService;

@ModelAttribute("searchTypeList")

  // 이 코드에서 @ModelAttribute 어노테이션이 적용된 referenceSearchTypeList() 메서드가 리턴한 모델 객체는 "searchTypeList"라는 이름으로 뷰에 전달된다.

public List<SearchType> referenceSearchTypeList() {

List<SearchType> options = new ArrayList<SearchType>();

options.add(new SearchType(1, "전체"));

options.add(new SearchType(2, "아이템"));

options.add(new SearchType(3, "캐릭터"));

return options;

}

@RequestMapping("/search/game.do")

public ModelAndView search(@ModelAttribute("command") SearchCommand command, ModelMap model) {

String[ ] queryList = getPopularQueryList();

model.addAttribute("popularQueryList", queryList);

ModelAndView mav = new ModelAndView("search/game");

SearchResult result = searchService.search(command);

mav.addObject("searchResult", result);

return mav;

}

  //@RequestMapping 어노테이션이 적용된 search() 메서드는 파라미터로 커맨드 객체와 ModelMap 객체를 전달받는다. search() 메서드 내부에서는 ModelMap 타입 파라미터인

// model을 이용해서 "popularQueryList"이름의 모델 객체를 뷰에 전달한다. 또한, 내부적으로 ModelAndView 타입의 mav 객체를 만든 뒤 mav를 이용해서 "searchResult"라는 이름의

// 모델 객체를 뷰에 전달한다.

}


이들 모델 객체는 서로 다른 방법으로 생성되었지만, DIspatcherServlet은 이들 모델 객체에 저장된 모델 데이터를 모두 뷰에 전달한다.

따라서 뷰 코드에서는 다음과 같이 이들 모델 객체를 모두 사용할 수 있게 된다.

<body>

인기 키워드: <c:forEach var="popularQuery" items="${popularQueryList}">${popularQuery} </c:forEach>

<form action="game.do">

<select name="type">

<c:forEach var="searchType" items="${searchTypeList}">

<option value="${searchType.code}" <c:if test="${command.type == searchType.code}">selected</c:if>>

${searchType.text}</option>

</c:forEach>

</select>

<input type="text" name="query" value="${command.query}"/>

<input type="submit" value="검색" />

</form>

검색 결과: ${searchResult}

</body>


2. Map, Model, ModelMap을 통한 모델 설정

Map, Model, ModelMap을 이용하면 뷰에 전달할 모델을 생성할 수 있다.


(1) 첫 번째 방법은 이들 세 가지 타입 중 한 가지를 파라미터로 전달받을 것이다.

@RequsetMapping("search1.do")

public String search1(Map model){

model.put("result", searchResult);

}


@RequsetMapping("search2.do")

public String search2(Model model){

model.addAttribute("result", searchResult);

}


@RequsetMapping("search3.do"){

public String search3(ModelMap model){

model.addAttribute("result", searchResult);

}


(2) 두 번째 방법은 Map과 Model을 리턴하는 것이다. 이 둘은 모두 인터페이스이기 때문에 실제로는 인터페이스를 구현한 클래스의 객체를 생성해서 리턴하면 된다. 

@RequestMapping("/search1.do")

public Map<String, Object> search1(){

...

HashMap<String, Object> model = new HashMap<String, Object>();

model.put("result", searchResult);

return model;

}


@RequestMapping("/search2.do")

public Model search2(){

...

Model model = new ExtendedModelMap();

model.addAttribute("result", searchResult);

return model;

}

스프링은 org.springframework.ui.Model 인터페이스의 구현 클래스인 ExtendedModelMap 클래스를 동일한 패키지에 제공하고 있으므로, ExtendedModelMap 클래스를 이용해서 모델을 설정하면 된다.

Map의 경우 많이 사용하는 java.util.HashMap 클래스를 이용해서 모델을 설정한다.


(3) Model 인터페이스의 주요 메서드

org.springframework.ui.Model 인터페이스는 모델을 설정할 수 있도록 다음과 같은 메서드를 제공하고 있다.


1 Model addAttribute(String name, Object value)

value 객체를 name 이름으로 추가한다. 뷰 코드에서는 name으로 지정한 이름을 통해서 value를 사용한다.


2 Model addAttribute(Object value)

value를 추가한다. value의 패키지 이름을 제외한 단순 클래스 이름을 모델 이름으로 사용한다. 이 때 첫 글자는 소문자로 처리한다.

value가 배열이거나 컬렉션인 경우 첫 번째 원소의 클래스 이름 뒤에 "List"를 붙인 걸 모델 이름으로 사용한다.

이 경우에도 클래스 이름의 첫자는 소문자로 처리한다.


3 Model addAllAttribute(Collection<?> values)

addAttribute(Object value) 메서드를 이용해서 컬렉션에 포함된 객체들을 차례대로 추가한다.


4 Model AddAllAttributes(Map<String, ?> attributes)

Map에 초함된 <키, 값>에 대해 키를 모델 이름으로 사용해서 값을 모델로 추가한다.


5 Model mergeAttributes(Map<String, ?> attributes)

Map에 포함된 <키, 값>을 현재 모델에 추가한다. 단, 키와 동일한 이름을 갖는 모델 객체가 존재하지 않는 경우에만 추가된다.


6 boolean containsAttributes(String name)

지정한 이름의 모델 객체를 포함하고 있는 경우 true를 리턴한다.


다음 코드는 컨트롤러 요청 처리 메서드에서 Model 타입의 파라미터를 이용해서 모델을 설정하는 예를 보여주고 있다.

@RequestMapping("/search/game.do")

public String search(SearchCommand command, Model model){

...

model.addAttrubute("searchResult", result);

model.addAttribute("searchTypeList", searchTypeList);

...

}


컨트롤러 요청 처리 메서드에서 Model 타입을 리턴하고 싶다면 Model 인터페이스의 구현 클래스인 org.springframework.ui.ExtendedModelMap 클래스를 사용하면 된다.

@RequestMapping("search/gmae.do")

public Model search(SearchCommand command){

...

Model model = new ExtendedModelMap();

model.addAttribute("searchResult", result);

model.addAttribute("searchTypeList", searchTypeList);

return model;

}


(4) ModelMap 클래스
org.springframework.ui.ModelMap 클래스는 Map의 구현 클래스로서 제공하는, 컨트롤러 메서드의 파라미터를 통해서 전달받는다.

@RequestMapping("search/game.do")
public String search(SearchCommand command, ModelMap model){
...
model.addAttribute("result", searchResult);
...
}
ModelMap 클래스가 제공하는 모델 설정 관련 메서드는 Model 인터페이스와 동일하므로 추가 설명은 하지 않겠다.

3. ModelAndView를 통한 모델 설정

org.springframework.web.servlet.ModelAndView 클래스는 컨트롤러의 처리 결과를 보여 줄 뷰에 전달할 값을 저장하는 용도로 사용되는데, 본 절에서는 ModelAndView 객체를 생성하는 방법을 살펴보자.


(1) modelAndView 객체를 생성하는 방법은 setViewName(String viewName) 메서드를 이용하여 뷰 이름을 설정하고, addObject(String name, Object value) 메서드를 이용하여 뷰에 전달할 값을 추가한다.

@RequestmMapping("/search/game.do")

public ModelAndView search(SearchCommand command) {

ModelAndView mav = new ModelAndView();

mav.setViewName("search/game");

mav.addObject("greeting", getGreeting());

return mav;


(2) Map에 저장된 <키, 값> 쌍 전체를 뷰에 전달할 값으로 ModelAndView 객체에 추가하고 싶다면 다음과 같이 addAllObjects(Map modeMap) 메서드를 사용하면 된다.

Map referenceMap = referenceData();

mav.addAllObjects(referenceData);


(3) 생성자를 사용해서 뷰 이름과 Map을 전달할 수도 있다.

Map referenceMap = referenceData();

return new ModelAndView("search/game", referenceData);


(4) 뷰에 전달할 객체가 한 개 뿐이라면 다음과 같은 생성자를 이용해서 코드 분량을 줄일 수 있을 것이다.

return new ModelAndView("search.game", "result", referenceData");


4. @ModelAttribute 어노테이션을 이용한 모델 데이터 처리


@ModelAttribute 어노테이션을 이용하면 다음의 두 가지 작업을 수행할 수 있다.

1 @RequestMapping 어노테이션이 적용되지 않은 별도 메서드로 모델에 추가될 객체를 생성

2 커맨드 객체의 초기화 작업을 생성


cp.) @ModelAttribute의 속성은 아래와 같다.


(1) 참조 데이터 생성

웹 어플리케이션을 구현하다 보면 동일한 모델 데이터를 두 개 이상의 요청 처리 결과 화면에서 보여주어야 할 때가 있다.

예를 들어, 검색 메인 화면과 검색 결과 화면에서 검색 타입과 인기 검색어를 보여줄 수 있을 것이다. 

이 경우 다음과 같이 이들 공통 모델 데이터를 설정해주는 메서드를 구현한 뒤 요청 처리 메서드에서 호출하도록 구현 할 수 있을 것이다.

private void referenceData(Model model){

...

model.addAttribute("searchTypeList", searchTypeList);

model.addAttribute("popularQueryList", queryList);

}


@RequestMapping("/search/main.do")

public String main(Model model){

referenceData(model);

return "search/main";


@RequestMapping("search/game.do")

public ModelAndView search(

@ModelAttribute("command") SeachCommand command, Model model){

referenceData(model);

ModelAndView mav = new ModelAndView("search/game");

SearchResult result = searchService.search(Command);

mav.addObject("searchResult", result);

return mav;

}


하지만, 위와 같이 구현하려면 다음과 같은 단점이 발생한다.

1 Model이나 ModelMap과 같이 모델 정보를 설정할 때 사용할 타입이 동일해야 한다.

2 각 요청 처리 메서드에 공통으로 사용되는 모델을 설정하기 위한 코드가 중복된다.


@ModelAttribute 어노테이션을 사용하면 이런 단점 없이 두 개 이상의 요청 처리 메서드에서 공통으로 사용되는 모델을 설정할 수 있다.

@ModelAttribute 어노테이션을 메서드에 적용하면 해당 메서드가 생성한 객체가 뷰에 전달된다.

따라서, @ModelAttribute 어노테이션을 사용하면 두 개 이상의 요청 처리 메서드에서 공통으로 사용되는 모델 객체를 생성할 수 있다.

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.ModelAttribute;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.servlet.ModelAndView;


@Controller

public class GameSearchController {

@Autowired

private SearchService searchService;


@ModelAttribute("searchTypeList")

public List<SearchType> referenceSearchTypeList() {

List<SearchType> options = new ArrayList<SearchType>();

options.add(new SearchType(1, "전체"));

options.add(new SearchType(2, "아이템"));

options.add(new SearchType(3, "캐릭터"));

return options;

}


@ModelAttribute("popularQueryList")

public String[] getPopularQueryList() {

return new String[] { "게임", "창천2", "위메이드" };

}


@RequestMapping("/search/main.do")

public String main() {

return "search/main";

}


@RequestMapping("/search/game.do")

public ModelAndView search(@ModelAttribute("command") SearchCommand command) {

ModelAndView mav = new ModelAndView("search/game");

System.out.println("검색어 = " + command.getQuery().toUpperCase());

SearchResult result = searchService.search(command);

mav.addObject("searchResult", result);

return mav;

}

...

}

위 코드에서 main() 메서드와 search() 메서드는 둘 다 referenceSearchTypeList() 메서드와 getPopularQueryList() 메서드를 호출하고 있지 않다.

하지만 reference SearchTypeList() 메서드와 getPopularQueryList() 메서드에는 @ModelAttribute 어노테이션이 적용되어 있으므로 main() 메서드와 search() 메서드가 실행될 때 refereceSearchTypeList() 메서드와 getPopularQueryList() 메서드가 리턴한 객체가 모델로 뷰에 함께 전달된다.

이때 @ModelAttribute 어노테이션의 값이 뷰에 전달되는 객체의 이름으로 사용되므로, 뷰 코드에서는 다음과 같이 @ModelAttribute 어노테이션의 값을 이용해서 모델 객체에 접근할 수 있게 된다.


<body>

인기 키워드: <c:forEach var="popularQuery" items="${popularQueryList}">${popularQuery} </c:forEach>

<form action="game.do">

<select name="type">

<c:forEach var="searchType" items="${searchTypeList}">

<option value="${searchType.code}" <c:if test="${command.type == searchType.code}">selected</c:if>>

${searchType.text}</option>

</c:forEach>

</select>

<input type="text" name="query" value="${command.query}"/>

<input type="submit" value="검색" />

</form>


(2) 커맨트 객체 초기화

@ModelAttribute 어노테이션을 사용하면 커맨드 객체의 초기화 작업을 수행할 수도 있다.

예를 들어, GET 요청시에는 폼의 특정 값을 미리 초기화하고 POST 요청시에는 폼에 입력한 값을 커맨트 객체로 받아야 한다고 해보자.

이런 경우 @ModelAttribute 어노테이션을 사용하면 다음과 같이 GET 요청과 POST 요청에 대해 알맞게 커맨트 객체를 초기화할 수 있다.

아래 코드는 @ModelAttribute 어노테이션의 사용 예를 보여주고 있다.


@Controller

@RequestMapping("/account/create.do")

public class CreateAccountController {


@ModelAttribute("command")

public MemberInfo formBacking(HttpServletRequest request) {

if (request.getMethod().equalsIgnoreCase("GET")) {

MemberInfo mi = new MemberInfo();

Address address = new Address();

address.setZipcode(autoDetectZipcode(request.getRemoteAddr()));

mi.setAddress(address);

return mi;

} else {

return new MemberInfo();

}

}


@RequestMapping(method = RequestMethod.GET)

public String form() {

return "account/creationForm";

}


@RequestMapping(method = RequestMethod.POST)

public String submit(@ModelAttribute("command") MemberInfo memberInfo)

return "account/created";

}

}

위 코드에서 주목할 점은 formBacking() 메서드의 @ModelAttribute 어노테이션값과 submit() 메서드의 파라미터에서 사용된 @ModelAttribute 어노테이션의 값이 "command"로 동일하다는 것이다. submit() 메서드는 POST 요청을 처리하도록 되어 있는데, POST 요청이 전송될 경우 formBacking() 메서드가 생성한 MemberInfo 객체는 "command"라는 이름의 모델 객체로 저장되며, 이렇게 생성된"command" MemberInfo 객체는 submit() 메서드의 동일한 모델 이름을 갖는 커맨드 객체(첫 번째 파라미터)로 전달된다.


@ModelAttribute 어노테이션이 적용된 메서드가 @RequestMapping 어노테이션이 적용된 메서드 보다 먼저 호출되기 때문에, 

커맨드 객체에 대한 초기화 작업이 필요하다면 커맨드 객체와 동일한 이름을 갖는 @ModelAttribute 어노테이션이 적용된 메서드를 이용하면 된다.


(3) @ModelAttribute 어노테이션 적용 메서드에 전달 가능한 파라미터 타입
@ModelAttribute 어노테이션이 적용된 메서드는 @RequestMapping 어노테이션이 적용된 메서드와 동일한 타입의 파라미터를 가질 수 있다. 
앞서 코드에서는 HTTP 메서드를 확인하기 위해 HttpServletRequest를 파라미터로 전달받았다.

@ModelAttribute("command")
public MemberInfo formBacking(HttpServletRequest request) {
if (request.getMethod().equalsIgnoreCase("GET")) {
...
} else {
return new MemberInfo();
}
}
@ModelAttribute 어노테이션이 적용 메서드는 HttpServletRequest 뿐만 아니라 필요에 따라 Locale, @RequestParam 어노테이션 적용 파라미터, @PathVariable 어노테이션 적용 파라미터 등을 이용해서 모델 객체를 생성하는 데 필요한 정보를 구할 수 있다.


'Programing > Spring' 카테고리의 다른 글

[Spring] 파일업로드 처리  (2) 2014.12.18
[Spring] 요청 URI 매칭  (0) 2014.12.18
[Spring] 뷰 지정  (0) 2014.12.18
[Spring] 컨트롤러 메서드의 파라미터 타입  (2) 2014.12.18
[Spring] HTML 폼과 자바빈 객체  (0) 2014.12.18

[Spring] 뷰 지정


뷰 지정

컨트롤러 처리 메서드는 처리 결과를 보여줄 뷰 이름이나 View 객체를 리턴하고, DispatcherServlet은 뷰 이름이나 View 객체를 이용해서 뷰를 생성하게 된다. 뷰 이름은 직접 또는 자동으로 설정할 수 있다.


1. 뷰 이름 명시적 지정: ModelAndView와 String 리턴 타입

뷰 이름을 명시적으로 리턴하려면 ModelAndView나 String을 리턴해야 한다.


(1) ModelAndView 클래스의 생성자

@RequestMapping("/hello.do")

public ModelAndView index(){

ModelAndView mav = new ModelAndView("hello");

...

return mav;

}


(2) setViewName() 메서드

@RequestMapping("/hello.do")

public ModelAndView index(){

ModelAndView mav = new ModelAndView();

mav.setViewName("hello");

...

return mav;

}



(3)  리턴 타입이 String인 경우

@RequestMapping("/help/main.do")

public String helpMain(ModelMap model){

...

return "help/main";

}

리턴 타입이 ModelAndView가 아니고 String 타입인 경우


2. 뷰 이름 자동 지정

다음의 경우 RequestToViewNameTranslator를 이용해서 URL로부터 뷰 이름을 결정한다.

- 리턴 타입이 Model이나 Map인 경우

- 리턴 타입이 void 이면서 ServletResponse나 HttpServletResponse 타입의 파라미터가 없는 경우


스프링 설정 파일에 RequestToViewNameTranslator 빈이 존재하지 않을 경우 기본적으로 DefalutRequestToViewNameTranslator를 사용한다. 이 클래스는 요청 URI로부터 맨 앞의 슬래시와 확장자를 제외한 나머지 부분을 뷰 이름으로 사용한다. ( 더 정확하게는 전체 경로를 사용하지 않도록 설정한 경우 서블릿 경로를 제외한 나머지 경로가 사용된다.)

@RequestMapping("/search/game2.do")

public Map<String, Object> search(){

HashMap<String, Object> model = new HashMap<String, String>();

...

return model;

}

위 코드는 뷰에 전달된 모델 데이터를 갖고 있는 Map을 리턴하고 있다. 이 경우 RequestToViewNameTranslator를 이용하여 결과를 보여줄 뷰 이름을 결정하게 되는데,

DefaultRequestToViewNameTranslator가 사용될 경우 다음과 같이 URL로부터 뷰 이름이 결정된다.

/search/game2.do --> search/game2


3. 리다이렉트 뷰

뷰 이름에 "redirect:" 접두어를 붙이면, 지정한 페이지로 리다리렉트 된다. 리다이렉트 URL은 다음과 같이 두 가지 방식으로 입력할 수 있다.

- redirect:/bbs/list: 현재 서블릿 컨텍스트에 대한 상대적인 경로로 리다이렉트

- redirect:http://host/bbs/list: 지정한 절대 URL로 리다이렉트


ModelAndView mav = new ModelAndView();

mav.setViewName("redirect:error.do");

return mav;



[Spring] 컨트롤러 메서드의 파라미터 타입


컨트롤러 메서드의 파라미터 타입


컨트롤러 @RequestMapping 어노테이션이 적용된 메서드는 커맨드 클래스뿐만 아니라 HttpServletRequest, HttpSession, Locale 등 웹 어플리케이션과 관련된 다양한 타입의 파라미터를 가질 수 있는데, 전달 가능한 파라미터 타입은 다음과 같다.



1. @RequestParam 어노테이션을 이용한 파라미터 매핑

컨트롤러를 구현하면서 가장 많이 사용되는 어노테이션이 바로 @RequestParam 어노테이션이다. @RequestParam 어노테이션은 HTTP 요청 파라미터를 메서드의 파라미터로 전달받을 때 사용된다. @RequestParam 어노테이션과 HTTP 요청 파라미터의 관계는 다음과 같다.


첫번째 파라미터인 userName은 name 요청 파라미터의 값을 전달 받으며, 두 번째 파라미터인 userEmail 파라미터는 email 파라미터 값을 전달 받는다.

바로, JSP에서 String name = request.getParameter("name"); 과 같다.


@RequestParam은 Controller 메소드의 파라미터와 웹요청 파라미터와 맵핑하기 위한 어노테이션이다. 관련 속성은 아래와 같다.

@Controller

public class HelloController {

 

    @RequestMapping("/hello.do")

   

    public String hello(@RequestParam("name") String name,

    // required 조건이 없으면 기본값은 true, 즉 필수 파라미터이다. 파라미터 pageNo가 존재하지 않으면 Exception 발생.       

     @RequestParam(value="pageNo", required=false) String pageNo){ 

    // 파라미터 pageNo가 존재하지 않으면 String pageNo는 null.

...

    }

}

@RequestParam 어노테이션이 적용된 파라미터는 기본적으로 필수 파라미터이다. 따라서 @RequestParam 어노테이션에 명시한 HTTP 요청 파라미터가 존재하지 않을 경우

스프링 MVC는 잘못된 요청을 의미하는 400 응답 코드를 웹 브라우저에 전송한다. 예를 들어 @RequestParam 어노테이션의 별도 설정을 하지 않은 경우, 다음의 URL을 요청하면 400 에러가 발생하게 된다.


http://host/chap06/search/internal.do?query=spring&p=3


@Controller

public class SearchController{

@RequestMapping("/search/internal.do")

public ModelAndView seachInternal(

@RequestParam("query") String query, @RequestParam("p") int pageNumber){

...

}

}

첫 번째 파라미터는 query 요청 파라미터의 값을 전달받으며, 두 번째 파라미터인 pageNumber 파라미터는 p 파라미터의 값을 전달받는다.

@RequestParam 어노테이션이 적용된 파라미터가 String이 아닐 경우 실제 타입에 따라서 알맞게 타입 변환을 수행한다. 예를 들어, pageNumber 파라미터의 타입은 int인데, 이 경우 자동으로 문자열을 int 타입으로 변환해준다. 


만약 아래와 같이 int 타입으로 변환할 수 없는 값인 "a"를 pageNumber 파라미터에 매핑되는 HTTP 요청 파라미터로 전달하면, 스프링 MVC는 잘못된 요청(Bad Request)을 의미하는 400 응답 코드를 웹 브라우저에 전송한다.

http://localhost:8080/chap06/search/internal.do?query=spring&p=a

@RequestParam 어노테이션이 적용된 파라미터는 기본적으로 필수 파라미터이다. 따라서 @RequestParam 어노테이션에 명시한 HTTP 요청 파라미터가 존재하지 않을 경우 스프링 MVC는 잘못된 요청을 의미하는 400 응답코드를 웹 브라우저에 전송한다. 예를 들어 아래와 같이 @RequestParam 어노테이션의 별도 설정을 하지 않은 경우, 다음의 URL을 요청하면 400 에러가 발생하게 된다.


http://localhost:8080/chap06/search/internal.do?query=spring


필수가 아닌 파라미터인 경우 required 속성 값을 false로 지정해 주면 된다. 아래 코드는 적용 예이다. 참고로 required 속성의 기본 값은 true이다.

import orr.springframework.web.bind.annotation.RequestMapping

...

...

@RequestMapping("/search/external.do")

public ModelAndView searchExternal(

@RequestParam(value="query", required=false) String query, @RequestParam(value ="p", required=false) int pageNumber){

System.out.println("query="+query+",pageNumber="+pageNumber);

...

}

필수가 아닌 요청 파라미터의 값이 존재하지 않을 경우 null 값을 할당한다. 그런데, null을 할당할 수 없는 기본 데이터 타입인 경우에는 타입 변환 에러가 발생하게 된다. 예를 들어 , 위 코드에서 "p" 요청 파라미터를 필수가 아닌 파라미터로 설정했는데, 이 상태에서 "p" 요청 파라미터를 지정하지 않은 경우 null을 기본 데이터 타입으로 변환할 수 없다는 예외가 발생하게 된다.


기본 데이터 타입을 사용할 경우 HTTP 요청 파라미터가 존재하지 않으며 기본 값을 할당하는 경우가 많은데, 이런 경우에는 다음과 같이 defaultValue 속성을 이용해서 기본값을 지정할 수 있다.

@RequestMapping("/search/external.do")

public ModelAndView searchExternal(

@RequestParam(value="query", required=false) String query, @RequestParam(value="p", defaultValue="1") int pageNumber){

System.out.println("query="+query+",pageNumber="+pageNumber);

...

}

defaultValue 속성을 이용해서 기본 값을 지정하면 해당 요청 파라미터를 지정하지 않을 경우 defaultValue 속성에 지정한 문자열을 값으로 이용하게 된다. 예를 들어, 위와 같이 "p" 요청 파라미터의 기본 값을 "1"로 지정했다면,

http://localhost:8080.chap06/search/external.do?query=spring

"p" 파라미터가 존재하지 않으므로 기본 값으로 지정한 "1"을 "p" 요청 파라미터의 값으로 사용하게 되고, 따라서 pageNumber 파라미터의 값은 1이 된다.


2. @RequestHeader 어노테이션을 이용한 헤더 맵핑


@RequestHeader 어노테이션을 이용하면 HTTP 요청 헤더의 값을 메서드의 파라미터로 전달받을 수 있다.

import org.springframework.web.bind.annotation.RequestHeader;

import org.springframework.web.bind.annotation.RequestMapping;


@Controller

public class HeaderController {


@RequestMapping("/header/check.do")

public String check(@RequestHeader("Accept-Language") String languageHeader) {

System.out.println(languageHeader);

return "header/pass";

}

}


cf.) 컨트롤러 메서드의 리턴 타입

컨트롤러 메서드는 ModelAndView를 비롯한 몇 가지 리턴 타입을 가질 수 있다.


'Programing > Spring' 카테고리의 다른 글

[Spring] 모델 생성하기  (1) 2014.12.18
[Spring] 뷰 지정  (0) 2014.12.18
[Spring] HTML 폼과 자바빈 객체  (0) 2014.12.18
[Spring] 자바 코드 기반 설정  (0) 2014.12.17
[Spring] 어노테이션 기반 설정  (0) 2014.12.17

[Spring] HTML 폼과 자바빈 객체


HTML 폼과 자바빈 객체


스프링 MVC는 HTML 폼에 입력한 데이터를 자바빈 객체를 이용해서 전달 받을 수 있도록 하고 있다.

예를 들어 HTML 폼의 항목과 이름과  자바빈 클래스의 프로퍼티 이름이 일치할 경우 폼에 입력한 값을 해당 자바빈 클래스의 프로퍼티 값으로 설정해주는 기능을 제공하고 있다.


<from method="post">

<input type="hidden" name="parantId" value="0" />

제목: <input type="text" name="title" />

내용: <textarea name="content"> </textarea>

<input type="submit"/>

</from>


public class NewArticleCommand{

private String title;

private String content;

private int parentId;


// 입력 항목의 이름과 일치하는 프로퍼티에 값이 저장

public void setTitle(String tilte){

this.title;

}

public void setContent(String content){

this.content;

}

public void setParentId(int parentId){

this.parentId = parentId;

}

}


@Controller

@RequestMapping("/article/newArticle.do")

public class NewArticleController{


@RequestMapping(method = RequestMethod.POST)

public String submit(NewArticleCommand command){

//command.getTitle(): title 파라미터 값 저장

//command.getContent(): content 파라미터의 값 저장

//comman.getParentId(): parentId 파라미터의 값 저장

return "article/newArticleSubmitted";

}

}

HTML 폼에 입력한 데이터를 자바빈 객체로 전달받는 방법은 매우 간단하다. 단지 @RequestMapping 어노테이션이 적용된 메서드의 파라미터로 자바빈 타입을 추가해주기만 하면 된다.

폼에 입력한 값을 NewArticleCommand 클래스로 전달 받고 싶다면 요청 처리 메서드에 NewArticleCommand 타입의 파라미터를 추가해주기만 하면 된다.


1. 뷰에서 커맨드 객체 접근하기

뷰 코드에서는 컨트롤러의 @RequestMapping 어노테이션 메서드에서 전달받은 command 객체에 접근할 수 있다. 예를 들어, 아래와 같이 NewArticleCommand 타입의 command 객체를 전달받는다고 하자.

@RequestMapping(method =ResquestMethod.POST)

public String submit(NewArticleCommand command){

}

 

이 경우 컨트롤러의 처리 결과를 보여주는 뷰 코드에서는 command 객체의 클래스 이름을 이용해서 command 객체에 접근할 수 있다. 즉, command 객체는 자동으로 모델에 추가된다.

<body>

제목:${newArticleCommand.title}


뷰에서 사용할 모델의 이름을 변경하고 싶다면 다음과 같이 @ModelAttribute 어노테이션을 이용해서 command 객체의 모델 이름을 지정할 수 있다.

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.ModelAttribute;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


@Controller

@RequestMapping("/article/newArticle.do")

public class NewArticleController {


@RequestMapping(method = RequestMethod.POST)

public String submit(@ModelAttribute("command") NewArticleCommand command) {

articleService.writeArticle(command);

return "article/newArticleSubmitted";

}

}

@ModelAndView 어노테이션을 이용해서 command 객체의 모델 이름을 지정했다면, 뷰 코드에서는 다음과 같이 해당 모델 이름을 사용해서 command 객체에 접근할 수 있다.

제목:${command.title} <---  [기존의 제목:${newArticleCommand.title}]


2. 커맨드 객체로 List 받기

스프링 MVC는 List 타입의 프로퍼티에 대한 바인딩도 처리해준다. 예를 들어, 아래 코드는 OrderItem 목록을 갖는 List타입의 orderItems 프로퍼티를 갖고 있다.


public class OrderCommand {


private List<OrderItem> orderItems; //OrderItem 목록을 갖는 List타입의 orderItems 프로퍼티

private Address address;


public List<OrderItem> getOrderItems() {

return orderItems;

}


public void setOrderItems(List<OrderItem> orderItems) {

this.orderItems = orderItems;

}


public Address getAddress() {

return address;

}


public void setAddress(Address address) {

this.address = address;

}

}


List 타입의 프로퍼티에 값을 전달하고 싶다면 폼에서 "프로퍼티명[index].프로퍼티" 같이 입력 폼의 이름을 구성하면 된다. 예를 들어, 위 코드의 orderItems 프로퍼티에 값을 전달하고 싶다면

아래 코드와 같이 폼을 구성하면 된다.

<form method="post">

상품1: ID - <input type="text" name="orderItems[0].itemId" /> 

개수 - <input type="text" name="orderItems[0].number" />

주의 - <input type="text" name="orderItems[0].remark" />

<br/>

상품2: ID - <input type="text" name="orderItems[1].itemId" /> 

개수 - <input type="text" name="orderItems[1].number" />

주의 - <input type="text" name="orderItems[1].remark" />

<br/>

......

<input type="submit" />

</form>



폼의 <input>이나 <select> 등의 name에 인덱스 값을 포함시키면 List 타입의 프로퍼티에 값을 전달받을 수 있다. 컨트롤러 코드에서는 다음과 같이 커맨드 객체를 @RequestMapping 메서드에 지정해주기만 하면 된다.

@Controller

@RequestMapping("/order/order.do")

public class OrderController {

@RequestMapping(method = RequestMethod.POST)

public String submit(OrderCommand orderCommand) {

return "order/orderCompletion";

}

}