[유연한 컨트롤러1 - v5] Java code로 Spirng MVC 핵심 기능 만들어 보기
# 인프런 김영한의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 개인적으로 정리한 글입니다.
과거 뭔가 겉핥기식으로 알았던 부분이 5회독 만에 완성되기 시작했다.
코드를 하나하나 분해해서 설명하겠다.
핵심 코드
<<역할과 구현을 나눈다 .JAVA 객체지향의 핵심.>>
1. 어댑터 목록을 찾는다(ControllerV3HandlerAdapter를 찾게된다.)
2-1. 찾은 어댑터handler, servletRequest, servletResponse을 넘기고
// 여기서는 ControllerV3가 handler로 사용이 된다.
@override handle 메소드를 사용해서 paramMap를 만든다.
2-2. paramMap을 가지고 (MemberFormControllerV3, MemberListControllerV3, MemberSaveControllerV3 중 1개)의
@override를 process메소드를 사용해서 ModelView로 viewName을 넘긴다.
3.넘겨진 modelview 의 viewname을 viewResolver 메소드를 통해
논리적 위치를 물리적 위치로 변환시킨다.
4. myview의 render 메소드를 통해 Http를 반환시킨다.
과정
0. 클라이언트로 HTTP요청이 온다.
1. FrontController에서 고객의 Need를 어떤 Handler를 사용해서 제공해야하는지 조회해본다.
3. 핸들러를 처리할수 있는 핸들러 어댑터를 조회한다.
4. 핸들러 어댑터를 가져온다.
5. 핸들러 어댑터에서 핸들러의 정보를 가져온다. == 핸들러를 호출한다.(v3 와 v4 handler를 Adapter가 가져올것이다)
6. 핸들러의 정보들을 ModelView 형식으로 반환한다.
7. viewResolver에서 완성된 MyView를 반환한다.
8. MyView에서 반환된 정보에 render 함수를 이용하여 HTML을 반환한다.
살펴보기(심화)
@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(); // initHandlerMappingMap 에는 Map 형식으로 핸들러 매핑을 초기화한다.
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());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object handler = getHandler(request); // handlerMappingMap에서 controller 가져오기 여기서 handler는 controller와 같은 의미로 사용된다.
if (handler == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
//어탭터 목록을 찾아서 가져오는 작업
//ControllerV3HandlerAdatper를 가져오겠지? //adapter = ControllerV3HandlerAdatper
MyHandlerAdapter adapter = getHandlerAdapter(handler);
ModelView mv = adapter.handle(request, response, handler);
MyView view = viewResolver(mv.getViewName());
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. HashMap 과 List를 사용해서 Adapter를 담을 공간과, Controller를 담을 공간을 나눈다.
이제 Controller는 handler라고 부르겠다.
private final Map<String, Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
2. FrontControolerServiceV5를 구현시 Adapter를 담을 공간과, handler를 담을 공간을 만든다.
handler를 담을 공간을 initHandlerMappingMap
adapter를 담을 공간을 initHandlerAdapters
라고 명명하겠다. // 객체 지향적인 코딩 역할을 나눠준다.
public FrontControllerServletV5(){
initHandlerMappingMap(); // initHandlerMappingMap 에는 Map 형식으로 핸들러 매핑을 초기화한다.
initHandlerAdapters(); // 어댑터를 초기화 한다.
}
3. initHandlerMappingMap에는 회원등록을 하는 폼, 회원 저장폼, 회원 목록폼 class를 넣어준다.
initHandlerAdapters에는 ControllerV3HandlerAdapter를 넣어 준다.
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());
}
4. HttpServlet을 extends하였기 떄문에 @Override를 시작해보겠다.
1. 어댑터 목록을 찾는다(ControllerV3HandlerAdapter를 찾게된다.)
MyHandlerAdapter adapter = getHandlerAdapter(handler);
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for(MyHandlerAdapter adapter : handlerAdapters){
if (adapter.supports(handler)){
return adapter;
}
}
throw new IllegalArgumentException("handler adapter를 찾을수 없습니다");
}
2-1. 찾은 어댑터handler, servletRequest, servletResponse을 넘기고
// 여기서는 ControllerV3가 handler로 사용이 된다.
@override handle 메소드를 사용해서 paramMap를 만든다.
ModelView mv = adapter.handle(request, response, handler);
-해당 매소드 있는 장소-
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
ControllerV3 controller = (ControllerV3) handler; // handler가 V3를 Casting 받아.
Map<String, String> paramMap = createParamMap(request); //request에서 파라미터 정보를 받아서
// paramMap으로 전달.
ModelView mv = controller.process(paramMap); // /ModelView로 paramMap에 전달된 내용들을 반환시켜 보내.
return mv;
}
//HttpServletRequest에서 파라미터 정보를 꺼내서 Map으로 변환한다.
// 그리고 해당 Map( paramMap )을 컨트롤러에 전달하면서 호출한다.
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;
}
2-2. paramMap을 가지고 (MemberFormControllerV3, MemberListControllerV3, MemberSaveControllerV3 중 1개)의
@override를 process메소드를 사용해서 ModelView로 viewName을 넘긴다.
방금 본 코드에서
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
ControllerV3 controller = (ControllerV3) handler; // handler가 V3를 Casting 받아.
Map<String, String> paramMap = createParamMap(request); //request에서 파라미터 정보를 받아서
// paramMap으로 전달.
ModelView mv = controller.process(paramMap); // /ModelView로 paramMap에 전달된 내용들을 반환시켜 보내.
return mv;
}
controller.process 기능이 있다.
ModelView mv = controller.process(paramMap); // /ModelView로 paramMap에 전달된 내용들을 반환시켜 보내.
이 기능은
MemberFormControllerV3, MemberListControllerV3, MemberSaveControllerV3 중 1개의 override된 메서드 이다.)
public class MemberFormControllerV3 implements ControllerV3 {
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
}
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;
}
}
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;
}
}
3.넘겨진 modelview 의 viewname을 viewResolver 메소드를 통해
논리적 위치를 물리적 위치로 변환시킨다.
MyView view = viewResolver(mv.getViewName());
private MyView viewResolver(String viewName){
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
4. myview의 render 메소드를 통해 Http를 반환시킨다.
view.render(mv.getModel(), request, response);
-해당 매소드 있는 장소-
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);
}
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));
}
}
객체 지향이라는 설계 즉 interface 때문에 초보 개발자인 나에게는 정리가 잘 되지 않았다.
과거 interface 1개에 여러 class를 override 했다면 ,
interface 여러개가 각자 연결 되면서 작용하니 어려울수 밖에 없다.
깊게 공부하자!