WebApplicationContext(DispatcherServlet)설정파일은 mvc-servlet.xml이다.
Controller단 관련 빈들을 등록해준다.
즉, mvc-servlet에서는 @Controller를 스캔하도록 하면된다.
물론 @Controller뿐만아니라 Controller 단에서 사용하는 여러 빈들을 등록하면된다.
근데 이런 빈들은 각각의 역할이 있기 때문에 단순히 빈 등록 방법만 하기보다는
이 DispatcherServlet이 어떤식으로 동작하는지를 알아야한다.
그 후 mvc-servlet.xml 내용을 살펴보자.
기본적으로 우리가 만드는 Controller는 다음과 같은 형태를 띈다.
@Controller
public class FreeController {
@RequestMapping("/free/freeList.wow")
public String freeList() {
return "free/freeList";
}
}
이렇게 했을 때 freeList.jsp가 실행되기까지의 과정은 다음과 같다.
Spring MVC 동작 원리
web.xml에서 DispathcerServlet을 Servlet으로 등록했다.
Spring 프로젝트에서는 이 DispatcherServlet이 FrontController 역할을 한다.
즉, 모든 요청을 이 DispatcherServlet이 처리한다.
저렇게 동작원리에 대한 그림이 있지만,
모두 DispatcherServlet의 doService메소드에서 실행되는 내용인 것이다.
위의 1~7번까지의 과정을 실행 후 RequestDispatcher.forward() 까지 실행하면 비로소 jsp화면이 나온다.
1. DispatcherSevlet 은 모든 연결을 담당하며, 웹 브라우저에서 요청이 들어오면
2. 그 요청을 처리하기 위해 HandlerMapping 객체에게 컨트롤러 검색을 요청한다.
HandlerMapping은 클라이언트의 요청 경로를 이용해서 이를 처리할 컨트롤러 객체를 찾아서
DispatcherServlet 에 리턴한다.
3. DispatcherServlet 은 @Controller 어노테이션을 이용해서 구현한 컨트롤러 클래스와
기타 응답처리 클래스를 처리하기 위해 HandlerAdapter 객체에게 요청 처리를 위임한다.
4. HandlerAdapter 객체는 컨트롤러의 알맞은 메소드를 호출해서 요청을 처리하고,
5. 컨트롤러는 여러가지 기능을 수행 한 후 어떤 결과 값을 리턴한다.
6. HandlerAdapter는 컨트롤러로부터 받은 결과값이 어떤 타입이든지
ModelAndView 객체에 담아서 DispatcherServlet에 리턴한다.
7. ModelAndView 객체를 반환 받은 DispatcherServlet 은 ViewResolver 객체를
이용해서 결과를 보여줄 뷰를 검색한다.
ViewResolver 객체는 ModelAndView 객체에 담긴 뷰 이름을
이용해서 View 객체를 찾거나 생성해서 리턴한다.
8. DispatcherServlet 은 ViewResolver 가 리턴한 View 객체에게 응답 결과 생성을 요청한다.
9. JSP를 사용하는 경우, View 객체는 JSP를 실행함으로서 브라우저에게
전송할 응답 결과를 생성한다.(forward)
ModelAndView 의 Model 객체에 담겨 있는 데이터가 응답 결과에 필요하면
Model 에서 데이터를 꺼내 JSP에서 사용할 수 있다.
DispatcherServlet은 요청을 처리할 때 여러 객체들을 사용하는데 이 객체들은 SpringContainer(WebApplicationContext)에 등록된 빈이다.
DispatcherServlet은 SpringContainer를 서버가 켜질 때 만드는데, web.xml에서 정의한 대로
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/spring/mvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
mvc-servlet.xml을 설정파일로 해서 SpringContainer를 만든다.
즉 mvc-servlet.xml에 Handlermapping,HandlerAdapter,
컨트롤러(@Controller붙은 클래스), ViewResolver 등에 관한
빈 등록 태그가 있어야 한다는 얘기이다.
mvc-servlet.xml
(우선 namespace에서 관련 내용을 체크해줘야합니다.
근데 spring프로젝트만들면 기본으로 돼어있음)
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!--1.annotation-driven태그 : 이 태그만 있으면 HandlerMapping을 비롯한 여러가지 mvc에 필요한 설정들이 자동으로 세팅 (기본값)
annotation-driven태그 안에 내용을 하나하나 다 써도 됩니다. 80줄정도-->
<annotation-driven />
<!--2. resources 태그 : mapping은 url요청, location은 webapp에서의 위치 -->
<!-- 귀찮으면 폴더이름도 resources, mapping도 resources하면 됨 -->
<resources mapping="/resources/**" location="/resources/" />
<!--3. viewResolver 태그 : ViewName을 찾는데 사용됨 -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!--4. component-scan태그 : @Service, @DAO를 제외한 @Controller만 빈으로 등록한다.
component-scan은 기본적으로 use-default-filter가 트루 일 때
@Controller,@Service,@Respoitory,@Component 등이 붙은 걸 빈으로 다 등록 (기본값도 true)
DS은 Presentation,Controller단 빈만 등록 -> @Controller만 가지도록
-->
<context:component-scan base-package="com.study" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans:beans>
WebApplicationContext(DispatcherServlet)설정파일은 mvc-servlet.xml이다.
Controller단 관련 빈들을 등록해준다.
이 글 맨위에 있던 내용인데,
DispatcherServlet이 동작하는데 필요한 객체들에 관한 빈이 등록된다.
(Service, Dao단의 빈들은 등록하지 않는다.)
<annotation-driven/>
일단 전체적으로 살펴보면 ViewResolver와 @컨트롤러 빈에 관한 내용이 보이는데
HandlerMapping,HandlerAdapter에 관한 빈은 보이지 않는다.
이 2개의 객체는 <annotation-driven/> 태그에 정의 되어 있다.
물론 이 2개의 객체 말고도 여러가지 기능이 포함되어있다.
기본적으로 <annotation-driven/> 태그에서 HandlerMapping,HandlerAdapter 구현체로 등록되는 빈은
RequestMappingHandlerMapping, RequestmappingHandlerAdapter이다 .
또 SpringMVC 동작원리 8번에서 JSP로 포워딩하는 View의 구현체로
등록되는 빈은 InternalResourceView이다.
<resources>
Spring MVC 동작원리는 .jsp같은 동적콘텐츠에 해당하는 것이다.
일반적으로 정적컨텐츠인 css,js 등은 WebServer를 따로 설치해서 처리하는게 좋지만,
WebServer를 따로 설치하지않았을 경우 DispatcherServlet이 정적컨텐츠를 처리할 수도 있다.
이 정적컨텐츠를 처리하도록 하는 태그가 <resources>태그이다.
mapping과 location 속성이 있는데 별로 어렵지 않다. mapping은 url요청에 대한 값을 적는다.
location은 프로젝트에서 파일 위치를 적는다.
만약 다음과 같이 설정되어있다면
<resources mapping="/url/*" location="/bootjs/" />
url 요청 : <script src="<%=request.getContextPath() %>/url/bootstrap-3.3.2/js/bootstrap.js"></script>이고
와 같다.
하지만 실제 코드에서는 헷갈리지 않게 그냥 mapping과 location 모두 resouces라는 값으로 통일했다.
<resources mapping="/resources/*" location="/resources/" />
url 요청 : <script src="<%=request.getContextPath() %>/resources/bootstrap-3.3.2/js/bootstrap.js"></script>이고
<bean class= InternalResourceViewResolver>
Spring 동작원리 7번부분에서 동작하는 빈이다. preFix, Suffix를 보면 다음과 같이 되어있다.
- prefix: /WEB-INF/views/
- suffix : .jsp
이렇게 되어있다. 만약 컨트롤러에서 "free/freeList"를 return 했다면
DispatcherServlet은 "/WEB-INF/views/free/freeList.jsp" 의 view 값을 얻는다.
나중에 이 jsp로 forwarding 하게 돼서 해당 jsp 화면이 나오게된다.
<context:component:scan>
이전글에서 설명했다. 단지 이전글에서는 ContextLoaderListenr가
@Controller를 제외한 @을 빈으로 등록하도록 했고
여기서 DispatcherServlet은 @Controller만 빈으로 등록하도록 했다.
DispatcherServlet 동작 코드로 살펴보기
localhost:8080/study4/free/freeList.wow로 요청했을 때
@Controller
public class FreeController {
@RequestMapping("/free/freeList.wow")
public String freeList() {
return "free/freeList";
}
}
이 실행되고 freeList.jsp에 의한 화면이 나오는 과정까지의 DispatcherServlet 코드동작을 살펴보겠다.
DispatcherServlet의 doService메소드. 클라이언트로부터 요청이오면 실행되는 메소드
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//생략
try {
doDispatch(request, response);
}
//생략
}
doDispatch메소드. doSerivce에서 호출한 메소드로서
DispatcherServlet동작에 관한 코드는 이 메소드안에 있음.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// multipartResolver에 의해 resolve 처리 (request를 파싱하여 file이 있다면 set)
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 1.현재 요청에 알맞은 핸들러를 가져온다.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2.위에서 가져온 핸들러를 실행(invoke) 시킬 수 있는 핸들러 아답터를 가져온다.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// last-modified header 수정. ( last-modified 헤더값으로 브라우저나 proxy 서버에서 캐시를 이용할 수 있음)
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 적용할 interceptor가 있다면, preHandle 적용
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//3.실제 컨트롤러 핸들러(메소드) 실행
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//비동기 처리라면, 종료. WebAsyncManager에서 별도의 스레드가 처리하게 된다.
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//View 에 대한 설정이 없다면, RequestToViewNameTranslator 전략 빈에 의해 view name을 설정한다.
applyDefaultViewName(processedRequest, mv);
//interceptor postHandle 실행
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
// 예외 처리 생략
}
//4.핸들러 실행 결과 ModelAndView를 적절한 response 형태로 처리한다.
// viewResolver를 이용해 view를 선택하고, forwarding한다
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
//아래 예외처리 부분 생략
인터셉터나 비동기처리에 관한 내용은 냅두고 일단 어떻게
우리가 만든 @Controller 의 @RequestMapping이
실행되는지만 살펴보자. 또 최종적으로 jsp 화면이 나오는지 까지도 알아보자.
1.mappedHandler=getHandler()의 getHandler // 현재 요청에 알맞은 핸들러를 가져온다.
우리는 @Controller를 붙인 클래스를 사용하지만,
DispatcherServlet(Spring) 입장에서는 이 요청을 처리하는게
@Controller를 붙인 클래스여야만 하는 건 아니다.
Controller 인터페이스를 구현한 클래스도, 또 다른 방법으로 만든 클래스도 처리할 수 있어야 한다.
이 모든것들을 통틀어서 핸들러라 한다.
근데 사실 우리는 @Controller붙여서 클래스만드는 방법만 사용하기때문에
핸들러= @Contrller 붙인 클래스(컨트롤러, MVC의 C가 아니다) 라고 인식해도 좋다.
코드를 보자.
DispatcherServlet.getHandler()
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
for문 자체가 중요한 건 아니다. HandlerMapping의 구현체를 전부 반복하겠단 얘기다.
특별히 따로 설정을 해주지 않는다면 기본적으로 반복문을 돌면서
handler !=null 이 아니게 되는 경우는
RequestMappingHandlerMapping 인 경우다.
RequestMappingHanlderMapping 객체 타입의 hm.getHandler(request) 메소드를 보자.
getHandler(request)메소드는 ReuqestMappingHandlerMapping의 부모 추상클래스인 AbstractHandlerMapping에 정의 되어있다. 코드를 보자.
AbstractHandlerMapping.getHandler()
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
// @Controller붙은 빈들중에서 /free/freeList.wow 에 해당하는 메소드가 있나 검사.
// 있으면 handlerMethod.createWithResolvedBean()를 통해 freeController 빈 객체 리턴
//type은 object
//생략, 없을 때 처리.
// Bean name or resolved handler?, 진짜 빈이 있는지 검사.
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
//이 메소드에서는 interCeptor에 대한 처리를 한다.
//큰 문제가 없다면 executionChain은 handler와 똑같이 freeController 빈 객체
// tpye은 object에서 HandlerExecutionChain으로 변경
//생략 cors 에 대한 처리
return executionChain;
}
사실 getHandlerInternal이나 getHandlerExecutionChain메소드도 쓰고 싶지만 그러면 너무 복잡하니...
결국 주석으로 대충 메소드 설명만 했다.
결국 @ReuqestMapping("/free/freeList.wow)가 있으므로 doDispatch메소드의
mappedHandler는 우리가 @Controller를 붙인 freeController 빈 객체가 된다.
2. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mappedHandler.getHandler()를 먼저 보자. mappedHandler는 HandlerExecutionChain 타입이다.
이걸 단순히 Object 타입으로 형변한 해주는 거라 생각하면 된다.
어쨋든 return 값은 freeController 빈 객체이다.
getHandlerAdapter를 보자.
DispatcherServlet.getHandlerAdapter()
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
역시 for문은 의미가 없다. 여기도 마찬가지로 특별히 설정을 안해주면 기본적으로
RequestMappingHandlerAdapter 일 때 if(ha.supports(handler)){return ha; } 가 실행된다.
물론 return 되는 타입은 HandlerAdapter이다.
3. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
실제 컨트롤러(핸들러) 메소드가 실행된다 라고 했는데, 이 실제 컨트롤러 메소드가 바로
개발자가 작성한 @RequestMapping이 붙어있는 메소드이다.
@RequestMapping("/free/freeList.wow")
public String freeList() {
return "free/freeList";
}
우리가 작성한 freeList()메소드가 실행되는 부분까지 가기위해서 ha.handle()에서 메소드를 호출하고
또 다른 객체의 메소드를 호출하고를 반복한다.
우선 ha.handle(processedRequest, response, mappedHandler.getHandler()); 에서
AbstractHandlerMethodAdapter.handle()
-> RequestMappingHandlerAdapter.handleInternal()
->RequestMappingHandlerAdapter.invokeHandlerMethod()
->ServletInvocableHandlerMethod.invokeAndHandle()
->InvocableHandlerMethod.invokeForRequest()
->InvocableHandlerMethod.doInvoke()에서 java Reflection을 이용해 Invoke()를 한다.
(개발자가 만든 메소드 실행)
그리고 메소드 실행(invoke())하고 return 받은 값을 가지고 modelAndView를 return 한다.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());에서는
개발자가만든 메소드가 실행되고 return받은 값 가지고 ModelAndView를 만든다.
즉, HandlerAdapter가 하는 일은 개발자가 만든 메소드 호출, 그 메소드의 return Type이 뭐든
ModelAndView로 만들어서 return 해주는 역할을 한다.
이 때 각 메소드를 실행할 때 마다 여러 필요한 파라미터들을 넘긴다.
여기서 잠깐 RequestMappingHandlerAdapter.invokeHandlerMethod() 를 보자
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//생략
invocableMethod.invokeAndHandle(webRequest, mavContainer);
//생략
return getModelAndView(mavContainer, modelFactory, webRequest);
}
//생략
}
위의 과정은 invocableMethod.invokeAndHandle() 의 과정이고
마지막 return getModelAndView()를 보자.
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
//생략
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
//생략
return mav;
}
ModelAndView를 만들기 전에 이미 여러과정을 거쳐서 데이터가 담긴 Model과 view이름 등을 가지고
ModelAndView를 만들고 return한다. 여기까지 했을때에 비로소
MVC동작원리 6번과정이 끝이다.
예시에서는 freeList() 메소드 실행하고 "free/freeList" 라는 String을 return 받는다.
그리고 String을 ModelAndView에 담는다.
사용자가 freeList에서 model에 담은 데이터도 ModelAndView에 담긴다.
4.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
viewResolver를 이용해 view를 선택하고, forwarding한다
DispatcherServlet.processDispatcherResult()
-> DispatcherServlet.render()
DispatcherServlet.render()
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
//생략
View view;
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
//생략
view.render(mv.getModelInternal(), request, response);
//생략
}
view= resolveViewName 에서는 viewResolver(구현체 기본 InternalResourceView)를 이용해 prefix,sufficx를 붙여서 /WEB-INF/views/free/freeList.jsp에 해당하는 View를 return 해준다.
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
여기서도 역시 for문은 중요하지 않다. mvc-servlet.xml에서 선택한 viewResolver 구현체를 이용해
view이름을 세팅해준다.
AbstractingCachingViewResolver.resolveViewName()
->UrlBasedViewResolver.createView()에서 실제로 view를 만들어 return 한다.
mvc-servlet.xml에서 선택한 InternalResourceView는 UrlBasedViewResolver를 상속받는다.
그리고 그 view를 이용해 render메소드를 호출한다. (view를 상속받은 AbstractView.render()가 실행된다)
->AbstractView.render()
->InternalResourceView.renderMergedOutputModel() 가 실행된다.
InternalResourceView.renderMergedOutputModel() 코드를 보자.
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);
}
}
정상적으로 잘 실행됐다면 String dispatcherPath = prepareForRendering(request, response); 에서
dispatcherPath의 값은 "/WEB-INF/views/free/freeList.jsp" 이다
그리고 RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); 가 있다.
마직막에 rd.forward(request,response)를 이용해 실제 /WEB-INF/views/free/freeList.jsp의 jsp로 forwarding한다.
※ 여기서 잠깐. HttpServlet의 요청을 처리하는 메소드는 doService가 아니라
service,doPost,doGet 등등인데.???
DispatcherServlet의 부모 추상클래스이자 HttpServlet의 자식인 FrameworkServlet의 메소드를 보자.
HttpServlet의 doGet,doPost 등의 메소드를 재정의 하면서 모두 processRequest 메소드를 실행한다.
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate POST requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
//그외 doPut,doDelete,doOptions, service들도 processRequest를 호출한다.
processRequest 메소드. doService 메소드를 호출한다.
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//기타기능
doService(request, response);
//이하 생략
}
doService메소드 .
/**
* Subclasses must implement this method to do the work of request handling,
* receiving a centralized callback for GET, POST, PUT and DELETE.
* <p>The contract is essentially the same as that for the commonly overridden
* {@code doGet} or {@code doPost} methods of HttpServlet.
* <p>This class intercepts calls to ensure that exception handling and
* event publication takes place.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
* @see javax.servlet.http.HttpServlet#doGet
* @see javax.servlet.http.HttpServlet#doPost
*/
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
추상메소드로 선언되어있다. 설명을 읽어보면 'GET,POST,PUT,DELETE 어떤 방식의 Http요청이든
자식메소드에서 재정의된 doService가 처리한다' 는 내용이다.
원래 HttpServlet에서는 GET방식이면 doGet, POST방식이면 doPost 메소드가 실행됐지만, DispatcherServlet에서는 어떤방식의 요청이든 요청을 받으면
doService메소드가 실행된다.
※ 여기서 잠깐, 따로 설정하지 않았을 때 기본적으로
HandlerMapping의 구현체는 RequestMappingHandlerMapping,
HandlerAdapter의 구현체는 RequestMappingHandlerAdapter라 했다.
mvc-servlet.xml의 <annotation-driven /> 태그를 사용했다. 이 말이 따로 설정하지 않았다는 말이다.
RequestMappingHandlerMapping, RequestMappingHandlerAdapter 대신 다른 구현체를 사용하고 싶다면
<annotation-driven/> 태그대신 직접 HandlerMapping. HandelerAdapter 의 구현체를 빈 등록해주면 된다.
물론 <annotation-driven/> 에는 이 2개의 관한 설정만 있지는 않다.
그 외에도 여러가지 설정을 기본적으로 해준다.
참고로 기본 설정 값은 Spring jar파일을 따운받을 때 DispatcherServlet.properties파일안에 명시돼어있다.
DispatcherServlet.properties
#DispatcherServlet.properties
...
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
...