Framework/Spring

Spring MVC - 프레임워크 사용 전과 후 & @Controller

leegeonwoo 2024. 4. 15. 22:17
 

Spring MVC - Dispatcher Servlet과 View Resolver

Spring MVC의 구조를 깊이 있게 알기위해서는 기존의 MVC패턴의 전체 구조를 알고 이해하는 것이 도움이 될 것이다. Spring MVC의 범위는 매우크고 기능 또한 매우 많기때문에 기본 베이스 없이 Spring MV

lee-dev-log.tistory.com

 

 

MVC패턴과 변천사 - 핸들러와 핸들러어댑터 패턴 적용

이전 포스팅에서는 Model개념을 적용하여 request.serAttribute로 수행하던 파라미터 값을 가져오는 것을 Model을 통해 해결함으로 Servlet에 종속성을 제거하였다. 또 뷰 리졸버를 사용해서 각 컨트롤러

lee-dev-log.tistory.com

이전 글들에서 사용했던 간단한 회원관리 웹 프로그램을 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;
    }
}

 

MyView Class

public class MyView {
    private String viewPath;

    public MyView(String viewPath) {
        this.viewPath = viewPath;
    }

    public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }

    public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        modelToRequestAttribute(model, request);
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }

    private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
        model.forEach((key, value) -> request.setAttribute(key, value));
    }
}

 

 

컨트롤러에 대한 코드는 생략하도록하겠습니다

 

 


 

Spring MVC프레임워크를 적용한 코드

 

위에 수많은 코드들을 SpringMVC 프레임워크를 사용하면 아래에 코드로 축약할 수 있다.

 

이는 SpringMVC프레임워크에 render, viewResolver, handler등의 기능들이 모두 프레임워크에 구현되어 있기때문에 우리는 이 프레임워크를 사용하기만 하면된다.

 

뿐만아니라 어노테이션기능을 제공하기때문에 RequestMapping등을 이용하여 유연하고 가독성있는 코드로 HTTP 요청을 처리할 수 있으며 지금은 배우지 않았지만 Spring MVC자체적으로 여러가지 기능들을 제공한다.

 

또한 Spring의 특징을 이용하여 객체지향에 극대화된 애플리케이션 설계를 가능하게한다.

@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/new-form")
    public ModelAndView newForm(){
        return new ModelAndView("new-form");
    }

    @RequestMapping("/save")
    public ModelAndView save(HttpServletRequest request, HttpServletResponse response){
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        ModelAndView mv = new ModelAndView("save-result");
        mv.addObject("member", member);

        return mv;
    }

    @RequestMapping
    public ModelAndView members() {
        List<Member> members = memberRepository.findAll();

        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members", members);
        return mv;
    }
}

 

@Controller가 적용된 클래스는 기능별로 연관된 것 들끼리 메서드로 묶어서 정리할 수 있다.

 


위 정리한 내용들은 모두 인프런에서 김영한님의 강의를 듣고 학습한 내용을 스스로 정리한 것임을 밝힙니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1#

728x90