본문 바로가기

nerv-team.co.kr

[Spring] nerv-team.co.kr "Default MVC" CH02.상세 화면 보기

자, 2번째 챕터 입니다. CH01 목록띄우기 (http://joke00.tistory.com/105) 을 무사히 마친 뒤에 보셔야 합니다.
목록화면에서 클릭하면 상세 화면으로 갈 것이기 때문이죠~ ^^;;;
못했다면, Shop.war 를 eclipse로 import 하셔서 시작해 보아요! 준비준비~ (import 는 할줄 안다고 생각하겠습니다)
(신입의덕목 중 하나가 tool 을 잘 사용할줄 알아야 한다는 사실을 새삼 깨닫는 요즘입니다 음하하하하---)

자, 이제 시작해 볼까요? 히위고오~
일단, Application 모양부터 보여 드리겠습니다.

자, 빨간색으로 표시 해 둔 부분이 이번에 추가 되는 파일들 입니다.
눈치 빠른 분들은... 아시겠지만. . 나머지 파일들은 수정이 되겠지요?~ ^^

그리고 디버그의 중요한 log4j.properteis 가 등장하네요..
log4j 는 너무나도 널리 알려진 것이고 간단하기 떄문에 제 블로그에도 보면 제가 잘 정리해 놓은 문서
<(http://joke00.tistory.com/61) 여기서 다운 받아서 보세요 ~^^ >가 있기 때문에
 걍 건너 뛰겠습니다.

자, 차근차근 해 봅시다!

이번 DetailController 에서 사용할 Spring 스펙(?) 입니다 -0-


다른점이 있다면? 앞에서 IndexController 말고 이번엔 DetailController 는 SimpleUrlHandleMapping  을 사용해 보겠습니다.

앞에서 쓴 BeanNameUlHandlerMapping  을 쓸 경우에서는 클래스에서 리퀘스트 컨트롤러를 작성하기 때문에 스프링 설정파일내에 리퀘스트 Url 을 기술할 필요가 있습니다. 컨트롤러의 수가 증가하여 설정파일 내에 리퀘스트 컨트롤러 기술이 흩어져 있어 복잡하게 됩니다. 그렇게 되면 리퀘스트 Url 에 관련된 리퀘스트 컨트롤러를 찾는 것이 번거롭게 되기 때문에 이들의 맵핑을 한 장소에서 관리하게끔 SimpleUrlHandlerMapping(이하 SimpleMapping 이라 부르겠음)  을 사용해 하겠습니다.

*SimpleMapping 클래스
1. Properties 형의 mappings 를 가지고 있습니다.
2. 리퀘스트 URL 과 리퀘스트 컨트롤러를 관련지은 Properties 오브젝트를 설정하면 됩니다.

기존 앞의 ch01 의 shop-servlet.xml 파일이 변경이 될 것입니다. 어떻게?
이렇게!!! +_+

일단. 그림을 보고 2가지를 눈치 채셔야 합니다. 1번과 2번.
1. bean 의 name 이었던것이 prop 의 key 로 옵니다
2. bean 의 id 가 prop 의 value 로 옵니다.

그리곤 indexCotnroller bean 의 모양은 이렇게 바뀌겠습니다. (별거 없습니다. Name 이 빠진것 뿐입니다 +_+)
<bean id="indexController" class="controller.IndexController">
  <property name="itemCatalog"><ref bean="itemCatalog"/></property>
 </bean>

앞으로 리퀘스트 컨트롤러가 추가될 때마다 mappings 프로퍼티에 리퀘스트 URL 과 리퀘스트 컨트롤러의 정의를 추가해 감으로써 리퀘스트 컨트롤러를 일괄로 관리할 수 있다는 이점이 있겠습니다! ^^



이번엔 ViewResolver 를 정의해 보겠습니다.
중복으로 쓰이는 view의 위치를 앞, 뒤 로 정의해 줄 수가 있습니다.
<!-- ViewResolver -->
 <bean id="internalResourceViewResolver"
  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass">
   <value>org.springframework.web.servlet.view.JstlView</value>
  </property>
  <property name="prefix">  // pre : 영어의 "~~ 앞에" 라는 뜻을 담고있듯이 주소(?^^;)의 앞 부분을 뜻합니다.
   <value>/jsp/</value>    // http://localhost:8080/Shop/jsp/파일이름.jsp 이렇게 부를때의 저 경로중의 /jsp/중복부분!
  </property>
  <property name="suffix"> // 얘는 아마 후위 라는 뜻을 가졌던것 같은데... (영어단어찾아보셈--;) 주소의 끝
   <value>.jsp</value>     // *.jsp 로 끝나니깐 .jsp 를 썻습니다.
  </property>
 </bean>
viewResolver 를 정의해 줌으로써 앞으로 controller 에서 viewName 써주는 코드가 변경될 것입니다.
어떻게? 이렇게!



View 모양
--------------------------------------------------------------------------
Prefix        |           ViewName           | suffix       |             결과                 |
--------------------------------------------------------------------------
/jsp/         |            index                 | .jsp          |       /jsp/index.jsp         |
--------------------------------------------------------------------------


*xml  설정

자, 그럼 이제 servlet.xml 설명을 마치고, 본격적으로  xml 설정을 시작하겠습니다.

1 web.xml :  웹 어플리케이션의 기본설정 파일.
2. shop-servlet.xml : 스프링 MVC 용의 설정 파일
3. applicationContext.xml  : 비지니스로직 정의용 스프링 설정 파일

앞에서는 servlet.xml 파일안에 모두 다 때려 박았지만, 이번에 등장한 applicationCotnext.xml 을 추가함으로써 web층과 비지니스층으로 구분하여 작성하도록 하겠습니다.
ch01 과 달라진 점은 appplicationContext.xml 을 작성하였기 때문에, listner 로 ContextLoaderListener 클래스를 정의해야 합니다.
* ContextLoaderListener 클래스
: applicationContext.xml 을 로드하면서 ServletContextListener 인터페이스를 구현하고 있기 때문에 ServletContext  인스턴스 생성시 호출됩니다. 즉, ContextLoaderListener  는 DispatcherServlet 클래스의 로드보다 먼저 동작하여 비즈니스 로직층을 정의한 스프링 설정파일을 로드합니다.

!!! 설정파일의 로드 순서 !!!
여러개의 설정파일을 이용할 경우, 설정파일의 참조관계가 걸리기 때문에 파일의 로드 순서가 중요합니다.
servlet.xml 파일이 applicationContext.xml 파일로 정의된 인스턴스( 예:: ItemCatalog , ItemDao )를 참조하기 때문에 servlet.xml 이 로드되기 전에 applicationContext.xml 파일이 로드 되어 있어야 합니다. ^^ 아시겠나요?
모르면 한번 application 작성해 보는것이 가장 이해가 빠르게 될 듯 합니다 !
(Antop 이 그랬던가..... 눈으로 보고 익히지 말고 손으로 치며 몸으로 익히라고 ㅠ-ㅠ 개발자 다운 말이죠;;; - -
이 뼛속까지 개발자인!! +_+ 네이버 in 정용 ㅎㅎㅎㅎ)

1. web.xml
..... 중략
<!-- 리스너를 설정합니다 -->
  <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 .....

2.shop-servlet.xml
(앞에서 다 설명한게 바로 이servlet.xml 이기 때문에 중복생략은 건너 뜁니다)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC
    "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
   
<beans>
 <!-- handlerMapping 설정  Detail Add-->
 <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  <property name="mappings">
   <props>
    <prop key="/index.do">indexController</prop>
    <prop key="/detail.do">detailController</prop>
   </props>
  </property>
 </bean>
<!-- Controller 설정 Detail Add -->
 <bean id="indexController" class="controller.IndexController">
  <property name="itemCatalog"><ref bean="itemCatalog"/></property>
 </bean>
  <bean id="detailController" class="controller.DetailController">
  <property name="itemCatalog"><ref bean="itemCatalog"/></property>
 </bean>
 <!-- ViewResolver  설정-->
 <bean id="internalResourceViewResolver"
  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass">
   <value>org.springframework.web.servlet.view.JstlView</value>
  </property>
  <property name="prefix">
   <value>/jsp/</value>
  </property>
  <property name="suffix">
   <value>.jsp</value>
  </property>
 </bean>
</beans>

3. applicationContext.xml
별거 없습니다. DataSource 설정, 그리고 ServiceImpl 단의 bean 설정 입니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC
    "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
 <!-- DataSource -->
 <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
  <property name="driverClassName"><value>oracle.jdbc.driver.OracleDriver</value></property>
  <property name="url"><value>jdbc:oracle:thin:@nerv-team.co.kr:1521:XE</value></property>
  <property name="username"><value>아이디</value></property>
  <property name="password"><value>패스워드</value></property>
 </bean>
 
 <!-- ItemCatalog -->
 <bean id="itemCatalog" class="service.impl.ItemCatalogImpl">
  <property name="itemDao"><ref bean="itemDao" /></property>
 </bean>
 
 <!-- ItemDao -->
 <bean id="itemDao" class="dao.impl.ItemDaoImpl">
  <property name="dataSource"> <ref bean="dataSource" /></property>
 </bean>
</beans>

자, 설정이 끝났습니다.

* Source 코드
이제 본격적으로 source 코드를 살펴 봅시다!

1. controller. DetailController
2. dao.impl.ItemDaoImpl
3. service.impl.ItemCatalogImpl
4. util 패키지

이 4가지를 중점적으로 살펴 보겠습니다.
1. controller. DetailController
AbstractController를 상속받아 구현했습니다.
일반 Controller 와의 차이점은
1. 스프링 설정파일에서 프로퍼티 설정함으로써 기능 추가 가능 ( AbstractController 프로퍼티 참조 )
2. handlerRequest() 메소드가 아닌 handleRequestInternal() 메소드 오버라이딩 구현 해야 합니다.



public class DetailController extends AbstractController{

private ItemCatalog itemCatalog;
 
 public void setItemCatalog(ItemCatalog itemCatalog) {
  this.itemCatalog = itemCatalog;
 }
 protected ModelAndView handleRequestInternal(HttpServletRequest request,
   HttpServletResponse response) throws Exception {
  //선택된 상품  ID 취득
  Integer itemId = RequestUtils.getIntParameter(request, "itemId");
  //선택된 상품 ID 로 부터 상품정보 취득
  Item item = this.itemCatalog.getItemByItemId(itemId);
  
  //모델작성
  Map model = new HashMap();
  model.put("item",item);
  //반환 값이 되는 ModelAndView 인스턴스 작성
  ModelAndView  mav = new ModelAndView();
  mav.setViewName("detail"); // 요건 viewResolver 에서 정의해줘서 그렇다는거 위에서 얘기 또 얘기 했었죠... 이제 이게 왜 이렇게 돼냐고 자꾸물으면... -_-;;; 뭐.. 또 말해줘야죠 움하하하하하하하
  mav.addAllObjects(model);
  
  return mav;
 }
}

 스프링에선 리퀘스트 파라미터 취득에 관한 유틸리티 클래스 로서 RequstUtil 클래스를 제공 합니다.
RequestUtils 클래스와 getIntParameter() 메소드를 사용함으로써 Integer 형으로 변환이 끝난 리퀘스트 파라미터를 취득 할 수 있습니다. (Integer 말고도 Double 등등이 있습니당 -0-^)

  를 사용하기 위해 RequestUtils는 spring 에서 제공하는 것을 import 시켜야합니다.
 <--------요거요거요거요거~ -0-///

2. service.impl.ItemCatalogImpl


 public Item getItemByItemId(Integer itemId) {
  return this.itemDao.findByPrimaryKey(itemId); // 쿼리 날리러 고고씽~*
 }


3. dao.impl.ItemDaoImpl
겉의 Class 부분은 제외하고 InnerClass 와 method 만을 보여 드리겠습니다.

............ 앞 단의 리스트 추출부분생략............
 /**
  * 상세 내용
  */
 private static final String SELECT_BY_PRIMARY_KEY =
  "SELECT ITEM_ID , ITEM_NAME, PRICE, DESCRIPTION, PICTUREURL FROM ITEM WHERE ITEM_ID = ?";
 private class ItemPreparedStatementSetterForPrimaryKey implements PreparedStatementSetter {
  //PreparedStatementSetter   : parameter 를 setting 할때 Object array 로 쓰기 싫고 명시적으로 넣고 싶을 때 쓴답니다.
  private Integer itemId;
  
  public ItemPreparedStatementSetterForPrimaryKey(Integer itemId) {
   this.itemId = itemId;
  }
  public void setValues(PreparedStatement pstmt) throws SQLException {
   //상품ID를 PreparedStatement 인스턴스로 설정
   pstmt.setInt(1, this.itemId.intValue());
  }
 }
 private class ItemResultSetExtractor implements ResultSetExtractor {
  public Object extractData(ResultSet rs) throws SQLException,
    DataAccessException {
   if(rs.next()) {
    Item item = new Item();
    item.setItemId(new Integer(rs.getInt(1)));
    item.setItemName(rs.getString(2));
    item.setPrice(new Integer(rs.getInt(3)));
    item.setDescription(rs.getString(4));
    item.setPictureUrl(rs.getString(5));
    return item;
   }
   return null;
  }
  
 }
 public Item findByPrimaryKey(Integer itemId) {
  return (Item)getJdbcTemplate().query(ItemDaoImpl.SELECT_BY_PRIMARY_KEY,
    new ItemPreparedStatementSetterForPrimaryKey(itemId),
    new ItemResultSetExtractor());
// sql, 넘겨줄 param , 결과 저장할 객체
 }



4. util 패키지
한글화 처리를 위해 파라미터들의 encoding 하는 부분입니다.
(전 UTF-8 기반이어서 모두 UTF-8 로 변환했는데, EUC-KR 인 분들은 EUC-KR 로 변경해 주세욤~)
요 녀석은 UiUtils.toUnicode(스트링) 이렇게해서 쓰일 것 이기 때문에 사용 방법은 <- 쪼~오거 저거저거 한줄 이기 때문에,
더이상의 설명은 없어도  될 듯 합니다 - 0 - 잇히히히히-

public class UiUtils {
 public static String toUnicode(String str) {
  try {
   byte[] b = str.getBytes("UTF-8");
   return new String(b);
  } catch (java.io.UnsupportedEncodingException e) {
   e.printStackTrace();
   return null;
  }
 }
 public static String toLatin(String str) {
  try {
   byte[] b = str.getBytes("UTF-8");
   return new String (b,"UTF-8");
  } catch (java.io.UnsupportedEncodingException e) {
   e.printStackTrace();
   return null;
  }
 }
}


* 결과화면입니다
처음 초기 시작 URL :    http://localhost:8080/Shop/index.do
전 파란 사과를 선택해보겠습니다.



상세 선택 URL :    http://localhost:8080/Shop/detail.do?itemId=4
우훗. - 아주 먹음직 스럽네요 - - 항상 배고파파파파 - -
다시 리스트 화면으로 돌아가기 위해선 상품리스트로 돌아감을 선택합니다.
그럼 리스트 화면이 뜰 겁니다! 제 2장 끄-읕! ^^




이번엔 이미지 파일들 때문에 용량이 3.7 M 나 되는군요.
용자님께서 알아서 테스트를~ 히위고~~~~~ ^^
수고하셨습니다~ CH 03 에서 만나요~ 바이바이~ ^^