[Spring-MVC] 스프링 MVC 구조 이해 (6)

조대훈·2024년 2월 26일

김영한 Spring MVC -1

목록 보기
6/9
post-thumbnail

목차
1. 스프링 MVC 전체 구조
2. 핸들러 매핑과 핸들러 어댑터
3. 뷰 리졸버
4. 스프링 MVC- 시작하기
5. 스프링 MVC- 컨트롤러 통합
6. 스프링 MVC- 실용적인 방식
7. 정리

실제 스프링 MVC 프레임워크 전체 구조

  • FrontController -> DispatcherServlet
  • handlerMappingMap-> HandlerMapping
  • MyHandlerAdapter -> HandlerAdapter
  • ModelView -> ModelAndView
  • viewResolver -> ViewResolver
  • MyView -> View

DispatcherServlet 구조

  • 스프링 MVC 도 컨트롤러 패턴으로 구현되어 있다.
  • 스프링 MVC의 프론트 컨트롤러가 바로 디스패처 서블릿이다.
  • 디스패처 서블릿이 스프링 MVC의 핵심이다

DispatcherServlet 서블릿 등록

  • DispatcherServlet 도 부모클래스에서 HttpServlet을 상속 받아 사용하고 서블릿으로 동작한다
  • 스프링 부트는 DispatcherServlet을 서블릿으로 자동으로 등록하면서 모든 경로 urlPatterns="/" 에 대해서 매핑한다.
    - 더 자세한 경로가 우선순위가 더 높아서, 기존에 등록한 서블릿도 함게 동작한다.

요청 흐름

  • 서블릿이 호출 되면 HttpServlet이 제공하는 service() 가 호출된다.
  • 스프링 MVC는 DispatcherServlet의 부모인 FrameworkServlet 에서 service()를 오버라이드 해두었다.
  • FrameworkServlet.servcie()를 시작으로 여러 메서드가 호출 되면서 DispatcherServlet.doDispatch()가 호출된다.

DispatchServlet.doDispatch()


protected void doDistpatch(HttpSerlvetReuqest request, HttpSerlvetResponse response) throws Exception{
	HttpServletRequest processRequest= request;
	HandlerExcutionChain mappedHandler= null;
	ModelAndView mv= null;
	// 1. 핸들러  조회
	mappedHandelr= getHandelr(processedRequest);
	if(mappedHandelr ==null){
		noHandlerfound(processedReqest, response);
		return;
		}

	// 2. 핸들러 어댑터 조회
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

	//3. 핸들러 어댑터 실행 -> 4.핸들러 어댑터를 통한 핸들러 실행 -> 5. ModelAndView 반환 
	mv= ha.handle(processedRequest, response, mappedHandler.getHandler());

	
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

	private void processDispatchResult(HttpServletRequest request, HttpSrvletResponse response, HandelrExecutionChain mappedHandler, ModelAndview mv, Exception exception) throws Exception{

	render(mv, reqeust, response);
	//렌더 호출 
	}

protected void render(ModelAndView mv, HttpServlet request, HttpServletResponse response) throws Exception{

	View view;
	String viewName = mv.getViewName();

	//6. 뷰 리졸버를 통해 뷰 찾기 7. View 반환
	view = resolveViewName(viewName, mv.getMdodelInternal(), locale, 
	request);

	view.render(mv.getModelInternal(),request,response);
}

}

동작 순서
1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다. (스프링은 URL 뿐만 아니라 HTTP의 Header에 있는 정보도 활용할 수 있다, 우리가 앞서 한 예시 보다 훨씬 고도화가 되어 있다. )
2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
3. 핸들러 어댑터 실행:
4. 핸들러 실행
5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환 후 반환하다.
6. viewResolver 호출 :
7. View 변환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
8. 뷰 렌더링 : 뷰를 통해 뷰를 렌더링한다.

인터페이스 살펴보기

  • 스프링 MVC의 강점은 DispatcherServlet 코드의 변경 없이, 원하는 기능을 변경하거나 확장할 수 있다는 점이다. 지금까지 설명한 대부분을 확장 가능한 인터페이스로 제공한다.
  • 이 인터페이스만 구현해서 DispatcherServlet 에 등록하면 본인만의 컨트롤러 또한 만들 수 있다.

주요 인터페이스 목록

  • 핸들러 매핑 org.springframework.web.servlet.HandlerMapping
    - 예시에서는 Map 으로 구현 했지만 인터페이스로 작동한다.
  • 핸들러 어댑터 `org.springframework.web.servlet.HandlerAdapter
  • 뷰 리졸버 org.springframework.web.servlet.ViewResolver
    - 스프링용 뷰 리졸버, 타임리프용 뷰 리졸버 다 각기 따로 제공한다.
  • org.springframework.web.servlet.View
    - JSP용 뷰, 타임리프용 뷰 다 나눠진다.

정리

스프링 MVC는 코드 분량도 많고, 복잡해서 내부 구조를 파악하는 것은 쉽지 않다. 사실 해당 기능을 직접 확장하거나 나만의 컨트롤러를 만드는 일은 없다. 대부분의 기능은 이미 구현이 다 되어 있다. 허나 핵심 동작 방식을 알아두어야 향후 문제가 발생시 어떤 부분에서 문제가 발생했는지를 쉽게 파악하고, 문제를 해결할 수 있다. 아울러 확장 포인트가 필요할 때 어떤 부분을 확장해야 할지 감을 잡을 수 있다. 실제 다른 컴포넌트를 제공하거나 기능을 확장하는 부분은 이후 강의에서 설명되어질 예정이다.

핸들러 매핑과 핸들러 어댑터

어노테이션이 나오기 전 Controller Interface로 컨트롤와 핸들러 매핑 어댑터를 이해 해보자.

Controller 인터페이스

과거 버전 스프링 컨트롤러
org.springfreamwork.web.servlet.mvc.Controller

	public interface Controller{

	ModelAndView handleRequest(HttpServletRequest request, HttpServeletResponse response) throw Exception;
	}

어노테이션 Controller와는 전혀 다른 개념의 Interface

OldController


@Component("/springmvc/old-controller")
public class OldController implements Controller{
	@Override  
	public ModelAndView
	handleRequest(HttpServletRequest request, 
	HttpServletResponse response) throws Exception {  
	    System.out.println("OldController.handleRequest");  
	    return null;  
	}
}

위 컨트롤러가 호출되는 순서

HandlerMapping(핸들러 매핑)

  • 핸들러 매핑에서 이 컨틀로러를 찾을 수 있어야 한다.
  • 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.

HandlerAdpater(핸들러 어댑터)

  • 핸들러 매핑을 통해서핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
  • Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.

스프링은 이미 필요한 매핑과 핸들러 어댑터를 대부분 구현 해놓았다. 개발자가ㅏ 직접 핸들러 매핑과 핸들러 애댑터를 만드는 일은 거의 없다.

스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터

HandlerMapping
0 = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping 에서 사용 
//통상 사용하는 메서드 

1= BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.
//이번 예시에서 쓰는 메서드 
HandlerAdapter
 0 = RequestMappingHandlerAdapter :어노테이션 기반의 @RequestMapping 에서 사용
 1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
 2 = SimpleControllerHandlerAdapter : Controller 인터페이스 (애노테이션X, 과거에 사용 ) 처리 

핸들러 매핑도, 핸들러 어댑터도 모두 순서대로 찾고 만약 없으면 다음 순서로 넘어간다.

  1. 핸들러 매핑으로 핸들러 조회
    1. HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다
    2. 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHanderMapping이 실행에 성공하고 핸들러인 OldController를 반환한다
  2. 핸들러 어댑터 조회
    1. HandlerAdaptersupports()를 순서대로 호출한다.
    2. SimpleControllerHandlerAdapterController 인터페이스를 지원하므로 대상이 된다.
  3. 핸들러 어댑터 실행
    1. 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter는 핸들러인 OldeController 를 내부에서 실행하고, 그 결과를 반환한다

정리 - OldController 핸들러 매핑, 어댑터

OldeController를 실행하며 사용된 객체
HandlerMapping=BeanNameUrlHanlderMapping
HandlerAdapter=SimpleControllerHandlerAdapter

HttpRequestHandler

Controller 인터페이스가 아닌 다른 핸들러를 써보는데 HttpRequestHandler (컨트롤러)는 서블릿과 가장 유사한 형태 의 핸들러다.

public interface HttpRequestHandler{

	void handleRequest(HttpServelrRequest request, HttpservletResponse response)throws ServeletException, IOExceoption
}

@Component("/springmvc/reuqest-handler")
pbulic class HttpRequestHandler implements
	HttpRequestHanlder{
	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response)throws IOE, ServletException{
	
	}

실행 흐름은 Old-Controller 와 똑같다
MyHttpRequestHandler를 사용하며 실행된 객체
HandlerMapping =BeanNameUrlHandlerMapping
HandlerAdpater=HttpRequstHandlerAdpater

@RequestMapping

가장 사용 우선수위가 높은 핸들러 매핑과 어댑터

  • RequestMappingHandlerMapping
  • RequestmappingHandlerAdapter
  • @RequestMapping의 앞글자를 따 만든 이름이며, 현재 가장 실무에서 제일 많이 쓰이는 애노테이션이다.

뷰 리졸버

OldController-view

조회할 수 있도록 변경 해본다

@Component("/srpingmvc/old-controller")
public class oldController implements Controller{
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, 
	HttpServletResponse response) throws Exception{
	System.out.println("OldController.handleRequest");
	return new ModelAndView("new-form");
	}
}

view를 사용할 수 있도록 다음 코드가 변경 되었다
return new ModelAndView("new-form)

http://localhost?:8080/springmvc/old-controller로 실행 하면 웹브라우저에 Whitelabel Error Page가 나오rㅗ 콘솔에 OldeController 만 출력이 된다.

application.properties에 아래 코드를 추가하게 되면 뷰가 제대로 작동하게 된다.

spring.mvc.view.prefix=/WEB_INF/view/
spring.mvc.vie.suffix=.jsp

어떻게 이렇게 작동하는 거지?

뷰 리졸버 -InternalResourceViewResolver

스프링 부트는 InternalResourceViewResolver 라는 뷰 리졸버를 자동으로 등록하는데, 이때 application.properties에 등록한 spring.mvc.view.prefix, spring.mvc.view.suffix 설정 정보를 사용해서 등록한다

권장하지 않는 방법으로 아래 방식으로 작동하기도 한다.
return new ModelAndView("/WEB-INF/view/new-form.jsp");

실행

폼은 나오지만 save 같은 기능은 제대로 작동하지 않는다

@Bean
InternalResourceViewResolver internalResourceViewResolver(){
	return new InternalResourceViewReslver("/WEB-INF/view/","jsp")
}

스프링 부트가 위 코드를 대신 기능해준다.

스프링 부트가 자동으로 등록하는 뷰 리졸버

실제로는 더 많다

1 = BeanNameViewResolver : 빈 이름으로 찾아서 반환
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰 반환 

우리가 작성한 예제에는 new-form 이라는 빈이 없으므로 JSP를 반환할 수 있는 뷰가 작동된다

1. 핸들러 어댑터 호출
핸들러 어댑터를 통해 new-form 이라는 논리 뷰 이름을 획득한다.

2. ViewResolver 호출
new-form 이라는 뷰 이름으로 viewResolver를 순서대로 호출한다.
BeanNameViewResolvernew-form 이라는 이름의 스프링 빈으로 등록된 뷰를 찾아야 하는데 없다. InternalResourceViewResolver 가 호출된다.

3. InternalResourceViewResolver
이 뷰 리졸버는 InternalResourceView 를 반환한다.

4. 뷰 - InternalResourceView
InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용한다.

5. view.render()
view.render() 가 호출되고 InternalResourceViewforward() 를 사용해서 JSP를 실행한다

스프링 MVC - 시작하기

스프링의 컨트롤러는 어노테이션 기반으로 작동하는데, 매우 유연하고 실용적이다.

@RequestMapping

@RequestMapping

  • RequestMappingHandlerMapping
  • RequestMappingHandlerAdapter

핸들러를 식별하고, 핸들러에 맞는 어답터를 실행하는 사용 빈도가 가장 높은 어노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터이다.
거의 모든 실무에서 이 핸들러와, 어답터를 사용한다.

@Controller  
public class SrpingMemberFormControllerV1 {  
  
        @RequestMapping("/springmvc/v1/members/new-form")  
        public ModelAndView process() {  
            return new ModelAndView("new-form");  
        }

@Controller

  • 자동으로 스프링 빈에 등록됨(@Component상속받아 컴포넌트 스캔의 대상)
  • 컴포넌트 스캔 등록 대상이 되고, RequestMappingHandlerMapping 의 대상이 되어 매핑 정보로 인식한다.

@RequestMapping

  • 요청 정보를 매핑. 해당 URL이 호출 되면 메서드가 실행된다.
  • 어차피 어노테이션으로 작동해 메서드의 이름은 임의로 지으면 된다.
    ModelAndView
  • 모델과 뷰 정보를 담아서 반환

스프링 MVC - 컨트롤러 통합

@RequestMapping 을 잘 보면 클래스 단위가 아니라 메서드 단위에 적용이 되었는데, 컨트롤러 클래스를 유연하게 하나로 통합할 수 있다.

통합을 넘어 조합도 할수 있다.

SpringMemberControllerV2

@Controller  
@RequestMapping("/springmvc/v2/members")  
public class SpringMemberControllerV2 {  
  
    private MemberRepository memberRepository = MemberRepository.getInstance();  
  
    @RequestMapping("/new-form")  
    public ModelAndView process() {  
        return new ModelAndView("new-form");  
    }  
  
    @RequestMapping("/save")  
    public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {  
        String username = request.getParameter("username");  
        // 이전에 해왔 듯 HttpServletRequest.getParametr()로 꺼내는 게 아니라 X        // 위의 정보는 프론트 컨트롤러에서 처리하고  
        // 파람 맵에다가 요청 파라메터 정보를 전부 담아 넘겨줄 것이다.  
        // 여기서는 단순히 꺼내서 쓰기만 하면 된다.  
        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 process(Map<String, String> paramMap) {  
        List<Member> members = memberRepository.findAll();  
  
        ModelAndView mv = new ModelAndView("members");  
        mv.addObject("members", members);  
  
        return mv;  
    }  
}

각기 흩어져 있던 메서드들이 전부 하나의 클래스로 통합되었다.
클래스 레벨에 RequestMapping 을 태그해 매핑을 조합해서 쓸수 있다.

스프링MVC 실용적인 방식

v3에서 ModelView를 개발자가 직접 생성해서 반환하

  1. Model 도입
  2. ViewName 직접 반환
  3. @RequestParam 사용
  4. @RequestMapping -> @GetMapping @PostMapping
@Controller  
@RequestMapping("/springmvc/v3/members")  
public class SrpingMemberControllerV3 {  
  
    private MemberRepository memberRepository = MemberRepository.getInstance();  
  
//    @RequestMapping(value = "/new-form",method = RequestMethod.GET)  
    @GetMapping("/new-form")  
    public String newForm() {  
        return "new-form";  
    }  
  
    @RequestMapping("/save")  
    public String save(  
            @RequestParam("username") String username,  
            @RequestParam("age") int age,  
            Model model) {  
//        String username = request.getParameter("username");  
//        int age = Integer.parseInt(request.getParameter("age"));  
  
        Member member = new Member(username, age);  
        memberRepository.save(member);  
  
        model.addAttribute("member", member);  
        return "save-result";  
    }  
  
//    @RequestMapping(method = RequestMethod.GET)  
    @GetMapping  
    public String members(Model model) {  
  
        List<Member> members = memberRepository.findAll();  
  
        model.addAttribute("members",members);  
        ModelAndView mv = new ModelAndView("members");  
        mv.addObject("members", members);  
  
        return "members";  
    }  
}
  • 임의로 만든 파람 맵 대신 Model을 파라메터로 넣을 수 있다.
  • RequestParamrequest.getParameter와 같다
  • @RequestMapping(value="new-form",method = RequestMethod.GET)@GetMapping("/new-form")으로 축약할 수 있다.

요약
1. 스프링 MVC 프레임워크 전체 구조

- 스프링 MVC는 DispatcherServlet을 중심으로 동작한다.
- DispatcherServlet은 핸들러 매핑, 핸들러 어댑터, 뷰 리졸버 등을 이용하여 요청을 처리한다.
- 주요 인터페이스로 `HandlerMapping`, `HandlerAdapter`, `ViewResolver`, `View`가 있다.
  1. 핸들러 매핑과 핸들러 어댑터

    • 스프링 MVC의 컨트롤러는 Controller 인터페이스를 통해 구현되었다.
    • HandlerMapping은 요청 URL과 핸들러를 매핑하고, HandlerAdapter는 핸들러를 실행한다.
    • 핸들러 매핑과 어댑터는 스프링 부트에서 자동으로 등록된다.
  2. 뷰 리졸버

    • InternalResourceViewResolver를 통해 JSP와 같은 뷰를 해석하고 처리한다.
    • 스프링 부트는 뷰 리졸버를 자동으로 등록하며, 설정 파일을 통해 뷰의 경로와 확장자를 지정할 수 있다.
  3. 스프링 MVC 시작하기

    • @Controller@RequestMapping을 이용하여 컨트롤러를 작성한다.
    • 컨트롤러 내에서 요청 처리와 뷰 반환을 담당하는 메서드를 작성한다.
  4. 스프링 MVC 컨트롤러 통합

    • 여러 메서드를 하나의 컨트롤러 클래스에 통합하여 코드를 관리한다.
    • 클래스 레벨에 @RequestMapping을 사용하여 요청을 조합한다.
  5. 스프링 MVC 실용적인 방식

    • Model을 사용하여 데이터를 전달하고, View의 이름을 직접 반환한다.
      • suffix,prefix 자동으로 추가
    • @RequestParam을 사용하여 요청 파라미터를 받아온다.
    • @GetMapping@PostMapping을 사용하여 GET과 POST 요청을 각각 처리한다.
profile
백엔드 개발자를 꿈꾸고 있습니다.

0개의 댓글