Spring MVC(1) - DispatcherServlet, Handler mapping, Controller
MVC pattern
mvc 구성요소
model : 비즈니스 로직 실행, 데이터 처리, 결과 데이터 생성
view : user interface 생성, 출력화면 생성
Controller : request 처리, request/resposne 데이터 전달
DispatcherServlet : 클라이언트의 요청을 받아 컨트롤러에 전달 / 컨트롤러의 처리 결과를 뷰에 전달하여 응답 생성
HandlerMapping : 클라이언트의 요청 URL을 어떤 컨트롤러가 처리할지를 결정
HandlerAdapter : 컨트롤러 객체를 호출하고 결과를 ModelAndView 타입 객체로 변환하여 DispatcherServlet에 반환
ModelAndView : 컨트롤러의 처리 결과 및 뷰 정보를 포함하는 객체
ViewResolver : 컨트롤러의 처리 결과를 출력할 뷰를 결정하고 뷰 객체를 생성
요청 처리 흐름 과정
Web App개발
기본설정
DispathcerSevlet, HandlerMapping, ViewResolver -> 스프링에서 제공되는 구현 클래스들 이용
Application에서 구현해야하는 요소
-Model component : 비즈니스 로직 및 데이터 처리 수행 (Services, Business, DAO 클래스 포함)
-클라이언트로부터 웹 요청을 처리하는 컨트롤러 클래스들
-입출력 화면을 생성하는 뷰 페이지 (JSP, Thymeleaf 등)
DispatcherServlet
xml설정
Client로부터 request를 전달받는 servlet 객체(front controller) : WEB-INF/web.xml파일에 dispatcher를 등록한다.
<web-app ... >
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
<!-- <url-pattern>*.do</url-pattern> -->
</servlet-mapping>
…
</web-app>
위와 같은 설정을 통해 모든 요청을 dispatcherServlet이 받게 만들 수 있고 url-pattern에 정의된 /으로 인해 jsp를 제외한 모든 리퀘스트를 처리한다.
dispatcher-servlet.xml
url을 맵핑하기 위한 컨트롤러가 되는 클래스와 연결
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example.test.web" /> <!-- controller 스캔 및 등록 -->
<mvc:annotation-driven /> <!-- hanndler mapping -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- view resolver -->
<!-- Example: a logical view name of 'showMessage' is mapped to '/WEB-INF/jsp/showMessage.jsp' -->
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
dispatcherservlet설정파일에는 핸들러맵핑, 컨트롤러, 뷰리졸버, 뷰 같은 빈을 설정하게 된다.
Java code기반으로 설정방법
위의 dispatcher-servlet.xml과 같은 설정임
@Configuration //@Controller가 적용된 클래스에 대해 bean생성
@EnableWebMvc //핸들러맵핑, 핸들러어댑터 등 필요한 bean생성
@ComponentScan(basePackageClasses={HelloController.class})
public class MvcConfig implements WebMvcConfigurer {
web.xml
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name> <!-- JavaConfig calss를 이용하는 컨테이너 -->
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.example.helloworld.config.MvcConfig <!-- javaConfig 클래스 -->
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
web.xml에는 한개 이상의 DispatcherServlet 설정이 가능하다.
각 DispatcherServlet 은 하나의 스프링 컨테이너를 생성한다.
ContextConfigLocation의 init-parameter를 이용해서 다른 설정파일들을 지정가능하다.
여러 계층으로 구성된 Web application은 각계층에 속하는 bean들을 서로 다른 설정 파일로 관리하는 것이 좋다.
이때 사용되는 것이 ContextLoaderListner이다.
web.xml에서 계층별로 나눈 것들을 모두 로드할 때 사용하며, root application을 생성하는 클래스이다.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/serviceContext.xml
/WEB-INF/persistenceContext.xml
classpath:common.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class> <!-- 위 param-value의 파일들을 참조해서 root container를 생성한다 -->
</listener>
<!-- web application context -->
<servlet>
<servlet-name>front</servlet-name> <!-- dispatcher servlet 1 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>rest</servlet-name> <!-- dispatcher servlet 2 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
위 설정에서 front와 rest는 각각 별도의 webapplicationcontext를 생성하므로 각각의 설정 파일에서 생성한 빈을 공유할 수 없다.
동시에 필요한 빈이 있는 경우 contextLoaderListner를 사용하여 설정할 수 있는데, contextConfigLocation의 밸류값 파일들을 이용해 루트 컨테이너가 생성되고 dispatcherServlet들은 이 root를 부모로 하는 자식 컨텍스트가 된다.
Hnadler Mapping
request URL이 web.xml의 <servlet-mapping>의 <url-patter>에 매칭되면 @RequestMapping에 지정된 경로들과 비교해서 controller 및 method가 선택된다.
servlet mapping 설정에 무관하게 전체 경로를 설정하려면
<bean id="handlerMapping"
class="o.s.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"
p:alwaysUseFullPath="true" />
controller에 맵핑되지 못한 요청은 모두 deafault servlet으로 전달하여 처리하려고 한다면 -servlet.xml에 다음과 같은 코드를 추가한다.
<mvc:default-servlet-handler />
java config를 이용한다면 configureDefaultServletHandling()메소드 구현이 필요하다.
@Configuration
@EnableWebMvc
@ComponentScan(...)
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configure) {
configurer.enable();
}
...
}
이 외에 properties 객체를 통한 맵핑이 필요할 땐 SimpleUrlHandlerMapping
Bean이름을 url 맵핑으로 이용할 땐 BeanNameUrlHandlerMapping
controller의 클래스 명을 이용한 맵핑엔 ConrollerClassNameHandlerMapping을 이용한다.
Controller 구현
Mapping
@Controller // 이 class의 객체를 controller로 사용
public class HelloController {
@RequestMapping("/hello.do") // "/hello.do" request를 처리할 method 정의
public String hello(Model model) {
model.addAttribute("greeting", "안녕하세요"); // model data
return “hello”; // view 이름
}
}
@RequestMapping을 클래스 명 앞에 붙이게 되면 각 메소드에 적용되는 URL의 기본 경로로 사용된다.
@Controller
@RequestMapping("/register") // 기본 경로 설정
public class RegisterController {
@RequestMapping("/member") // 예: "/register/member" request URL과 matching
public String handleMember() { ... }
@RequestMapping("/event/*.do") // 예: "/register/event/concert.do" 와 matching
public String handleEvent() { ... }
}
HTTP 전송 방식을 지정할 수 있다.
@RequestMapping(value="/newArticle.do", method=RequestMethod.GET)
public String form(){ ... } //get방식의 리퀘스트를 처리함
@RequestMapping(value="/newArticle.do", method=RequestMethod.POST)
public String submit(){ ... } //post방식의 리퀘스트를 처리함
@GetMapping == @RequestMapping(Method=RequestMethod.GET)
@PostMapping == @RequestMapping(Method=RequestMethod.POST)
parameter 추출
@Controller
public class RegisterController {
@GetMapping("/register/step2")
public String handleStep2(HttpServletRequest request, Model model) {
String agree = request.getParameter("agree"); // request parameter 추출
if (agree.equals("false")) { // 주의: agree가 null이면 오류 발생!
return "register/step1";
}
model.addAttribute("registerRequest", new RegisterRequest());
return "register/step2";
}
}
HttpServletRequest 객체를 이용해서 agree라는 이름의 파라미터를 추출할 수 있다.
적용된 method parameter 타입으로 자동변환이 수행되지만 변환이 불가능할 경우 오류가 발생
required 속성을 false로 설정하면 파라미터가 존재하지 않을 때 null값을 할당함
defaultValue 속성을 통해 default 값을 지정 가능
URL template
URL을 확인하기 전에 대체해야하는 매개변수를 포함하는 URL을 지정하는 방법
@Controller
@RequestMapping("/members")
public class MemberController {
@RequestMapping("/{memberId}/orders/{orderId}")
public String memberOrderDetail (@PathVariable String memberId,
@PathVariable("orderId") int orderNum, Model model) {
model.addAttribute("order", new OrderInfo(orderNum, memberId));
return "member/orderDetail";
}
}
URL 예 : /members/minsu/orders/12