본문 바로가기

백앤드 개발/Java & Spring

[Spring boot] 스프링 MVC 구조 이해

1. DispatcherServlet 구조와 동작 순서

DispatcherServlet 은 스프링 웹 어플리케이션의 핵심 컴포넌트

클라이언트로부터 HTTP 요청을 받아 URL과 HTTP 매서드에 매핑되는 핸들러 조회 및 응답 반환

http 헤더 정보, 컨텐츠 타입 등의 정보를 활용하기도 함

 

Spring MVC 동작 순서

 

// org.springframework.web.servlet.DispatcherServlet

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;

// 1. 핸들러 조회: @RequestMapping 등의 어노테이션을 기반으로 처리할 컨트롤러 결정
mappedHandler = getHandler(processedRequest);

if (mappedHandler == null) {
	noHandlerFound(processedRequest, response);
	return;
}

// 2. 핸들러 어댑터 조회: 컨트롤러에 알맞은 처리 방식 제공
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 3. 핸들러 어댑터 실행
// 4. 핸들러 실행(@RequestMapping 지정 메서드 실행)
// 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException);
}

private void processDispatchResult(HttpServletRequest request,
HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView 
mv, Exception exception) throws Exception {
render(mv, request, response);
}

protected void render(ModelAndView mv, HttpServletRequest request,
HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();

// 6. 뷰 리졸버를 통해서 뷰 찾기:
// application.properties에서 설정한 spring.mvc.view.prefix, spring.mvc.view.suffix, 지정 메서드의 문자열 리턴값을 통한 경로 설정
// 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

// 8. 뷰 렌더링
view.render(mv.getModelInternal(), request, response);
}

2. 구현

// Memeber (DTO)

...
import
...

@Getter @Setter
public class Member {
    private Long id;
    private String username;
    private int age;

    public Member() {
    }

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

 

// MemberRepository (DAO)

...
import
...


public class MemberRepository {
    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    private static final MemberRepository instance = new MemberRepository();

    public static MemberRepository getInstance(){
        return instance;
    }

    // 싱글톤으로 만들때는 private으로 생성자를 생성해 아무나 생성하지 못하도록 막음
    private MemberRepository(){
    }

    public Member save(Member member){
        member.setId(++sequence);
        store.put(member.getId(),member);
        return member;
    }

    public Member findById(Long id){
        return store.get(id);
    }

    public List<Member> findAll(){
        // new 로 선언한 이유: store 값들을 보호하려고
        return new ArrayList<>(store.values());
    }

    public void clearStore(){
        store.clear();
    }
}

 

//application.properties

...
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
...

 

//SpringMemberController (핸들러/컨트롤러)

...
import
...

@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberController {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @GetMapping("/new-form")
    public String newForm(){
        return "new-form";
    }

    @PostMapping("/save")
    public String save(
            @RequestParam("username") String username,
            @RequestParam("age") int age,
            Model model){
        Member member = new Member(username, age);
        memberRepository.save(member);
        model.addAttribute("member", member);
        return "save-result";
    }

    @GetMapping
    public String Members(Model model){
        List<Member> members = memberRepository.findAll();
        model.addAttribute("members", members);
        return "members";
    }
}

3. 동작 단계별 상세 설명

1. 핸들러 조회와 우선순위

  • @RequestMapping 등으로 요청 URL과 매핑하는 핸들러(컨트롤러) 조회
  • 1. RequestMappingHandlerMapping: 먼저 어노테이션 기반의 컨트롤러인 @RequestMapping 조회
  • 2. BeanNameUrlHandlerMapping: 없다면 스프링 빈의 이름으로 핸들러 조회, @Component 사용

2. 어댑터 조회와 핸들러 어댑터 처리 우선순위

  • 핸들러 어댑터는 컨트롤러에 알맞은 처리 방식 제공
  • 1. RequestMappingHandlerAdapter : @RequestMapping 사용시, 어노테이션 기반 컨트롤러 어댑터 사용
  • 2. HttpRequestHandlerAdapter: HttpRequestHandler 상속시 사용되는 컨트롤러 어댑터
  • 3. SimpleControllerHandlerAdapter: 컨트롤러 인터페이스 상속시 사용 되는 컨트롤러 어댑터

3. handle(handler): 핸들러 어댑터 실행

4. 핸들러 호출

  • @RequestMapping 지정 메서드 실행

5. ModelAndView 반환

  • 모델과 뷰정보 담아서 반환

6. ViewResolver 호출

  • application.properties 에서 설정한 spring.mvc.view.prefix, spring.mvc.view.suffix, 지정 메서드의 문자열 리턴값을 통한 경로 설정
  • 물론 전체 경로로도 호출 가능함

7. View 반환

8. View 랜더링

4. 기타 코멘트

  • DTO: 특정 테이블 정보를 레코드 단위로 정의 해놓은 클래스
  • DAO: DB 접속, 명령 전송을 전담하는 클래스
  • DTO, DAO는 Django models.py 에서 하는 역할을 수행하는 듯 하다
  • 실무에선 99% 어노테이션 기반의 컨트롤러를 사용
  • mv.addObject("member", member): Model 데이터 추가