1. 프론트 컨트롤러 패턴 소개
프론트 컨트롤러 도입 전에는 클라이언트의 요청에 맞는 컨트롤러를 작동시켰다. 이때 컨트롤러들은 여러 공통 작업을 수행하게 되고, 프로젝트의 규모가 커질 수록 이러한 공통 작업의 양은 많아진다. 이를 막기 위해 우리는 프론트 컨트롤러를 만들 수 있다. 이 프론트 컨트롤러에 모든 공통 로직을 몰아넣고, 이전 컨트롤러들은 각각의 목적에 맞는 로직만 수행하면 된다. 그림으로 나타내면 다음과 같다.
FrontController의 특징은 다음과 같다.
- 먼저 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받는다. = 입구가 하나다.
- 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아 호출한다.
- 공통 처리가 가능하다.
- 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용할 필요가 없다.
스프링 웹 MVC의 핵도 바로 "프론트 컨트롤러"에 있다. 스프링 웹 MVC의 "DispatcherServlet"이 FrontController 패턴으로 구현되어 있기 때문이다.
2. 프론트 컨트롤러 도입 - v1
이제 이전 코드에서 프론트 컨트롤러를 도입해보자. v1의 구조는 다음과 같다.
먼저 각 컨트롤러의 인터페이스를 만들 것이다. 우리는 다형성을 활용하여 프론트 컨트롤러를 작성할 것이기 때문이다. 이때 process라는 메서드는 기존 코드의 service와 동일한 역할을 하며, 이전 코드를 그대로 사용하면 된다. (즉, service 메서드의 이름만 process로 변경한 코드를 사용하면 된다.)
public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
그리고 프론트 컨트롤러의 코드는 다음과 같다.
// "v1/" 하위의 어떤 요청이 들어와도 이 서블릿이 호출됨.
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
private Map<String, ControllerV1> controllerMap = new HashMap<>();
public FrontControllerServletV1() {
controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
controllerMap.put("/front-controller/v1/members/save", new MemberSaveControllerV1());
controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 요청 받은 URI를 그대로 가져옴.
String requestURI = request.getRequestURI();
// 요청 받은 URI에 해당하는 컨트롤러를 가져옴.
ControllerV1 controller = controllerMap.get(requestURI);
// 예외 처리를 해줌
if (controller==null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// controller가 잘 조회된 경우, 해당 컨트롤러의 process를 실행시킴.
controller.process(request, response);
}
}
- 위처럼 매핑 정보를 직접 매핑해주고, 받은 URI에 따라 컨트롤러를 가져온다. 그리고 해당 컨트롤러의 process라는 메서드를 수행시킨다.
3. View 분리 - v2
이전 컨트롤러들의 코드를 보면 컨트롤러에서 뷰로 이동하는 부분이 모두 중복되었다. 해당 코드는 아래와 같다.
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
따라서 이를 따로 처리하기 위해 뷰를 처리하는 객체를 따로 만들것이다. v2의 구조는 아래와 같이 변경된다.
먼저 view를 처리하는 MyView를 만들자.
public class MyView {
private String viewPath;
public MyView(String viewPath) {
this.viewPath = viewPath;
}
// 기존에 jsp로 이동을 처리하는 과정을 랜더링 한다고 표현할 것이다.
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
- MyView는 생성될 때 viewPath (jsp가 있는 경로)를 받아온다. 그리고 render() 메서드를 통해 해당 jsp로 포워딩할 수 있다.
그리고 controller의 인터페이스는 이전과 다르게 MyView 객체를 반환해야 하므로 아래와 같이 process의 반환형을 바꿔줘야 한다.
public interface ControllerV2 {
MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
- 이전과 차이점은 반환 타입이 MyView로 바뀌었다는 점이다.
각 controller들의 코드들은 아래와 같이 간단하게 바뀐다.
// 입력 form에 대한 처리를 하는 controller (회원가입 데이터를 받아오는 화면 출력)
public class MemberFormControllerV2 implements ControllerV2 {
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
return new MyView("/WEB-INF/views/new-form.jsp");
}
}
// 회원 가입을 처리하고 결과를 화면에 출력해주는 controller
public class MemberSaveControllerV2 implements ControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
request.setAttribute("member", member);
return new MyView("/WEB-INF/views/save-result.jsp");
}
}
// 전체 회원을 조회할 수 있는 controller
public class MemberListControllerV2 implements ControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
return new MyView("/WEB-INF/views/members.jsp");
}
}
위와 같이 코드가 매우 간편해진 것을 볼 수 있다. 그리고 마지막으로 프론트 컨트롤러는 다음과 같다.
@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {
private Map<String, ControllerV2> controllerMap = new HashMap<>();
public FrontControllerServletV2() {
controllerMap.put("/front-controller/v2/members/new-form", new MemberFormControllerV2());
controllerMap.put("/front-controller/v2/members/save", new MemberSaveControllerV2());
controllerMap.put("/front-controller/v2/members", new MemberListControllerV2());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 요청 받은 URI를 그대로 가져옴.
String requestURI = request.getRequestURI();
// 요청 받은 URI에 해당하는 컨트롤러를 가져옴.
ControllerV2 controller = controllerMap.get(requestURI);
// 예외 처리를 해줌
if (controller==null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// view를 받아와서 render()해줌.
MyView view = controller.process(request, response);
view.render(request, response);
}
}
- v1과 다른 점은 단지 마지막에 controller.process만 실행시키는 것이 아니라, 반환해주는 view를 받아와서 해당 뷰에서 랜더링을 수행하도록 한다는 점이다.
4. Model 추가 - v3
이번에는 모델을 추가해보자. 먼저 "서블릿의 종속성을 제거"할 것이다. 컨트롤러 입장에서 생각해보자. 일단 HTTP Form을 처리하는 컨트롤러는 HttpServletRequest, HttpServletResponse 객체들을 사용하지 않는다. 물론 어떤 것이 필요한 친구도 있다. 하지만 save의 경우도 보면 요청 파라미터의 정보가 필요한 것이지, request, response 정보가 당장 필요한 것이 아니다. 따라서 request, response가 필요한 것이 아니라 파라미터 정보가 필요하므로 요청 파라미터 정보는 Map을 통해 넘길 것이다. 그러면 컨트롤러는 서블릿을 몰라도 동작할 수 있다. 또한 request 객체를 Model로 사용하는 대신, 별도의 Model 객체를 만들어 반환하면 된다. 즉, MVC에서 Request의 setAttribute를 사용해 이를 모델로 활용하여 안에 무언가를 담아 뷰에서 랜더링했다. 그것도 모델이라는 객체를 대신 만들어서 이 모델이 해당 역할을 대신 하도록 할 것이다. 이렇게 하면 구현 코드도 단순하고, 테스트 코드 작성도 쉬워진다.
추가적으로 view이름의 중복을 제거할 것이다. 지금까지 view를 가져오려면 해당 view의 위치를 전부 다 작성해야 했다. 이때 중복되는 부분은 제거해보자. 이때 컨트롤러는 "뷰의 논리 이름"만을 반환할 것이고, 실질적인 위치는 프론트 컨트롤러가 처리하도록 할 것이다. 만약 이후 뷰의 폴더가 바뀌면 프론트 컨트롤러만 고치면 된다. 우리가 구현하는 v3의 구조는 다음과 같다.
- v2에서는 View를 반환했다. 이번에는 model이랑 view가 섞여있는 (Model을 직접 만들고, view의 이름까지 전달하는 객체) ModelView를 반환할 것이다.
- 컨트롤러는 view의 논리 이름만 반환한다. 따라서 이를 물리 주소로 바꾸어줄 친구가 필요한데 이를 viewResolver가 해결하고, MyView를 반환할 것이다.
먼저 사용할 ModelView 클래스는 다음과 같다.
- view의 논리 이름을 가질 String 타입 필드를 가진다.
- view를 생성하기 위해 사용할 데이터들을 받아올 때 Map을 활용한다. 따라서 Map 타입의 필드를 추가로 만든다.
@Getter @Setter
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
}
이제부터 컨트롤러들은 HttpServletRequest, HttpServletResponse 객체를 사용하지 않는다. 단, 화면에서 전달된 데이터들을 담는 친구들이 필요한데, 이는 Java에서 제공하는 Map을 활용할 것이다. 이를 바탕으로 프론트 컨트롤러의 작동 순서를 보면 다음과 같다.
- 프론트 컨트롤러에서는 먼저 HttpServletRequest에 담겨있는 파라미터들을 모두 Map에 담아야 한다.
- 컨트롤러의 결과인 ModelView를 받는다. (이때 ModelView에는 찾아야할 view의 논리 이름만 담겨있다)
- view의 논리 이름을 변환하여 MyView객체를 만드는 작업을 위해 viewResolver라는 메서드를 사용한다.
- 생성된 view를 받아 render메서드를 실행시킨다.
@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
private Map<String, ControllerV3> controllerMap = new HashMap<>();
public FrontControllerServletV3() {
controllerMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
controllerMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
controllerMap.put("/front-controller/v3/members", new MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 요청 받은 URI를 그대로 가져옴.
String requestURI = request.getRequestURI();
// 요청 받은 URI에 해당하는 컨트롤러를 가져옴.
ControllerV3 controller = controllerMap.get(requestURI);
// 예외 처리를 해줌
if (controller==null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// HTML Form으로 받은 데이터는 HttpServletRequest 객체에 담겨있다.
// 해당 객체의 파라미터 내용을 옮겨서 만든 것이 paramMap이고 각 컨트롤러들은 이를 활용하게 된다.
Map<String, String> paramMap = createParamMap(request);
// 컨트롤러의 결과를 받아온다.
ModelView mv = controller.process(paramMap);
// ModelView에 저장된 논리 이름을 올바른 path로 변경하여 MyView객체를 만든다.
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
// view의 render 메서드를 작동시킨다.
view.render(mv.getModel(), request, response);
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/"+viewName+".jsp");
}
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
이제 각 컨트롤러의 인터페이스를 만들자. v2와의 차이는 v2는 MyView 객체를 반환했지만, v3는 ModelView를 반환해야 한다는 것이다. 또한 HttpServletRequest와 HttpServletResponse 객체를 받지 않고, 단지 paramMap을 받아온다. 따라서 해당 인터페이스는 아래와 같이 구성된다.
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
각 컨트롤러의 내용은 아래와 같이 간단해진다.
// 단순히 HTTP Form을 생성하는 jsp의 위치를 알려주기 위한 controller
public class MemberFormControllerV3 implements ControllerV3 {
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
}
// 방금 회원가입한 회원의 정보를 기록하고, 해당 회원 정보를 모델에 담아주는 controller
public class MemberSaveControllerV3 implements ControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member);
return mv;
}
}
// 회원가입한 모든 유저를 찾아 model에 담아주는 controller
public class MemberListControllerV3 implements ControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
List<Member> members = memberRepository.findAll();
ModelView mv = new ModelView("members");
mv.getModel().put("members", members);
return mv;
}
}
이때, MyView는 이전과 달리 render에 정보가 들어있는 model을 추가로 보내줘야 한다. jsp는 HttpServletRequest 객체를 바탕으로 화면을 구성하므로 model에 담긴 데이터들을 모두 해당 객체로 옮기는 과정을 추가해야한다. 이는 메서드 오버로딩으로 처리하여 MyView는 다음과 같이 작성된다.
public class MyView {
private String viewPath;
public MyView(String viewPath) {
this.viewPath = viewPath;
}
// 기존에 jsp로 이동을 처리하는 과정을 랜더링 한다고 표현할 것이다.
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
modelToRequestAttribute(model, request);
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
private static void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
model.forEach((key, value) -> request.setAttribute(key, value));
}
}
5. 단순하고 실용적인 컨트롤러 - v4
v3는 잘 설계된 컨트롤러지만 개발자 입자에서 항상 ModelView 객체를 생성하고, 반환하는 것은 번거롭다. 따라서 v3 구조를 바탕으로 하여, 좀 더 편리하게 개발할 수 있는 v4 버전을 개발해보자. v4의 구조는 v3와 거의 비슷하다.
차이점은 다음과 같다.
- controller를 호출할 때 paramMap뿐만 아니라 model도 같이 전달한다.
- Controller는 ModelView가 아니라 ViewName만을 반환한다.
일단 v4의 컨트롤러들의 인터페이스는 다음과 같다.
- view의 이름만 전달할 것이므로 반환형은 String인 process 메서드를 가지고 있어야 한다.
- process 메서드 사용시 파라미터들이 담긴 paramMap과 이후 view에서 사용할 model 두 친구들을 인자로 줘야한다.
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
그리고 각 컨트롤러의 코드는 아래와 같이 간편해진다.
- ModelView와 같이 복잡한 객체가 아니라, 단순히 String 타입을 반환한다.
- 이전에는 ModelView에 있는 모델을 get 메서드로 가져와서 이후 view에서 화면을 구성할 때 사용할 데이터를 넣어줬다. 하지만 파라미터로 model을 넘겨주므로 get 메서드 같은 것을 사용할 필요가 없고 그냥 model에 바로 넣어주면 된다.
public class MemberFormControllerV4 implements ControllerV4 {
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
return "new-form";
}
}
// -----
public class MemberSaveControllerV4 implements ControllerV4 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
model.put("member", member);
return "save-result";
}
}
// -----
public class MemberListControllerV4 implements ControllerV4 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
List<Member> members = memberRepository.findAll();
model.put("members", members);
return "members";
}
}
그리고 프론트 컨트롤러는 다음과 같이 변경된다.
- 컨트롤러를 실행할 때 model을 같이 넘겨야 하므로 그 전에 미리 model을 선언한다.
@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
private Map<String, ControllerV4> controllerMap = new HashMap<>();
public FrontControllerServletV4() {
controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 요청 받은 URI를 그대로 가져옴.
String requestURI = request.getRequestURI();
// 요청 받은 URI에 해당하는 컨트롤러를 가져옴.
ControllerV4 controller = controllerMap.get(requestURI);
// 예외 처리를 해줌
if (controller==null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(request);
Map<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
MyView view = viewResolver(viewName);
view.render(model, request, response);
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/"+viewName+".jsp");
}
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
6. 유연한 컨트롤러1 - v5
만약 어떤 사람은 ControllerV3 방식으로 개발하고 싶고, 어떤 사람은 ControllerV4 방식으로 개발하고 싶다면 어떻게 해야할까? 이를 해결하기 위해 어댑터 패턴을 사용할 수 있다. v5의 구조는 아래와 같다.
- 핸들러 어댑터 목록에서 ControllerV3 관련된 어댑터 혹은 ControllerV4와 관련된 어댑터를 찾아온다. 즉, 요청에 맞는 컨트롤러와 관련된 어댑터를 찾아온다.
- 기존에는 바로 프론트 컨트롤러에서 컨트롤러를 호출했다. 지금부터 컨트롤러는 어댑터를 통해 컨트롤러를 호출해야 한다. 이때 어댑터가 컨트롤러를 호출해 결과를 가져오고 결과를 FrontController에게 전달한다.
- 핸틀러는 컨트롤러보다 더 넓은 범위를 의미한다. 이제부턴 어댑터가 있기 때문에 컨트롤러 개념ㄴ뿐만 아니라 어떠한 것이든 해당하는 종류의 어댑터만 있으면 다 처리할 수 있다.
먼저 어댑터의 인터페이스는 다음과 같다.
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
- boolean supports(Object handler): 매개변수인 handler는 컨트롤러를 말한다. 이때 해당 메서드는 인자로 들어온 컨트롤러를 처리할 수 있는지 판단한다.
아래는 ControllerV3를 지원하는 어댑터다.
public class ContorllerV4HanderAdapter implements MyHandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV4);
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
ControllerV4 controller = (ControllerV4) handler;
controller.process();
return null;
}
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
- 위와 같이 supports 메서드는 instanceof를 활용해 해당 타입의 컨트롤러인지 확인해준다.
- 기존에 V3에서는 ModelView를 반환했으므로 별도의 처리없이 컨트롤러의 결과를 그대로 반환해주면 된다.
아래는 ControllerV4에 대한 어댑터이다.
public class ContorllerV4HanderAdapter implements MyHandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV4);
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
ControllerV4 controller = (ControllerV4) handler;
Map<String, String> paramMap = createParamMap(request);
Map<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
ModelView mv = new ModelView(viewName);
mv.setModel(model);
return mv;
}
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
- ControllerV4는 단지 view의 이름만 반환하며, model에 대한 정보는 따로 model에 저장된다. 따라서 ModelView 객체를 새로 만들어 구성한 후 해당 결과를 프론트 컨트롤러에 반환한다.
그리고 아래는 프론트 컨트롤러다.
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
private final Map<String, Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontControllerServletV5() {
initHandlerMappingMap();
initHandlerAdapters();
}
private void initHandlerMappingMap() {
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HanderAdapter());
handlerAdapters.add(new ContorllerV4HanderAdapter());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 핸들러를 가져옴.
Object handler = getHandler(request);
// 예외 처리를 해줌
if (handler==null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyHandlerAdapter adapter = getHandlerAdapter(handler);
ModelView mv = adapter.handle(request, response, handler);
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
view.render(mv.getModel(), request, response);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if(adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("hander adapter를 찾을 수 없습니다. handler="+handler);
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/"+viewName+".jsp");
}
}
- 프론트 컨트롤러는 handler adapter의 인터페이스에만 의존한다. 따라서 구현 클래스가 뭐가 들어와도 상관이 없다. 따라서 이후 확장에도 굉장히 편하다. 이후 확장을 한다면 추가적으로 adapter만 만들어주면 되고, 프론트 컨트롤러는 mapping 정보와 handler에 대한 정보만 추가로 작성해주면 아무것도 바꿀 것이 없다.
출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
'BackEnd > Spring' 카테고리의 다른 글
[Spring MVC-1] 스프링 MVC - 기본 기능 (0) | 2023.09.23 |
---|---|
[Spring MVC-1] 스프링 MVC - 구조 이해 (0) | 2023.09.22 |
[Spring MVC-1] 서블릿, JSP, MVC 패턴 (0) | 2023.09.12 |
[Spring MVC-1] 서블릿 (0) | 2023.09.09 |
[Spring MVC-1] 웹 어플리케이션 이해 (0) | 2023.09.06 |