이전 글들에서 사용했던 간단한 회원관리 웹 프로그램을 Spring MVC패턴을 적용해보며 Spring MVC를 사용하면 코드가 얼마나 간결해지는지 살펴보며 Spring MVC프레임워크에 대해서도 알아보도록하자
@Controller
먼저 new-form에 대한 컨트롤러이다.
@Controller
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process(){
return new ModelAndView("new-form");
}
}
위 코드는 URL이 입력되면 즉, "/springmvc/v1/members/new-form" 요청이 클라이언트로부터 들어오면 process()메서드를 실행하여 결론적으로는 ModelAndView를 생성하고 "new-form"이라는 View를 반환하게 된다.
이 때 new-form응답은 단순히 JSP HTML을 클라이언트에게 응답하는 로직을 수행하기 때문에 Model을 사용하여 데이터를 입력하는 로직은 수행하지 않는다.
@Controller
Controller어노테이션은 이름 그대로 컨트롤러의 역할을 수행하며 사용자의 요청을 받고 비즈니스 로직대로 처리하고 사용자에게 응답을 반환하는 역할을 하게 됩니다. @Controller어노테이션이 붙게되면 Spring 컨테이너에 해당 클래스를 Spring Bean으로 등록하게 된다. (->내부적으로 @Component가 선언되어있어 Component Scan대상에 포함된다.) @Controller내부에 @Component가 선언되어있는 모습 주요 역할 1. 요청 매핑처리: @RequestMapping어노테이션과 함께 사용해 특정 URL요청을 해당 메소드와 매핑하여 이를 통해 HTTP요청에 따라 처리할 메서드를 지정
2. 요청 데이터 처리: 클라이언트로부터 전달받은 데이터(HTML FORM, 쿼리 파라미터, JSON등)를 @RequestParam, @PathVariable, @RequestBody등의 어노테이션을 사용하여 추출해내고 메서드 파라미터를 통해 전달가능
3. 비즈니스 로직호출: @Controller메서드 내에서 서비스 계층의 메서드를 호출하여 비즈니스 로직을 수행 (레거시 프로젝트에서는 Controller와 Service의 분리가 없기도함)
4. 모델 데이터 설정: 비즈니스 로직의 실행 결과를 모델에 추가하여 뷰에 전달. 이 때 사용되는 타입이 ModelAndView타입을 사용하여 모델에 데이터를 넣어 View로 전달합니다.
5. 뷰 반환: 처리 결과를 표시할 View의 이름을 반환 반환된 이름은 ViewResolver를 통해 실제 물리주소 View와 매핑되고 클라이언트에게 HTML을 응답
Spring MVC적용 전 후 비교
Spring MVC를 적용하기 전 코드
FrontController Class
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
private final Map<String, Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontControllerServletV5() {
initHandlerMappingMap();
initHandlerAdapters();
}
private void initHandlerMappingMap() {
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
//V4
handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object handler = getHandler(request);
if (handler == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyHandlerAdapter adapter = getHandlerAdapter(handler);
ModelView modelView = adapter.handle(request, response, handler);
String viewName = modelView.getViewName();
MyView view = viewResolver(viewName);
view.render(modelView.getModel(), request, response);
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if(adapter.supports(handler)){
return adapter;
}
}
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler = " + handler);
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}
ModelView Class
public class ModelView {
private String viewName; //JSP 주소
private Map<String, Object> model = new HashMap<>(); //<키, 밸류>
public ModelView(String viewName) {
this.viewName = viewName;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, Object> getModel() {
return model;
}
public void setModel(Map<String, Object> model) {
this.model = model;
}
}