2016년 9월 20일 화요일

DispatcherServlet 역할(퍼옴)



                                                                  
글출처: http://ellehu.com/jsp/3793
SpringFrameWork는 웹을 개발하는데 있어 Full Spec을 지원하는 프레임 웍이다.
SpringMVC로 웹개발을 할때 작동 순서는 위의 그림과 같다.

1. 클라이언트로 부터 URL이 요청되어 들어오면 제일먼저 web.xml 파일을 로딩하여 스프링에서 지원하는 DispatcherServlet이 실행된다.
(이름에서 알수 있듯이 SpringFramework는 Servlet 기반임으로 스프링에서 지원해주는 Controller에서는 HttpServletRequest,HttpServletResponse 인터페이스를 이용해서 request, response 인스턴스를 사용 할 수가 있다.)

2. DispatcherServlet은 클라이언트로부터 들어온 URL을 HandlerMapping 이라는 곳으로 전송후에 URL을 분석해서 알맞은 컨트롤러 이름을 DispatcherServlet 으로 보낸다.

3. HandlerMapping이라는 것을 통해서 실행될 Controller의 이름을 입력받은 DispatcherServlet은 전달받은 Controller를 실행 시킨다. 이렇게 실행된 Controller는 스프링에서 제공하는 ModelAndView 객체에 뷰페이지에 전달할 객체와 뷰페이지 이름 정보를 담고 DispatcherServlet으로 보낸다.

4. ViewResolver를 통해 보여질 View페이지를 탐색한후 View페이지를 보여준다.

SpringMVC 의 웹개발은 위와같은 루틴으로 움직이기 때문에 스프링 초기 설정을 할때는 다음과 같이 순서를 생각하면서 셋팅을 하면 잊어 버리지 않는다.

<스프링 설정>
1. web.xml 세팅
① DispatcherServlet을 세팅해 준다.
<servlet>
   <servlet-name>dispatcher</servlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
   <!-- 기본 위치는 [servlet name]-servlet.xml 은 WEB-INF에서 불러 오지만 기타 다른위치에 있을때는 위치를 지정한다 -->
   <init-param>
    <param-name>contextConfigLocation</param-name>
     <!-- 클래스 패스에 위치한 파일로부터 설정정보를 읽으려면 "classpath:" 를 붙이고 경로를 써준다 -->
     <!-- ex) classpath:kr/co/springboard/config.xml -->

    <param-value>/WEB-INF/config/dispatcher-servlet.xml</param-value>
   </init-param>
  </servlet>


<servlet-mapping>
   <servlet-name>dispatcher</servlet-name>
   <url-pattern>*.do</url-pattern>
</servlet-mapping>

Url pattern이 ~~.do 로 들어오게 되면 위에 설정에 servlet name이 dispatcher인 servlet을 실행해서 Spring의 핵심인 DispatcherServlet을 실행한다.

② DI 를 선언할 applicationContext 파일을 로딩하기 위해서 알맞은 리스너와 Context Param을 설정해 준다.

<listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
   /WEB-INF/config/applicationContext.xml
  </param-value>
 </context-param>

ContextLoaderListner는 기본적으로 /WEB-INF/applicationContext.xml 을 설정 파일로 로딩한다.
다른위치에 있을때 위와같이 context-param설정을 통해 위치를 정해 준다. 그리고 context파일이 많을때는 ,(콤마)를 찍고 다른 경로를 또 입력하면 된다.
context파일 이름이 applicationContext.xml 과 applicationContext-ibatis.xml 이라면 applicationContext*.xml 이라고 설정하면 한번에 두가지 파일을 불러 들인다.

③ encoding 설정을 해준다.
jsp에서 response.setCharactorEncoding() 메소드를 사용해서 페이지에 맞는 케릭터셋을 지정할 수 있으나 스프링에서는 위와 같은 설정 없이 케릭터 셋을 지정해 줄 수 있다.

