하나의 서블릿이나 JSP만으로 비즈니스 로직과 뷰 렌더링까지 모두 처리하게되면안된다
변경의 라이프 사이클
컨트롤러: HTTP 요청을 받아서 파라미터 검증하고, 비즈니스 로직을 실행한다. 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담아준다.
모델: 뷰에 출력할 데이터를 담아둔다. 뷰가 필요한 데이터를 모두 모둘에 담아서 전달해주는 덕분에 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고, 화면을 렌더링 하는 일에 집중할 수 있다.
뷰: 모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중한다. 여기서는 HTML을 생성하는 부분을 말한다.
참고
컨트롤러에 비즈니스 로직을 둘 수도 있지만, 이렇게 되면 컨트롤러가 너무 많은 역할을 담당한다. 그래서 일반적으로 비즈니스 로직은 서비스(Service)라는 계층을 별도로 만들어서 처맇나다. 그리고 컨트롤러는 비즈니스 로직이 있는 서비스를 호출하는 담당함.


dispatcher.forward(): 다른 서블릿이나 JSP로 이동할 수 있는 기능이다. 서버 내부에서 다시 호출이 발생함.
참고
/WEB-INF 이 경로 안에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없다. 우리가 기대한느 것은 항상 컨트롤러를 통해서 JSP를 호출하는 것이다.!
redirect vs forward
프론트 컨트롤러 도입 전

프론트 컨트롤러 도입 후

특징은?
- 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
- 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
- 입구를 하나로 만든다!
- 공통 처리 기능
- 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 된다!


// 패키지 위치는 frontcontroller임
이거는 view 클래스
public class MyView {
private String viewPath;
public MyView(String viewPath) {
this.viewPath = viewPath;
}
public void render(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
이거는 controller 클래스
public class MemberFormControllerV2 implements ControllerV2 {
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
MyView myView = new MyView("/WEB-INF/views/new-form.jsp");
return myView;
}
}
이제 컨트롤러는 복잡한 dispathcer.forward()를 직접 생성해서 호출하지 않아도 된다.
단순히 MyView 객체를 생성하고 거기에 뷰 이름만 넣고 반환하면 댐
view.reder을 호출하면 forward로직을 수행해서 JSP가 실행되는 구조
서블릿 종속성 제거
컨트롤러 입장에서 servlet 관한 정보 덜어주기
요청 파라미터 정보를 자바의 Map으로 대신 넘기도록 하면서 컨트롤러가 서블릿 기술을 몰라도 동작하게끔 설계를 해보자
뷰 이름 중복 제거
컨트롤러에서 지정한느 뷰 이름에 중복이 있는 것을 확인할 수 있다.
컨트롤러는 뷰의 논리 이름을 반환하고, 실제 물리 위치의 이름은 프론트 컨트롤러에서 처리하도록 단순화 하기
이렇게 해두면 향후 뷰의 폴더 위치가 함께 이동해도 프론트 컨트롤러만 고치면 되기 때문

서블릿의 종속성을 제거하기 위해 Model을 직접 만들고, 추가로 View 이름까지 전달하는 객체를 만들어보자
ModelView.java
@Getter @Setter
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName){
this.viewName=viewName;
}
}
뷰의 이름과 뷰를 렌더링할 때 필요한 model객체를 가지고있다. model은 단순히 map으로 되어 있으므로 컨트롤러에서 뷰에 필요한 데이터를 key,value로 넣어주기
ControllerV3
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
이 컨트롤러는 서블릿 기술을 전혀 사용하지 않음
HttpServletRequest가 제공하는 파라미터는 프론트 컨트롤러가 paramMap에 담아서 호출해주면 된다.
응답 결과로 뷰 이름과 뷰에 전달할 Model 데이터를 포함하는 ModelView 객체를 반환하면 된다.
MemberFormControllerV3
public class MemberFormControllerV3 implements ControllerV3 {
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
}
ModelView를 생성할 때 new-form이라는 view의 논리적인 이름ㅇ르 지정한다. 실제 물리적인 이름은 프론트 컨트롤러에서 처리한다.
MemberSaveControllerV3
public class MemberSaveControllerV3 implements ControllerV3 {
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;
}
paramMap.get("username")
파라미터 정보는 map에 담겨있다. map에서 필요한 요청 파라미터를 조회하면 된다.
mv.getModel().put("member",member)
모델은 단순한 map이므로 모델에 뷰에서 필요한 member 객체를 담고 반환한다.
MemberListControllerV3
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;
}
}
FrontControllerServletV3
@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 {
String requestURI=request.getRequestURI();
ControllerV3 controller=controllerMap.get(requestURI);
if(controller==null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// paramMap
Map<String, String> paramMap = createParamMap(request);
ModelView mv=controller.process(paramMap);
String viewName=mv.getViewName();
MyView view = viewResolver(viewName);
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;
}
}
전체흐름 정리하자
1.클라이언트에서 요청 오면 frontcontroller가(서블릿이) 받는다
2.service쪽으로 HttpServlet에 대한 정보 들어옴
3.request.getRequestURI로 URI정보 뽑아온 후
4.controllerMap에서 controller 객체 뽑아온다
5.Map<String,String> paramMap 선언 후 createParamMap(request) 실행 --> 이는 HttpServletRequest의 정보를 뽑아와서 ?? 이거 왜 쓰는지 이해가 안된다
6.controller의 process함수에 paramMap 넘겨줌 이를 ModelView 객체로 초기화 시켜줌
7.modelview에서 viewname 뽑아옴
8.이 viewname을 이용해 viewResolver실행 이는 view객체를 리턴한다.
9.Myview 객체를 통해서 forward실행

프론트 컨트롤러에서 model(비엇음) 넘겨주고
Controller에서 model을 채워주는 방식임
어댑터 패턴
지금까지 개발한 프론트 컨트롤러는 한가지 방식의 컨트롤러 인터페이스만 사용할 수 있다. 호환이 가능하도록
어댑터 패턴을 사용해서 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 변경해보자

MyHandlerAdapter
어댑터는 이렇게 구현해야 한다는 어댑터용 인터페이스임
ControllerV()HandlerAdapter
어댑터임
FrontControllerServletV5
컨트롤러(핸들러로 호칭 변경)
분석해보자
@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 initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
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());
//v4 추가
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());
}
@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);
// paramMap
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("handler adapter를 찾을 수 없습니다.");
}
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");
}
}
1.service 에서 모든 로직이 실행된다.
2.먼저 생성자(FrontControllerServletV5)에서
2-1. initHandlerAdapters() 가 실행된다. 이때 handlerMappingMap에
각 컨트롤러들이 map형태로 들어가게 된다.
2-2.initHandlerAdapters() 에서 handlerAdapters(리스트형식) 핸들러 아댑터 넣기
3.getHanlder 로 우선 requestURI 를 처리할 수 있는지 받아옴 이때 handlerMappingMap에서 찾음
4.getHandlerAdapter()로 지원하는 핸들러인지 찾는다. 반환은 MyHandlerAdapter 형 근데 객체임
5.adapter.handle로 modelview 생성함 이때 컨트롤러가 사용된다.
6.modelview에서 view이름 뽑기
7. view 랜더링
한곳에 놓고 실행해도 될 것들을 계에에에속 분리를 시킨다...
이유는 아직 잘 모르겠다. 그냥 유지보수가 쉽겠거니 하고 일단은 생각중이다 그리고 아직 머리속에도 이 전체적인 흐름이 잘 안들어온다.. 머리가 안좋은 터이겠거니 열심히 노력이라도 해야겠다.