월루를 꿈꾸는 대학생
[MVC-1] MVC 프레임 워크 만들기 본문
프론트 컨트롤러 패턴 소개
일단 지금은 손수 다 만들고.... 나중에 스프링 mvc패턴까지 가서 mvc패턴을 깊게 이해해보자
- 공통 로직을 각각 처리 아무거나 다 들어올 수 있으니까 컨트롤러마다 공통로직을 도입했어야했다 = 중복
프론트 컨트롤러 도입 후
특징
- 프론트 컨트롤러 서블릿 하나로 클라언트 요청을 받음 (수문장)
- 이 프론트 컨트롤러가 알아서 요청에 맞는 컨트롤러로 토스함
- 입구를 하나로 만들어서 공통 처리를 중복되지 않도록 하여 유지보수가 쉽도록
- 프론트 컨트롤러 제외한 나머지 컨트롤러는 서블릿 필요없음
- 서블릿이라는 것은 http요청이 들어왔을 때 was서버가 req,resp 객체를 만들어서 서블릿 컨테이너에 등록된 서블릿에다가 던져 준 다음 서블릿이 그 요청에 맞는 로직을 실행하는 것이었음 근데 이 역할을 프론트 컨트롤러가 앞에서 다 해버리니까 나머지 뒷단 컨트롤러들은 굳이 서블릿을 만들지 않아도 알아서 호출 당하니까 굳굳
- 다른 컨터이너에 어노테이션이나 리퀘스트 , 리스폰스 같은 거 안써도 실행가능 !
스프링 웹 mvc 패턴의 핵심도 FrontController임
스프링 웹 mvc의 DispatcherServlet도 FrontController 패턴으로 되어 있다
그래서 우리는 이 frontcontroller가 어떤 것인지 또 어떻게 발전하는지 알아두면 mvc를 잘 이해할 수 있을 것!
프론트 컨트롤러 도입 - v1
- 요청이 오면 프론트 컨트롤러가 받은 후에 요청에 따른 매핑 정보를 컨트롤러 조회함 그 후 해당하는 컨트롤러를 호출하고 컨트롤러는 로직 수행후 forward로 jsp 뷰를 응답
컨트롤러는 인터페이스로 만들것임 == 다형성
public interface ControllerV1 {
// 해당 인터페이스를 구현한 컨트롤러들을 만들고 로직의 일관성을 유지 = 다형성
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
위의 인터페이스를 구현한 컨트롤러들
어노테이션 없음 = 서블릿 컨테이너에 들어가지 않는다
내부로직은 전에 했던거와 거의 동일
public class MemberListControllerV1 implements ControllerV1 { private MemberRepository memberRepository = MemberRepository.getInstance(); @Override public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Member> members = memberRepository.findAll(); request.setAttribute("members", members); String viewPath = "/WEB-INF/views/members.jsp"; RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); dispatcher.forward(request, response); } }
프론트 컨트롤러
- 매핑정보 = controllerMap
- url요청이 오는 경우 (/v1/* ) 해당 하위 요청까지 일단 오면 이 프론트 컨트롤러가 실행이 됨
프론트 컨트롤러는 생성 시 매핑 정보를 만들게 되고 http요청이 오면 service가 콜백되어서 해당 url에 맞는 process() 메서드를 실행하게 됨 == 이럴려고 앞에 인터페이스로 구현하게 해서 다형성을 쓴 거
- get.ReqeustURI() 로 요청 경로 얻기 가능
//v1/* : 하위에 어떤 거가 들어오든 일단 해당 프론트 컨트롤러가 동작함
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
// url를 키로 삼는 맵
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 {
System.out.println("FrontControllerServletV1.service");
String requestURI = request.getRequestURI(); // 요청 url 얻음
ControllerV1 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND); //404
return;
}
controller.process(request, response); // 다형성
}
}
분석
- urlPatterns
- /v1/* : /v1 밑에 있는 하위 경로들을 호출 할 시 해당 서블릿이 호출되도록 설정
- controllerMap
- url : contoller 객체 => 다형성 활용
- Service
- 요청받은 url를 키로 해서 해당하는 컨트롤러 객체를 꺼낸 다음 구현된 proces() 메서드를 싱행시킴
구조를 어느정도 변경했고 다음 시간은 뷰를 건들임
공통된 부분
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
View 분리 - v2
모든 컨트롤러에서 jsp 파일로 포워딩 해주는 코드가 중복됨을 알 수 있음 = 지저분 , 유지보수 빡침
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
즉 이 부분을 별도로 뷰 처리만 전담으로 하는 객체를 만들 것
컨트롤러가 그냥 myview객체만 다시 프론트 컨트롤러로 전달만 해주면 오케
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);
}
}
기존 인터페이스 ControllerV1은 process 메서드의 반환 타입이 void였지만 v2에서는 MyView 객체를 반환하도록 한다
public interface ControllerV2 {
MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
컨트롤러 수정
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");
}
}
새로운 프론트 컨트롤러 부분
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI(); // 요청 url 얻음
ControllerV2 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND); //404
return;
}
MyView view = controller.process(request, response);
view.render(request,response);
}
원래는 그냥 controller 객체의 process() 메서드를 호출해서 각 컨트롤러에 정의되어 있는 포워드 로직을 실행했었는데 해당 로직을 분리해서 MyView객체를 바꿨고 컨트롤러의 process도 void가 아니라 MyView객체를 반환하도록 코드를 변경한 후에 프론트 컨트롤러에서 MyView 객체를 받고 그 객체에 정의된 포워드 로직을 실행해서 깔끔하게 만든 코드
- front-controller/v2/members/new-form 요청
- 프론트 컨트롤러에서 매핑 정보 확인 후 해당 객체를 사용
- 컨트롤러 객체에서 process 메서드를 호출해서 MyView객체를 반환 받음
- MyView객체의 render함수를 실행해서 jsp파일로 포워딩 시킴
Model 추가 - v3
서블릿 종속성 제거
public MyView process(HttpServletRequest request, HttpServletResponse response)
보면 request ,response 정보는 필요도 없는데 스펙상 받아야하는 경우가 있어서 모든 컨트롤러에서 중복적으로 해당 코드를 적는 중
요청 파라미터 정보를 Map으로 대신 넘기도록 설계 -> 컨트롤러는 서블릿 몰라도 동작 가능
request 객체를 Model로 사용하는 대신 별도 Model객체 만들어서 반환하도록 설계 -> request 객체 필요없어짐
==즉 서블릿 없어도 사용할 수 있도록 코드 수정하기~!==
뷰 이름 중복 제거
return new MyView("/WEB-INF/views/save-result.jsp");
뷰 이름 중복이 있으니까 이 부분을 컨트롤러가 뷰의 논리 이름 반환하도록 설정하고 물리적인 위치는 프론트 컨트롤러에서 처리하도록 하면 나중에 폴더위치 바뀔 때 컨트로러 마다 수정하는 게 아니라 프론트 컨트롤러만 수정하면 되니까 유지보수가 간편
/WEB-INF/views/save-result.jsp => save-result
v3구조
ModelView
- 지금까지 request객체를 사용을 했기 때문에 코드 종속성이 생겼음
- 종속성 제거를 위해 별도의 Model객체를 만들어서 Vie에 전달하도록 하자 (Model + View 같이 있는 객체)
별도로 만든 모델 객체
public class ModelView {
private String viewName;
private Map<String,Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, Object> getModel() {
return model;
}
public void setModel(Map<String, Object> model) {
this.model = model;
}
}
- 해당 모델 객체에 뷰이름이랑 또 랜더링 할 때 쓸 모델 객체(object)를 넣은 맵을 만들어서 그걸로 이제 꺼내서 쓰면 되도록 설계
컨트롤러 인터페이스
public interface ControllerV3 {
ModelView process(Map<String, String> paraMap);
}
- request , response 객체가 안들어가니까 매우 간단하게 정리 가능 = > 서블릿 기술 사용 안함
컨트롤러
@Override
public ModelView process(Map<String, String> paramMap) {
// 이미 받을 때 프론트컨트롤러가 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");
// getModel로 map을 반환한 다음 그 map에다가 member 데이터 넣어줌
mv.getModel().put("member", member);
return mv;
}
- 논리적인 이름만 넣어서 반환해주면 오케
- 물리적인 경로는 프론트 컨트롤러에서 처리
- mv는 단순 객체니까 getModel로 map을 반환한 후에 쓸 데이터를 넣어서 반환시킴
- ModelView 객체를 반환해서 모델처럼 사용하도록 처리 = 서블릿 종속 x
MyView 클래스
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 void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
model.forEach((key, value)-> request.setAttribute(key, value));
}
- 모델에 있는 값들을 request에 다 세팅해주고 그 request객체를 통해서 jsp로 포워딩 해줌
- jsp는 request객체가 필요하니까 모델에 있는 값들을 다시 request로 세팅하는 과정이 필요
🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋
- request 객체는 스코프가 http 연결 끊길 때까지니까 해당 객체는 어디서든 접근이 되는 느낌이라 return값없어도 모델에서 값 세팅한 request를 따로 반환 안 해도 되는 듯
🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋
viewResolver : 논리이름을 물리이름으로~! 실제뷰를 찾아주는 해결자
- myView객체에 물리 경로를 넣어서 반환
private MyView viewResolver(String viewName) { return new MyView("/WEB-INF/views/" + viewName + ".jsp"); }
프론트 컨트롤러
@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; }
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 Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
// request객체에 있는 모든 값들을 조회한 다음 해당 값들을 paramMap에 넣어줌
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
// 해당 뷰리졸버로 논리이름을 파라미터로 받아서 물리적 경로로 변경해서 반환해줌
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}
단순고 실용적인 컨트롤러 - v4
- v3 컨트롤러는 서블릿 종속성 제거하고 뷰 경로 중복 제거등 했음
- 이제 보다 더욱 편리하게 쓸 수 있도록 v4를 작성해보자
- 컨트롤러가 ModelView말고 ViewName만 반환하도록 !
프론트 컨트롤러가 모델까지 만들어서 컨트롤러에 넣어줌
반환값이 String이라 ViewName만 넣어주면 됨
public interface ControllerV4 {
String process(Map<String,String>paramMap, Map<String,Object> model);
}
컨트롤러 구현
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);
// v3에서는 모델뷰를 여기서 만들었는데 v4프론트 컨트롤러에서 만들어서 주니까 put만 하면 되네
model.put("member", member);
return "save-result";
}
}
- 컨트롤러에서 모델을 만들필요가 없어짐
- 또 프론트에서 만들어진 Model객체의 참조값을 파라미터로 받은 다음 컨트롤러에서 값을 수정한 거라 메모리 상에 있던 인스턴스도 값이 수정된 상태에서 처리가 된다 밑에 질문 참고
https://www.inflearn.com/questions/371530
프론트 컨트롤러 부분
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
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);
}
보면 모델을 만들고 그 모델을 각 컨트롤러의 process메서드의 파라미터로 보내서 값을 처리한다
생각해보니 껍데기만 넘겨주면 컨트롤러에서 알아서 값 넣어주니까 ㄱㅊ은건가
처리된 모델은 뷰로 넘겨서 랜더링 하도록
유연한 컨트롤러1 - v5
- 어떤 개발자는 v3 또 다른 사람은 v4로 개발하고 싶으면 어떻게 처리할까 어떻게 해야 모두 수용할 수 있을까 기존 코드의 프론트 컨트롤러는 타입을 명시적으로 지정하고 있어서 유연한 처리가 불가
private Map<String, ControllerV4> controllerMap = new HashMap<>();
어뎁터 패턴
- 프론트 컨트롤러는 하나의 타입만 쓸 수 있음
- v3 ,v4 컨트롤러들은 110v , 220v 같이 완전 다른 인터페이스라 호환이 불가
- 이럴 때 어댑터 를 사용하면 다양한 방식 컨트롤러 처리 가능
- 핸들러 어댑터 : 중간에 어댑터 역할을 수행 , 다양한 종류의 컨트롤러 호출 가능해짐
- 핸들러 : 컨틀러의 더 넓은 이름 , 다른 것들도 처리 할 수 있으니까 핸들러라고 범위를 넓힘
http요청이 오면 프론트 컨트롤러에서 핸들러 매핑정보에서 처리할 v3컨트롤러를 하나 찾음
핸들러 어댑터 목록을 찾을 때 v3를 처리할 수 있는 어댑터를 찾아야함 그 때 쓰는 게 MyHandlerAdapter
즉 요청에 맞는 컨트롤러를 찾고 해당 컨트롤러로 처리할 수 있는 어뎁터를 찾는 흐름임
핸들러 어댑터를 쓰는 이유
핸들러 어댑터를 쓰면 프론트 컨트롤러와 핸들러 사이를 연결해 주기 때문에 프론트컨트롤러에서 프론트컨트롤러 인터페이스 코드 변경 없이 다양한 종류의 컨트롤러를 호출할 수 있기 때문
https://www.inflearn.com/questions/750629
핸들러
public interface MyHandlerAdapter {
boolean supports(Object handler); // 파라미터로 받은 컨트롤러를 지원할 수 있는지 아닌지 bool로 반환
ModelView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws ServletException, IOException;
}
- 그 전에는 프론트 컨트롤러에서 컨트롤러를 호출했지만 이제는 어댑터를 통해서 컨트롤러를 호출한다
어댑터 구현
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV3); // 파라미터 컨트롤러가 v3인지 아닌지
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
ControllerV3 controller = (ControllerV3) handler; // handle메서드는 짜피 supports 다음 호출이라 캐스팅해도 ㄱㅊ
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
return mv;
}
프론트 컨트롤러
// 다 지원하기 위해서 컨트롤러 버전이 아닌 Object를 넣음
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());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
}
- 생성자로 멤버 변수 초기화 시키는 메서드 호출
- 컨트롤러 -> 핸들러 : 어댑터를 사용해서 컨트롤러 호출 . 이젠 어댑터가 지원하면 뭐라도 url매핑해서 사용 가능
@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);
MyView view = viewResolver(mv.getViewName());
view.render(mv.getModel(), request, response);
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) { // 반복문을 돌려서 해당하는 어댑터를 꺼내서 반환
if (adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다.handler=" + handler);
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
getHandler로 request에 있는 요청 url을 확인한 후에 handlerMappingMap에서 요청된 url과 매핑되는 컨트롤러를 반환 -> 여기선 v3 save
v3 save를 지원하는 어댑터를 gethandleradapter메서드로 찾아서 반환
해당 어댑터의 메서드 handle을 호출해서 모델뷰를 반환
@Override public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException { ControllerV3 controller = (ControllerV3) handler; // handle메서드는 짜피 supports 다음 호출이라 캐스팅해도 ㄱㅊ Map<String, String> paramMap = createParamMap(request); ModelView mv = controller.process(paramMap); return mv; }
해당 모델 뷰에서 받은 뷰이름을 뷰리졸버에 돌림
뷰에 랜더링 시킴
유연한 컨트롤러 2 -v5
- 프론트 컨트롤러에 몇개만 추가하면 오케
- 해당 설정부분만 외부 주입 시키면 OCP지키는것도 가능할 듯
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());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
어댑터 부분
@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);// 모델뷰를 생성 110v -> 220v mv.setModel(model);
return mv;
}
- v4 컨트롤러의 process 는 string 뷰네임을 반환을 하는데 우리가 쓰는 프론트 컨트롤러에서는 MovdelView를 받기 원함 -> 어뎁터가 필요
- 어댑터가 다 호환될 수 있도록 viewName을 세팅한 ModelView를 만들어서 반환
정리
- v1: 프론트 컨트롤러를 도입
- 기존 구조를 최대한 유지하면서 프론트 컨트롤러를 도입
- v2: View 분류
- 단순 반복 되는 뷰 로직 분리
- v3: Model 추가
- 서블릿 종속성 제거
- 뷰 이름 중복 제거
- v4: 단순하고 실용적인 컨트롤러
- v3와 거의 비슷
- 구현 입장에서 ModelView를 직접 생성해서 반환하지 않도록 편리한 인터페이스 제공
- v5: 유연한 컨트롤러
- 어댑터 도입
- 어댑터를 추가해서 프레임워크를 유연하고 확장성 있게 설계
출처
'Programing > Spring Boot' 카테고리의 다른 글
[MVC-1] 스프링 MVC - 기본 기능 (0) | 2023.08.01 |
---|---|
[MVC-1] 스프링 MVC - 구조 이해 (0) | 2023.08.01 |
[MVC-1] 서블릿 , JSP , MVC패턴 (0) | 2023.07.25 |
[MVC-1] 서블릿 (0) | 2023.07.25 |
[MVC-1] 웹 애플리케이션 이해 (0) | 2023.07.25 |