<filter>
   <filter-name>encodingFilter</filter-name>
   <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
   <init-param>
    <param-name>encoding</param-name>
    <param-value>EUC-KR</param-value>
   </init-param>
  </filter>
  
  <filter-mapping>
   <filter-name>encodingFilter</filter-name>
   <url-pattern>/*</url-pattern>
  </filter-mapping>

2. 위에서 선언한 dispatcher-servlet.xml 파일을 생성한다.
① urlHandlerMapping을 설정한다.
<bean id="springBoardUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
     <property name="mappings">  <!-- mappings는 바뀌면 안된다. -->
      <props>
       <prop key="/boardList.do">listAbstractCommandController</prop>
       <prop key="/boardWrite.do">writeSimpleFormController</prop>
       <prop key="/boardReadDelete.do">readDeleteMultiActionController</prop>
      </props>
     </property>
    </bean>

boardList.do 라는 url로 들어오면 listAbstractCommandController 클래스를 실행 시킨다. 물론 이 이름은 나중에 설정할 applicationContext.xml 에 선언되어있는 bean의 id와 같아야 한다.

② ModelAndView객체를 받아 View페이지로 이동시켜줄 View Resolver를 구현한다.

<bean id="viewResolver"
       class="org.springframework.web.servlet.view.InternalResourceViewResolver">
       <property name="prefix" value="/WEB-INF/view/"/>
       <property name="suffix" value=".jsp"/>
</bean> 

위와 같은 설정으로 만약에 ModelAndView 에 ViewName 이 boardList 라는 String 값이 들어 있으면 /WEB-INF/view/boardList.jsp 라는 파일을 부르게 된다. prefix는 경로의 앞에 붙는 것, 즉 viewName의 앞쪽에 붙는 것, subffix는 viewName뒤에 붙는것, 즉 확장자라고 생각하면 되겠다.


3.스프링에서 사용할 DI를 applicationContext.xml에 설정한다.

4. 기능에 맞게 각종 Controller를 사용해서 구현한다

web.xml url-pattern / 와 /* 의 차이점

출처 :  http://lng1982.tistory.com/97

[*.do에서 /* 로 바꾸게 된 이유]
스프링 3.1 샘플 프로젝트의 web.xml(DD) 구성 시 url-pattern을 *.do와 같이 설정하였다.
이유는 단순하다.
프로젝트를 진행할 때 항상 *.do를 사용했기 때문이다.

하지만 REST 방식의 웹 어플리케이션을 구성하기 위해서는 다음과 같은 URL 형식을 제공해야 하는데 현재는 *.do와 같이 되어 있기 때문에 pattern을 /* 와 같이 변경해야만 했다.
http://localhost:8080/user/list


[현상]
이제 내가 만들어 놓은 프로젝트는 REST 방식을 지원하는 spring web application이 되었다.
허나 controller mapping url을 호출하면 404 에러가 발생하면서 아래와 같은 로그가 찍힌다.
No mapping found for HTTP request with URI [/WEB-INF/view/common/layout/default/layout.jsp] in 
DispatcherServlet with name 'dispatcher'


[원인]
controller mapping url을 못 찾아서 발생하는 404가 아닌 /WEB-INF/view/common/layout/default/layout.jsp 의 매핑 URL을 찾을 수 없어서 발생하는 오류였다.

이 오류가 발생하는 원인을 알기 위해서는 서블릿 컨테이너와 웹 어플리케이션간의 연동 방법을 알아야 한다.
일단 url-pattern에 등록할 수 있는 URL은 다음과 같다.
"/"로 시작하고 "/*"로 끝나는 패턴은 path로 인식
"*."으로 시작하는 경우 확장자 매칭
"/"만 정의한 경우 디폴트 서블릿 의미
그 외의 경우 동치 매칭

위의 패턴 매칭에 존재하지는 않지만 /*는 요청 받는 모든 URL을 처리한다는 의미다. (아래와 같은 유형의 패턴 모두)
/user/list
/user/list.do
/user/userList.jsp
/img/test.png

결국 모든 요청을 DispatcherServlet에서 처리하겠다고 지정했으니 jsp에 대한 호출도 DispatcherServlet이 처리를 하려고 했던 것이다.
이로 인하여 jsp에 해당하는 mapping url을 찾을 수 없어 HTTP 404 오류가 발생하게 된 것이다.


[해결 방안]
그럼 해결 방법은 뭘까?
결론적으로 말하자면 url-pattern을 "/" 로 지정하면 된다.
해결 방법은 간단하지만 왜 이렇게 설정하면 잘 되는지 궁금해 졌다.

위에서 언급했지만 "/"로 정의한 경우 디폴트 서블릿을 의미한다고 하였다.

이게 뭔 말인가?
이 말의 의미를 찾아 보니 디폴트 서블릿은 서블릿 매핑 URL에 걸리지 않는 요청들을 처리한다고 한다.

또 의문이 생긴다.
난 서블릿 매핑을 "/" 와 같이 한 개만 만들었는데...결국 거르는 작업 없이 내가 만들어 놓은 서블릿 매핑에 모두 걸리는 것 아닌가?
그리고 디폴트 서블릿은 뭔가?

위의 의문들을 해결하기 위해서 이제 tomcat 서블릿 컨테이너에 대해서 언급할 때가 왔다.
${TOMCAT_HOME}/conf/web.xml 파일을 열어 보면 다음과 같이 세 개의 서블릿 매핑이 존재한다.
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>


<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>



<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>

*.jsp, *.jspx와 같은 url 패턴은 JspServlet이 처리하고, DefaultServlet은 spring Controller mapping과 jsp 패턴에 걸리지 않는 요청 들을 처리한다는 결론이 나온다.
즉, DefaultServlet은 png, jpg, js, html등 정적인 content를 처리한다는 말이다.

정리하면 
http://localhost:8080/user/userList.jsp <- JspServlet 요청 처리
http://localhost:8080/img/button.jpg <- DefaultServlet 요청 처리
http://localhost:8080/user/list <- DispatcherServlet 요청 처리


[스프링에서 정적 content 호출 시 404 에러 발생]
문제가 생겼다.
http://localhost:8080/user/list 는 정상적으로 동작하는데 
http://localhost:8080/img/button.jpg 로 호출 할 때 404 에러가 발생한다.

DispatcherServlet with name 'dispatcher' processing GET request for [/img/button.jpg]

로그를 보면 *.jpg 호출 시 DefaultServlet이 처리하는 것이 아닌 DispatcherServlet이 처리를 하게 되는 것을 볼 수 있다.
당연히 DispatcherServlet이 처리를 하게 되면 controller mapping URL이 존재하지 않으니 404 오류가 발생한다.

DispatcherServlet은 url-pattern을 "/" 와 같이 설정하게 되면서 tomcat의 server.xml에 정의되어 있는 url-pattern "/"을 무시하기 때문이다.
결국 DispatcherServlet url-pattern을 재정의하게 되어서 DefaultServlet은 더이상 동작할 수 없게 된 것이다.

스프링에서는 이를 해결하기 위해서 <mvc:default-servlet-handler /> 설정을 지원한다.

<mvc:default-servlet-handler /> 설정은 내부적으로 DefaultServletHttpRequestHandler가 담당하게 되고, 이 핸들러(컨트롤러)는 /**로 매핑되어 있다고 한다. (아래 토비님 글 참고)

DefaultServletHttpRequestHandler가 하는 역할은 DispatcherServlet이 처리 못하는 매핑 url을 DefaultServlet으로 넘기는 것이다.

드디어 정리 끝...
이라고 생각하면 오산.
<mvc:default-servlet-handler /> 설정을 추가하니 http://localhost:8080/user/list 호출 시 404에러가 발생한다.
로그를 확인해 보니 이상한 부분이 있었다. 
아래와 같이 SimpleUrlHandlerMapping에서 처리를 하고 있는 것이다.
SimpleUrlHandlerMapping - Matching patterns for request [/user/list] are [/**]

디폴트 핸들러 매핑인 DefaultAnnotationHandlerMapping이 처리해야 하는데 왜 SimpleUrlHandlerMapping이 처리를 하게 된 것일까?
이유는 두 개 이상의 핸들러 매핑이 등록되었을 경우에는 디폴트 전략이 무시되기 때문이다.

결국 <mvc:annotation-driven /> 설정을 통해 DefaultAnnotationHandlerMapping이 자동 등록되게 설정하였고, 이로써 모든 요청이 정상적으로 동작하였다.

단지 url-pattern만 정리하려고 했는데 꼬리에 꼬리를 물어서 스프링까지 설정까지 오게 되었다.

지금까지 url-pattern에 대해서 대수롭지 않게 생각했는데 까면 깔수록 공부해야 할 것들이 산더미이다.
그래도 오늘 하루 내가 뭔가를 배울 수 있었다는 것에 보람을 느낀다.

2016년 9월 19일 월요일

eclipse font control java, jsp, Console

All change
Preferences -> Fonts -> basic -> Text Editor

java
Preferences -> Fonts -> java

jsp
Preferences -> Fonts -> Structured Text Editors

Console(log)
Preferences -> Fonts ->Debug

xml
Preferences-> Appearance-> Colors and Fonts->Basic->Text Font

2016년 9월 1일 목요일

confirm IT Technology

1 16.09.01
System.out.println 보다는 Thread.dumpStack()을 활용하라.
최근 오픈 소스는 리플렉션을 적극 활용하거나 DI 등을 이용하여 코드 그 차제만으로는 어떤 클래스가 실행중에 바인딩 되는지 찾기 어려운 경우가 많습니다.  그리고 해당 로직이 어떤 경로를 거쳐 호출되는지 찾기 어려운 경우가 많습니다.이 경우 앞에서 설명한 디버거를 이용할 경우에도 어디에 breakpoint를 걸어야 할 지 애매한 경우가 많은데 이 경우 짐작이 가는 부분에 Thread.dumpStack() 코드를 추가하면 전체 호출 흐름 및 실제 바인딩 되는 클래스를 확인할 수 있습니다.