티스토리 뷰
웹 어플케이션 프레임워크의 구조에 대해서 조금이라도 공부해본 적이 있다면 Front Controller 패턴에 대해 들어본 적이 있을 것이다. 프론트 컨트롤러는 간단히 "웹 어플리케이션에서의 모든 요청을 앞단에서 처리하는 핸들러"라고 정의할 수 있는데, 이러한 구조는 코드 중복을 줄이고 유연성과 재사용성을 높여주기 때문에 많은 웹 프레임워크에서 프론트 컨트롤러 패턴을 채택하고 있다.
사실 이 정도의 정의만 이해하고 넘어가도 프레임워크를 단순히 사용하는데에는 지장이 없겠지만 프레임워크의 전체적인 구조가 어떻게 생겼는지 이해하는 것도 분명 필요하다. 따라서 기본적인 프론트 컨트롤러 패턴은 어떻게 구현되는지, 스프링에서는 이 패턴을 어떤 식으로 변형하여 MVC 구조를 구현해냈는지, 더 나아가 스프링에서 프론트 컨트롤러의 역할을 하는 DispatcherServlet의 동작 흐름에 대해 더 자세히 알아보고자 한다.
앞서 정의한 바에 따르면 프론트 컨트롤러는 "웹 어플리케이션에서의 모든 요청을 앞단에서 처리하는 핸들러"이다. 즉 모든 요청은 요청별로 적절히 처리되기 전 일단 프론트 컨트롤러를 거쳐간다. 왜 굳이 이런 번잡스러운 과정을 거쳐서 요청을 처리해야 할까?
프론트 컨트롤러는 일종의 문지기라고 생각할 수 있는데, 이때 문지기는 방문객이 안전한 사람인지 신원을 확인하며 방문객에 대한 정보를 명단에 작성하고 방문 목적에 따라 적절한 장소를 안내를 해준다. (이 일련의 과정은 모든 방문객마다 반복된다.)
프론트 컨트롤러도 전달된 요청에 대해 비슷한 역할을 수행하는데, 다시 말해 웹 어플리케이션의 앞단에서 보안, 국제화, 로깅과 같은 공통 기능을 한 곳에서 처리해주며 요청 별로 다른 컨트롤러에게 처리를 위임한다. 따라서 요청 별로 다른 응답을 받을 수 있도록 보다 유연한 구조를 제공하고, 공통 처리를 한 곳에서 수행하기 때문에 코드 중복이 줄어들며 다른 컨트롤러들은 각 요청 처리에만 집중할 수 있으므로 SRP 원칙에 더 적합한 구조라고 할 수 있다.
*SRP(Signle Responsibility Policy, 단일 책임 원칙): 클래스는 하나의 책임만 가져야 한다는 원칙으로, 여기서 '책임'은 '기능'을 의미. 즉 하나의 클래스는 하나의 기능만을 담당해야 한다는 원칙
이러한 프론트 컨트롤러 패턴은 기본적으로 4가지 요소로 구성된다. Controller, Dispatcher, Helper, View인데 각 객체의 역할은 다음과 같다.
(Front)Controller | 모든 요청이 처리되는 입구로, 요청에 대한 공통 처리를 수행하며 요청 별로 적절한 Helper에게 처리를 위임함. |
Dispatcher | 요청에 따른 적절한 View를 탐색하고 관리하는 역할로, 별도의 객체로 분리될 수 있지만 Controller가 Dispatcher의 역할까지 수행할 수 있음 |
Helper (=Command) |
View와 Controller의 처리를 지원하는 객체로 요청 별로 적절한 처리를 수행하여 관련 데이터와 뷰 정보를 반환 |
View | Helper가 모델링한 데이터를 전달받아 클라이언트에게 응답할 뷰를 렌더링 |
그럼 이러한 프론트 컨트롤러 패턴을 자바를 이용하여 정말 간단히 구현해보자. 구현할 어플리케이션은 전달된 요청에 따라 다른 뷰를 콘솔에 출력하는 동작만을 수행한다. 각 객체간 관계는 다음과 같다.
FrontController
import java.util.Objects;
public class FrontController {
private Dispatcher dispatcher = new Dispatcher();
//공통 기능 처리
private void logging(String request) {
System.out.println("Request Log: " + request);
}
private boolean isAuthenticUser() {
System.out.println("User is authenticated successfully.");
return true;
}
public void doDispatch(String request) {
logging(request);
if (isAuthenticUser()) {
Command command = getCommand(request);
String viewName = command.handle(); //요청별 처리 위임
dispatcher.process(viewName);
}
}
private Command getCommand(String request) {
if (Objects.equals(request, "hello")) return new HelloCommand();
else if (Objects.equals(request, "bye")) return new ByeCommand();
return null;
}
}
FrontController
클래스에서는 logging
, isAuthenticUser
메소드에서 모든 요청마다 실행되어야 하는 공통 기능을 처리하고 있으며 getCommand
메소드로는 요청별 처리를 위임할 Command
객체를 조회하고 있다. Command
객체는 요청 처리 결과로 콘솔에 출력할 뷰의 이름을 반환하는데 이 뷰 이름을 Dispatcher
객체에게 전달해준다. (Command
객체의 작업 결과를 Dispatcher
객체에게 전달하면서 작업을 요청하는 것처럼 프론트 컨트롤러는 전체 요청 처리 과정을 관리, 감독한다고 볼 수 있다.)
Dispatcher
import java.util.Objects;
public class Dispatcher {
public void process(String viewName) {
View view = getView(viewName);
view.render();
}
private View getView(String viewName) {
if (Objects.equals(viewName, "helloView")) return new HelloView();
else if (Objects.equals(viewName, "byeView")) return new ByeView();
return null;
}
}
위에서 말했듯 Dispatcher는 View를 탐색하고 관리하는 역할로 단순히 뷰 이름에 매핑되는 View
객체를 가져와 이를 렌더링한다. 현재 구현된 Dispatcher
는 단순히 View
객체를 생성하여 render
메소드를 호출해주는 역할만을 하고 있어 굳이 Dispatcher
객체가 필요할까? 의문이 들 수 있다. 실제로 스프링에서는 Dispatcher
객체를 별도로 분리하기 보다는 FrontController
가 Dispatcher
의 역할까지 모두 수행하는 식으로 동작한다. 즉 다음과 같이 Dispatcher
객체를 제거하고 Dispatcher
클래스 내의 코드들을 FrontController
클래스에 포함시킬 수 있다.
//FrontController의 doDispatch 메소드
public void doDispatch(String request) {
logging(request);
if (isAuthenticUser()) {
Command command = getCommand(request);
String viewName = command.handle(); //요청별 처리 위임
//Dispatcher 코드 추가
View view = getView(viewName);
view.render();
}
}
Command
public interface Command {
String handle();
}
Command는 요청 별로 적절한 처리를 수행한다. 스프링에서의 우리가 아는 일반적인 Controller에 대응된다. 실제로는 데이터 조회, 비즈니스 로직 처리 등 더 복잡한 처리를 수행하지만, 현재 구현된 Command
는 단순히 전달받은 요청에 따라 콘솔에 출력할 뷰의 이름을 반환하기만 한다.
HelloCommand
public class HelloCommand implements Command {
@Override
public String handle() {
return "helloView";
}
}
ByeCommand
public class ByeCommand implements Command {
@Override
public String handle() {
return "byeView";
}
}
View
public interface View {
void render();
}
View는 사용자에게 보여줄 화면을 렌더링하는 역할을 한다. 실제 어플리케이션에서는 데이터 모델과 뷰 파일(ex. .jsp 파일)을 렌더링하여 응답으로 반환하지만, 현재 구현된 View
객체는 단순히 콘솔에 메시지를 출력하기만 한다.
HelloView
public class HelloView implements View {
@Override
public void render() {
System.out.println("Hello World!");
}
}
ByeView
public class ByeView implements View {
@Override
public void render() {
System.out.println("Bye World!");
}
}
FrontControllerDemo
public class FrontControllerDemo {
public static void main(String[] args) {
FrontController frontController = new FrontController();
frontController.doDispatch("hello");
frontController.doDispatch("bye");
}
}
실행 결과
Request Log: hello
User is authenticated successfully.
Hello World!
Request Log: bye
User is authenticated successfully.
Bye World!
해당 어플리케이션의 실행 결과는 위와 같다. 요청마다 공통 작업이 모두 수행되었으며 요청 별로 알맞은 메시지가 콘솔에 출력되었다.
현재 구현된 코드는 정말 간단하고 기본적인 프론트 컨트롤러 패턴이다. 사실 코드를 보면 FrontController
, Dispatcher
객체의 책임이 많다고 할 수도 있는데, 이는 요청에 따라 적절한 Command
와 View
객체를 선택하는 책임이 해당 객체에 추가로 존재하기 때문이다. 따라서 해당 책임을 지는 별도의 객체로 분리하여 리팩토링할 수 있는데 이는 다음 글에서 진행해보기로 하자.
사실 스프링에서 구현된 프론트 컨트롤러 패턴은 이보다 매우 복잡하다. 이에 대해 자세히 알아보기 전 프론트 컨트롤러의 구조에 대해 쉽게 이해하기 위해 매우 단순하면서도 웹 어플리케이션의 동작과는 매우 동떨어진 코드를 작성하였다. 따라서 다음 글에서는 향상된 프론트 컨트롤러 구조를 스프링의 서블릿을 이용하여 구현해보고 각 클래스와 대응되는 실제 스프링의 클래스는 무엇인지에 대해 알아보고자 한다.
끝.
참고
https://www.tutorialspoint.com/design_pattern/front_controller_pattern.htm
https://en.wikipedia.org/wiki/Front_controller
'자바 > 개념' 카테고리의 다른 글
[디자인 패턴] Facade 패턴 (1) | 2023.12.17 |
---|---|
[디자인 패턴] Strategy Pattern과 Template Callback Pattern (0) | 2023.09.26 |
[디자인 패턴] Template Method Pattern (0) | 2023.09.19 |
[개념] Builder Pattern과 자바의 @Builder 어노테이션2 (0) | 2023.02.13 |
[개념] Builder Pattern과 자바의 @Builder 어노테이션1 (0) | 2023.02.13 |
- Total
- Today
- Yesterday
- Spring Security
- 단위 테스트
- spring boot
- Transaction
- ParameterizedTest
- vscode
- facade 패턴
- 템플릿 콜백 패턴
- Front Controller
- FrontController
- github
- spring
- Assertions
- servlet filter
- junit5
- 모두의 리눅스
- QueryDSL
- Git
- C++
- Java
- Linux
- 디자인 패턴
- SSE
- Gitflow
- 서블릿 컨테이너
- spring aop
- 전략 패턴
- JPA
- mockito
- rest api
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |