[Spring MVC] Dispatcher Servlet이란

rvlwldev·2023년 2월 22일
0

Spring

목록 보기
1/8

Spring MVC에서 Dispatcher Servlet은 스프링에서 제공하는 핵심 기능 중 하나이며
웹 애플리케이션의 모든 HTTP 요청을 받아서 요청을 처리하는 컨트롤러로 전달하는 역할을 한다.

즉, Dispatcher Servlet은 클라이언트로부터 요청을 받아서 해당 요청을 처리하는 컨트롤러를 찾아서 그 컨트롤러에게 요청을 전달하고, 컨트롤러가 처리한 결과를 클라이언트에게 반환하는 매개체 역할을 한다.

Spring MVC를 이해하기 위해서는 꼭 알아야되는 클래스

Dispatcher Servlet이 등장하기 전

스프링 초기버전에는 Dispatcher Servlet은 없었다.
대신 Framework Servlet이라는 객체가 존재했었는데 이 객체는 모든 HTTP요청마다 Servlet API의 메서드를 호출해 새로운 요청과 응답객체를 생성하고 처리한 뒤 클라이언트로 반환했다.

이 로직이 매 요청마다 반복되며 컨트롤러와 뷰의 결합도 또한 높아서 유연성도 떨어졌다.
(뷰를 변경하려면 컨트롤러의 코드도 변경해야했다.)

또한 매 요청마다 단일 인스턴스로 존재했기 때문에 여러 클라이언트가 동시에 요청을 보내면 충돌할 위험도 있었다.

이후 Framework Servlet의 단점을 보완한 Dispatcher Servlet이 등장하게 된다.
단점을 보완했을뿐 아니라 이후 핸들러 맵핑, 비동기 요청 처리, 웹 소켓 처리 등을 지원하게 되면서 Spring MVC의 중요한 기능 중 하나로 자리를 잡는다.

Dispatcher Servlet의 동작방식

이미지 출처

  • 요청이 들어오면, DispatcherServlet은 이 요청을 처리할 핸들러를 찾기 위해 HandlerMapping 인터페이스를 구현한 객체에게 요청을 전달하고 HandlerMapping 구현 객체는 해당 요청을 처리할 핸들러를 찾아 DispatcherServlet에게 반환 ( 1 -> 2)
  • DispatcherServlet은 핸들러를 처리하기 위해 HandlerAdapter 인터페이스를 구현한 객체에게 핸들러를 전달 (3)
  • HandlerAdapter 구현 객체는 핸들러를 실행(이미지의 RestController), 결과를 반환 ( 4 -> 5 -> 6)

  • DispatcherServlet은 클라이언트에게 결과를 반환하거나 (7 -> 8)
    ModelAndView 이나 String 등의 결과를 반환받았다면(@Controller) 객체를 처리하기 위해 ViewResolver 인터페이스를 구현한 객체에게 전달

  • ViewResolver 구현 객체는 ModelAndView 등의 객체를 처리할 뷰를 찾아서 DispatcherServlet에게 반환하고, 곧 클라이언트에게 뷰를 반환

  • (선택) HandlerInterceptor를 구현하여 (2)의 단계에서 공통적인 전처리(preHandle)
    또는 (7)의 단계에서 후처리(postHandle) 가능

  • (선택) HandlerExceptionResolver를 컨트롤러에서 발생한 예외를 처리할 수 있다.

Dispatcher Servlet의 장점

다양한 기능을 기본적으로 지원해주기때문에 오늘날의 웹개발자는 Dispatcher Servlet을 통해 비교적 간단하게 웹 어플리케이션을 개발할 수 있다.

1. Annotation 지원

이전에는 web.xml과 같은 설정파일에 Dispatcher Servlet에 필요한 모든 빈을 선언하고 값을 직접 설정해줘야 했기 때문에 가독성도 떨어지고 매우 복잡해질 수 있었다.

예전 버전의 스프링이 아니라면 (Spring Framework 2.5 이전) 지금은 간단하게 @Controller같은 Annotation을 통해 간단하게 설정이 가능하기 때문에 유지보수와 가독성이 좋아졌다.

