dispatcher servlet 이란 front controller로 들어오는 요청에 대해 적절하게 판단하여 위임해 주는 역할을 하는 서블릿이다.
공통적인 작업을 처리 한 후에 해당 요청을 처리해야하는 컨트롤러에게 위임해준다.
장점
많은 서블릿들의 URL매핑을 위해 설정파일에 등록을 시켰어야 했지만, dispatcher servlet이 모든 요청을 처리해주고 공통 작업을 진행해줌으로써 등록없이 편리하게 사용가능하다. 컨트롤러에 구현이 되있다면 dispatcher servlet이 알아서 해준다.
- 디스패처 서블릿이 클라이언트의 요청을 받는다.
- 요청을 위임할 컨트롤러를 handler mapping이 찾는다.
- 요청을 컨트롤러로 위임해줄 handler adapter를 찾는다.
- handler adapter가 controller로 요청을 위임한다.
- 비즈니스 로직을 수행한다.
- 컨트롤러가 반환값을 받는다.
- handler adpater가 반환값을 처리한다.
- dispatcher servlet 클라이언트로 반환한다.
Handler Mapping
HandlerMapping의 구현체 중 하나인 RequestMappingHandlerMapping이 등록된 Bean에서 @Controller, @RequestMapping 어노테이션을 갖는 클래스를 찾고 반환한다. 이 과정에서 @PostMapping, @GetMapping 어노테이션을 사용하는 메서드를 핸들러로 등록한다.
Handler Adapter
dispatcher servlet은 Handler Adapter를 통해 어댑터 패턴을 이용하여 컨트롤러로 요청을 위임한다.
어댑터 패턴을 사용함으로써 컨트롤러의 구현 방식에 상관없이 요청을 위임할 수 있기에 사용하게 되었다.
위의 handleMapping에서 등록된 것을 토대로 RequestMappingHandlerAdapter가 이 핸들러를 처리할 수 있는 Adapter를 조회한다. 요청 방식에 따라 requestResolver가 호출된다. 역직렬화 과정이 이뤄진다.
Argument Resolver & 비즈니스 로직
파라메터는 Arugment Resolver를 통해 처리 된다. 이는 다음 게시글에서 자세히 설명 할 예정이다.
비즈니스 로직이 처리된다.
Response
처리된 비즈니스 로직의 결과가 ResponseEntity를 반환한다면 HttpMessageConverter가 MessageConverter를 사용해 응답 객체를 직렬화하여 반환한다. 그 외에 값은 ReturnValueHandler를 통해 반환된다.
만약 view이름이라면 viewresolver가 veiw를 반환하기 위한 준비를 한다.
handle adapter → dispatcherServlet으로 결과가 반환되었는데 만약 반환값이 view라면 viewResolver가 선택되어진다.
RequestMappingHandlerAdapter
그럼 실제로는 어떻게 작동하지 ?
- 디스패처 서블릿이 클라이언트의 요청인 post 이며 url이 /play를 받는다.
function submitForm() {
const names = document.getElementById("names").value;
const count = document.getElementById("count").value;
const data = {
names: names,
count: count
};
**fetch("/plays", {
method: "POST",
headers: {
"Content-Type": "application/json;charset=UTF-8"
},
body: JSON.stringify(data)
})**
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error("Network response was not ok.");
}
})
.then(data => {
const resultPosition = document.createElement("div");
for (let index in data.racingCars) {
const span = document.createElement("span");
span.innerHTML = `Name: ${data.racingCars[index].name}, Position: ${data.racingCars[index].position}<br>`;
resultPosition.appendChild(span);
}
document.getElementById("resultWinner").innerHTML = data.winners
document.getElementById("resultPosition").innerHTML = '';
document.getElementById("resultPosition").appendChild(resultPosition);
})
.catch(error => {
console.error("Error:", error);
});
}
2. 요청을 위임할 컨트롤러를 handler mapping이 찾는다.
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
3. 요청을 컨트롤러로 위임해줄 handler adapter를 찾는다.
**@PostMapping("/plays")**
public ResponseEntity<RacingGameResponseDto> playGame(final @RequestBody @Valid RacingGameRequestDto racingGameRequestDto) {
RacingGameResponseDto racingGameResponseDto = gameService.executeRacingGame(racingGameRequestDto);
return ResponseEntity.ok(racingGameResponseDto);
}
@PostMapping 내부
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
**@RequestMapping(method = RequestMethod.POST)**
public @interface PostMapping {~}
HandlerMapping의 구현체 중 하나인 RequestMappingHandlerMapping이 등록된 Bean에서 @Controller, @RequestMapping 어노테이션을 갖는 클래스를 찾고 반환한다. 이 과정에서 @PostMapping, 어노테이션을 사용하는 메서드를 핸들러로 등록한다.
4. handler adapter가 controller로 요청을 위임한다.
찾은 handler를 실행할 수 있는 adapter를 찾는다.
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
위의 handleMapping에서 등록된 것을 토대로 RequestMappingHandlerAdapter가 이 핸들러를 처리할 수 있는 Adapter를 조회한다.
요청 방식에 따라 requestResolver가 호출된다. 역직렬화 과정이 이뤄진다.
이 밑으로는 코드추적이 너무 어려웠따 ..
5. 비즈니스 로직을 수행한다.
6. 컨트롤러가 반환값을 받는다.
7. handler adpater가 반환값을 처리한다.
처리된 비즈니스 로직의 결과가 ResponseEntity를 반환한다면 HttpMessageConverter가 MessageConverter를 사용해 응답 객체를 직렬화하여 반환한다. 만약 view이름이라면 viewresolver가 veiw를 반환하기 위한 준비를 한다.
8. dispatcher servlet 클라이언트로 반환한다.
handle adapter → dispatcherServlet으로 결과가 반환되었는데 만약 반환값이 view라면 viewResolver가 선택되어진다. RacingGameResponseDto가 MappingJackson2HttpMessageConverter를 통하여 직렬화가 이뤄진다음 json형태로 변환된다.
정리
서블릿은 클라이언트로부터 받은 요청을 처리하고 그 값을 반환하는 기술이다.
이것을 사용하는 이유는 서블릿이 있음으로써 http메소드 분기처리도 해주고 url만 매핑해주면 우리가 작성한 비즈니스 로직이 실행되고 반환되는 환경을 만들어주기에 개발자가 편할 수 있다.
서블릿 자체는 요청과 서블릿이 1:1 매핑을 가지고 있다. 중복되는 코드도 많다. 그래서 이것을 해결하기 위해 스프링에서는 dispatcher Servlet을 사용해서 요청을 받을 때 매 요청마다 각각의 서블릿이 처리하는게 아닌 dispatcher servlet이 처리하게 한다. 모든 요청이 dispatcher servlet을 거쳐서 지나가기에 중복 코드가 줄고 관리가 쉽다.
dispatcher servlet역할이 엄청 많았다. 근데 이것을 분리한다.
각 요청에 맞는 컨트롤러를 매핑하여 정보를 보관하는 handler mapping, 요청이 들어오면 매핑 정보를 찾아서 해당 컨트롤러를 호출하는 handler adapter, 전달할 결과를 생성할 viewresolver로 나눠진다.
handler adapter를 사용함으로써 컨트롤러의 구현 방식에 상관없이 요청을 위임할 수 있기에 사용하게 되었다. 어노테이션을 쓸수도있고? controller 인터페이스 구현방식으로 할 수 도 있어서 이다. handler adapter들을 순회하며 맞는 adapter를 찾는다. 찾은 adapter를 통해 컨트롤러에서 요청에 해당하는 메소드를 호출한다.
인터셉터 처리된다.
요청에 해당 하는 메소드에 인자를 넣어주어야 하는데 이는 argument resolver가 진행한다. 이는 파라미터를 가공하는 역할을 한다. 인자를 추가,수정하거나 암호화→복호화 할 경우 코드를 드러내지 않고 진행할 수 있다. 없어도 컨트롤러에서 파라메터를 추가,수정할 수 있다. 하지만 중복코드가 양산된다. 이는 handler에 매개변수를 넣어줄 책임을 갖는다.
근데 httpbody에 값이 들어가 있을 경우 이를 파라메터에 넣기위해 messageconverter가 호출된다. 이에 맞는 객체를 만들고 handler를 호출한다.
그 후 결과를 ReturnValueHandler로 받는다. 결과 값이 httpbody에 담아야하면 messageconverter가 호출된다. 아닐경우 String이면 Viewresolver가 호출된다.
컨트롤러가 ModelandView객체를 리턴하면 ViewResolver를 사용하여 View객체를 구하고 이를 토대로 내용을 생성한다.
학습내용을 노션에 정리한 것을 올려보았습니다~
'코코코딩공부 > Spring' 카테고리의 다른 글
도메인은 id 값을 가져도 될까 ? (0) | 2023.05.28 |
---|---|
[Spring] Argument Resolver 내부 구경 하기 (0) | 2023.04.30 |
[Spring] Servlet 이란 ? (0) | 2023.04.30 |
[Spring] 어떤 객체를 빈으로 등록해야 할까 ? (1) | 2023.04.23 |
[Spring] Bean 이란 ? (0) | 2023.04.23 |