이번 시간에는 Test Case로 동작원리를 살펴보려고 한다.
전체적인 틀을 먼저 살펴보겠다.
http://localhost:8000/board2/boardDetail.gd3?b_no=5
이 url로 조건분기를 진행하겠다.
@WebServlet("*.gd3")
.gd3을 가로채서 ActionSupport 서블릿에서 진행한다.
upmu = command.split("/");
이전에는 String reulst = null로 받았지만, 모든 Case를 받기 위해서 Object로 변경되었다.
그렇다면 object로 뭘 받는것일까?
[Pojo2 - ActionServlet] 코드
if("board".equals(upmu[0])) {
logger.info("workname - board - execute호출");
req.setAttribute("upmu",upmu);
result = controller.execute(req, res);
}
[Pojo3 - ActionSupport]
obj = HandlerMapping.getController(upmu, req, res);
1. HandlerMapping클래스인 getController 메소드를 실행시킨다.
2. 중요한 부분은 파라미터로 넘어가는 값이 upmu / req / res 라는 점이다.
큰 골자부터 살펴보자. HandlerMapping
클래스를 왜 설계했는가?
직관적이지 않던, if문을 전담하는 클래스를 따로 구성한다.
컨트롤클래스(board2Controller)에서 기존에 if문으로 분기처리 되었던 부분을 -> 메소드로 변경해보기 위해서.
그렇다면 왜 req,res를 지원하는 메소드여야 하는가?
HashMapBinder
, Logic
처리 ModelAndView
에 전부 이 값이 필요하기 떄문이다.
이 부분에서 중간 경유지 와 같은 역할을 한다.
if("board2".equals(upmu[0])) {
else if("boardDetail".equals(upmu[1])) {
obj = controller.boardDetail(req, res);
if(obj instanceof ModelAndView) {
return (ModelAndView)obj;
}
}
"board2".equals(upmu[0])
해당 조건에 걸리게 되어 조건문 스코프 안으로 들어간다.
upmu[0] 배열은 어떻게 사용할 수 있는가? 파라미터를 통해 원본값을 넘겨받았으니 사용가능하다.
obj = controller.boardDetail(req, res);
이 부분을 통해서 req,res를 넘겨준다.(원본값)
그 때, obj는 controller.boardDetail이 끝날 떄 까지 stop한다.
@Override
public Object boardDetail(HttpServletRequest req, HttpServletResponse res) {
logger.info("boardDetail");
List<Map<String ,Object>> bList = null;//nList.size()=1
// NoticeLogic의 메소드 호출 - 객체주입 - 내가(책임) 아님 스프링(제어역전)
//select * from notice where n_no=5;
HashMapBinder hmb = new HashMapBinder(req);
hmb.bind(pMap);
bList = bLogic.boardDetail(pMap);
logger.info(bList);
ModelAndView mav = new ModelAndView(req);//WEB-INF/jsp/[[[board2/boardDetail]]].jsp
mav.addObject("bList", bList);
mav.setViewName("board2/boardDetail");
return mav;
}
전체적인 골자는 Select[전체조회] 일 떄와, 상세조회일때, 응답페이지가 달라지기 때문에 메소드를 분리하여 설계했다.Select
일 경우에는 배포위치가 WebApp이기 때문에 경로를 처리하는 부분이 달라진다.
path = "forward:board/boardList";
Detail
인 경우에는 WEB-INF일때
/WEB-INF/jsp/workname/xxxxx[메소드이름] or upmu[1].jsp
로 설정한다.
이 또한 메소드 파라미터를 통해서 원본값 (req,res)를 받는다.
HashMapBinder hmb = new HashMapBinder(req)를 통해서 사용자가 입력 한 값에 대한 Map<>
처리는 완료
bLogic으로 갔다가 Dao 처리 후 Oracle 값을 받고 돌아온 값이 bList에 담겨있다.
Detail인 경우 경로처리가 달라지기 때문에, ModelAndView
클래스를 설계하여 사용할 것이다.
다음은 ModelAndView 클래스를 굳이 왜 사용할까?
[Pojo2 코드]
else if("boardDetail".equals(upmu[1])) {
logger.info("boardDetail");
List<Map<String ,Object>> bList = null;//nList.size()=1
// NoticeLogic의 메소드 호출 - 객체주입 - 내가(책임) 아님 스프링(제어역전)
//select * from notice where n_no=5;
hmb.bind(pMap);
bList = bLogic.boardList(pMap);
//원본에다가 담아 두자
req.setAttribute("bList", bList);
path="forward:/board/boardDetail.jsp";
}
req.setAttribute를 사용하지 않고, 데이터만 저장한다. 그리고 이후에 path를 경로 설정하여 처리하지만,
ModelAndView는 데이터와 이동하고자 하는 ViewPage를 같이 저장한다.
xxx Controller에서 if문으로 분기하고 있는데, 이 부분을 해결하기 위해서 HandlerMapping을 구현했다.
xxxController는 서블릿이 아니다. Controller 인터페이스의 추상메소드로 구현 된 것 뿐이다. --> 실질적인 주인은 ActionServelt
Select로 n건을 조회하고 싶을 떈, (List<Map>)
으로 받아야함.
ModelAndView mav = new ModelAndView(req);//WEB-INF/jsp/[[[board2/boardDetail]]].jsp
mav.addObject("bList", bList);
mav.setViewName("board2/boardDetail");
여기서 ModelAndView를 인스턴스화한다.
req를 넘겨주는 이유는 무엇일까?
req는 사용자에 대한 정보를 저장하고 있다.(form)
그 값을 ModelAndView클래스로 넘겨주고 생성자로 받게 하면 req는 원본값을 유지한다.
mav.addObject를 하게 되면, ModelAndView 클래스안에 메소드로 정의되어 있는 addObject
를 통해서 bList(오라클로부터 받은 사용자에 대한 디비정보)를 넘겨주게 된다.
HttpServletRequest req = null;//나는 어디서 주입을 받게 되나요?
List<Map<String,Object>> list = new ArrayList<>();
//컨트롤 클래스에서 결정된 화면의 이름을 담을 변수 선언
String viewName = null;
public ModelAndView(HttpServletRequest req) {
this.req = req;
}
생성자로부터 값을 유지하고
String viewName으로 경로에 대한 값을 저장할 수 있는 전역변수를 선언했다.
그리고
[Board2Controller.java]
ModelAndView mav = new ModelAndView(req);//WEB-INF/jsp/[[[board2/boardDetail]]].jsp
mav.addObject("bList", bList);
mav.setViewName("board2/boardDetail");
return mav;
[ModelAndView.java]
HttpServletRequest req = null;//나는 어디서 주입을 받게 되나요?
List<Map<String,Object>> list = new ArrayList<>();
//컨트롤 클래스에서 결정된 화면의 이름을 담을 변수 선언
String viewName = null;
public void addObject(String name, Object obj) {
Map<String,Object> pMap = new HashMap<>();
pMap.put(name, obj);
req.setAttribute(name, obj);
list.add(pMap);
}
[Board2Controller]에서 mav.addObject("bList",bList);
를 통해서 ModelAndView - addObejct(name,obj)
메소드 인자로 전달한다.
addObejct("bList",bList)를 받게되고 이 값을 Map 객체를 생성하여 list.add(name,obj)로 list에 담는다.
여기서 N건을 넘기고 싶다면 req.setAttribute(name,list)넘기면 된다.
한 건을 넘기고 싶다면, req.setAttribute(name,obj)를 넘거야 된다.
다시 돌아와서
ModelAndView mav = new ModelAndView(req);//WEB-INF/jsp/[[[board2/boardDetail]]].jsp
mav.addObject("bList", bList);
mav.setViewName("board2/boardDetail");
나머지 이 부분을 수행하는데, mav.addObject("bList",bList);를 하면
bList : 사용자에 대한 디비정보를 쥐고 있다. 그것을 ModelAndView 클래스 addObject 메소드에 파라미터로 넘겨서 값을 "저장"한다.
같은말은 ? = addObject메소드에서 (String name, Object obj)는 Key
와 Value
값이다.
그 값을 ModelAndView에서 전역변수 list로 관리한다.
req.setAttribute(name,obj)를 하게 된다면, 파라미터 기준 req.setAttribute("bList",bList)를 넘긴다.
req.getAttribute로 로 받아, 이 값을 jsp에서 사용가능하다.
위에 적혀있는 코드를 수행하고 나면 return으로 mvn을 하게된다. 이 떄, mvn에는 "경로"와 "list" 객체를 쥐고 있다.
그리고 다시 mav를 쥐고 있는 값 return을 통해서
handlerMapping으로 가고
else if("boardDetail".equals(upmu[1])) {
obj = controller.boardDetail(req, res);
if(obj instanceof ModelAndView) {
return (ModelAndView)obj;
}
else if(obj instanceof String) {
return (String)obj;
}
}//////////end of boardDetail메소드 호출
obj = mav를 쥐고있다. (경로 + list)를 갖고있다.
obj = HandlerMapping.getController(upmu, req, res);
다시 여기로 돌아와서 obj = mav를 갖고있다. + 타입(ModelAndView)이다.
지금부터 다른 클래스를 호출하는 것은 끝났다. 이젠, 경로 설정 및 object를 슬라이싱해서 어떤 jsp를 호출할지, 정하는 부분이다.
이 부분을 정할 때, "타입"으로 Object를 나눠서 처리한다.
앞쪽에 위치한 부분은 "단순히 paveMove 배열에 값을 넣기위한 조건 분기이다." 핵심을 잃지말자.
타입을 살펴보면, 현재까지
String : Json, image(avatar.png) , path
ModelAndView : Detail
byte[] : Quill editor (마임타입이 enctype)
이렇게 큰 골자로 조건을 분기한다.
여기서 괄목해야 될 부분은 String이다.
Contains(":") : forward, sendRedirect
콜론을 가지고 분기하여 paveMove[]에 값을 담는다.
paveMove[0] : (forward/sendRedirect)✅
paveMove[1] : board2/boardList
Contains("/") : WEB-INF
WEB-INF인 경우에는 경로설정 자체를 board/boardList 이런식으로 앞에 (:)이 붙지 않게 되어 있다.(설계를 그렇게 함)
"/"기준으로 paveMove에 값을 담게된다.
else (Json/파일이름)
파일이름 같은 경우이기 때문에,
paveMove : new String[1];
정리하면,
((forward,sendRedirect)✅ , board/boardList) : forward,sendRedirect인경우
(board,boardList) : WEB-INF
(avatar.png) : else
타입이 ModelAndView인 경우(Detail)인 경우
WEB-INF로 처리해야되기 때문에
mav = (ModelAndView)obj를 받는다.(list,viewName)를 쥐고 있다.
pageMove[0] = ""
pageMove[1] = "board2/boardDetail"
이렇게 나오게 된다.
정리하면, ModelAndView인 경우에는 무조건 ("","board2/boardDetail")
로 담기게 된다.
항상 NullPointException을 예방하기 위해서 방어적인 프로그래밍을 해야한다. 그렇기 떄문에 if문으로 Null체크를 반드시 해주고,
앞에선 pageMove에 대한 조건 분기를 "
[pojo2 - ActionServlet]
if("redirect".equals(pageMove[0])) {
res.sendRedirect(path);//board/boardList.jsp
//forward 처리시와 동일한 컨벤션을 적용하기 위해서 접두어와 접미어를 붙이는 과정에서 오류가 발동함.
//현재 구조상 입력|수정|삭제 모두 처리 성공시 목록페이지로 응답이 나가도록 설계가 되어 있다는 것을 간과함
//res.sendRedirect("/"+path+".jsp");//board/boardList.gd2.jsp
}//end of sendRedirect
else if("forward".equals(pageMove[0])) {
RequestDispatcher view = req.getRequestDispatcher("/"+path+".jsp");
view.forward(req, res);
}//end of forward
else {//콜론이 없는 경우에 실행되는 코드임 - WEB-INF
path = pageMove[0] +"/"+pageMove[1];//board/boardList
// /WEB-INF/jsp/board/boardList.jsp -> spring ViewResolver
RequestDispatcher view = req.getRequestDispatcher("/WEB-INF/jsp/"+path+".jsp");
view.forward(req, res);
이렇게 진행했지만,
지금은
[ActionSupport]
if(pageMove !=null && pageMove.length == 2) {
logger.info("pageMove 원소의 갯수가 2개 일 때");
String path = pageMove[1];
if("redirect".equals(pageMove[0])) {
res.sendRedirect(path);
}else if("forward".equals(pageMove[0])) {
RequestDispatcher view = req.getRequestDispatcher(path);
view.forward(req, res);
}else {
// /WEB-INF/jsp/board/boardList.jsp -> spring ViewResolver
RequestDispatcher view = req.getRequestDispatcher("/WEB-INF/jsp/"+path+".jsp");
view.forward(req, res);
}
}///////////////////end of if
else if(pageMove !=null && pageMove.length == 1) {//quill editor 이미지 선택시 파일이릌반환
res.setContentType("text/plain;charset=utf-8");
PrintWriter out = res.getWriter();
out.print(obj);
return;
}
//JSON포맷으로 반환되는 값을 출력하기 - @ResponseBody, @RestController역할 재현
else {
res.setContentType("text/plain;charset=utf-8");
PrintWriter out = res.getWriter();
out.print(obj);
return;
}
pageMove의 길이가 2일 땐,(pageMove.length == 2)
3가지 경우밖에 없다.
redirect
forward
WEB-INF(else)
pageMove의 길이가 1일 땐, pageMove.length == 1
else(paveMove의 길이가 1,2 둘다 아닐 떈)
하지만 http://localhost:8000/board2/boardDetail.gd3?b_no=6 인 경우에는
pageMove("", board2/boardDetail)
그렇게 되면 forward방식으로 url은 고정이며, 페이지에 대한 처리는 완료되어 나타난다.
if(pageMove !=null && pageMove.length == 2) {
logger.info("pageMove 원소의 갯수가 2개 일 때");
String path = pageMove[1];
if("redirect".equals(pageMove[0])) {
res.sendRedirect(path);
}else if("forward".equals(pageMove[0])) {
RequestDispatcher view = req.getRequestDispatcher(path);
view.forward(req, res);
}else {
logger.info("여길타야되는거아닌가?");
// /WEB-INF/jsp/board/boardList.jsp -> spring ViewResolver
RequestDispatcher view = req.getRequestDispatcher("/WEB-INF/jsp/"+path+".jsp");
view.forward(req, res);
}
}///////////////////end of if
이 부분에서 else부분을 실행했다.
Pojo1 -AcionForward에선
private String path
private String isRedirct
로 경로와 forward와 sendRedirect를 관리했고,
Pojo2 - ActionForward를 없애고, String으로 받았다.
Pojo3 - ModelAndView 생성 및 HandelrMapping 생성
obj로 리턴타입에 대한 제한적인 부분을 해결했고, ModelAndView클래스를 생성해서 Detail
인 경우에 객체 공유 및 WEB-INF
에 대한 경우의수를 추가하여 접근에 대한 문제를 해결했다.
if문에 대한 분기처리도(컨트롤러) 메소드로 해결함.
req, res Depth