2. 유연한 구조

위 동작방식에서 HandlerInterceptor를 구현하여 컨트롤러에 맵핑되기전 공통적으로 전처리를 할 수 있다는 점부터 HandlerMapping, HandlerAdapter, ViewResolver모두 필요에 따라 커스터마이징도 가능하다.

(HandlerInterceptor 구현 예시)

@Component
public class CustomHandlerInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request,
    						 HttpServletResponse response, 
	    					 Object handler) {
	String auth_header = request.getHeader("Authentication");
    
    // 헤더의 Authentication 값 검증 로직 ...
    
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
	@Autowired
    CustomHandlerInterceptor customHandlerInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(customHandlerInterceptor)
                .addPathPatterns("/domain1/**")
                .excludePathPatterns("/domain2/**");
    }
}

위 코드를 구현하면 Dispatcher Servlet이 HandlerMapping 전에
간단하게 개발자의 로직을 실행한다.

(HandlerMapping 커스텀 예시)

public class CustomHandlerMapping implements HandlerMapping {

    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 요청된 URL에 맞는 컨트롤러, 인터셉터를 반환하는 로직 커스텀
    }
}

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("/domain1", HandlerTypePredicate.forAnnotation(DomainController.class));
    }

    @Override
    public void configureHandlerMapping(HandlerMappingRegistry registry) {
        registry.addMapping(new CustomHandlerMapping());
    }
}

3. 모듈화 가능

Dispatcher Servlet은 웹과 관련된 모든 기능을 모듈화하여 처리할 수 있다.

예를 들어 Dispatcher Servlet를 상속받은 클래스를 구현하여 패키지별로 다른 Dispatcher Servlet을 구현할 수 있다.
(당연히 각각 인터셉터, View Resolver 등을 가지며 따로 등록도 따로 가능하다.)

때문에 도메인별로 기능을 분리하거나, 독립적인 개발 및 배포가 가능하여 애플리케이션 전체의 유지보수성과 확장성을 높일 수 있다.

(예시)

  • application
    • Domain1
      • Controller
      • Service
      • Dao
    • Domain2
      • Controller
      • Service
      • Dao
    • Global
      • Controller
      • Service
      • Dao

위와 같은 구조가 있다고 가정했을때,

public class Module1DispatcherServlet extends DispatcherServlet {
    @Override
    protected WebApplicationContext createWebApplicationContext(
            WebApplicationContext parent) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.setParent(parent);
        context.register(Domain1Config.class);
        
        return context;
    }
}
@Configuration
@ComponentScan(basePackages = "com.example.application.Domain1")
public class Domain1Config {
  
	@Bean
	public Domain1Service domain1Service() {
		return new Domain1ServiceImpl();
	}
  
	...

}

간단하게 @ComponentScan 어노테이션을 통해 모듈화의 범위를 지정하여 해당 모듈에서만 사용하는 Bean을 등록할 수 있다.

(이전의 Framework Servlet도 모듈화가 가능했으나 지금처럼 유연하지 못했고 복잡했으며 매우 제한적이였다고 한다...)

4. 자동 데이터 바인딩

Dispatcher Servlet는 클라이언트의 요청 데이터를 자바 객체의 필드와 데이터를 매핑해준다.
때문에 개발자는 클라이언트의 데이터를 자바객체로 변환하는 로직에 대해 생각할 필요가 없다.

보통 데이터가 맵핑되는 객체를 DTO(Data Transfer Object)라고 하며 보통 파라미터에 @RequestBody 어노테이션을 사용한다.


Dispatcher Servlet을 사용하면 대부분의 공통적인 기능을 지원하며 목적에 맞는 모듈화와 커스터마이징을 편하게 적용할 수 있지만 위 장점을 너무 남발하게 되면 오버헤드와 같은 예상치 못한 문제점이 발생하거나 잦은 상속과 인터페이스의 구현으로 가독성을 오히려 해칠 수도 있다.

가장 핵심적인 클래스 중 하나인 만큼 달달 외우지는 않더라도 이해하고 넘어가는 편이 역시 좋다고 생각한다....

profile
ㅇ0ㅇ

0개의 댓글