- src/main/java에 annotation, controller, filter폴더를 복사
- WEB-INF에 web.xml복사
메인 컨트롤러는 DisPatcherServlet(사용자 요청을 최초로 받고, 이를 분석해서 적당한 컨트롤러에게 작업을 지시)이다. 이 컨트롤러가 작업을 지시할 컨트롤러(실제로 일을 하는 컨트롤러)를 초기에 메인 컨트롤러가 생성될 때 함께 생성한다. 이를 자동으로 하게 만드려고 web.xml파일에 init-param을 지정하는 것이다.
원리를 아는 것도 중요하지만 사용방법을 익혀라!

- 프레임워크 초기 실행 (혹은 restart)
- DispatcherServlet 생성
- DispatcherServlet init()
- web.xml에 있는 <init-param>태그에 정의된 <param-value>값을 읽어옴
- DispatcherServlet이 만들어주는 것이다. 리플렉션을 사용해 읽어온 이름으로 클래스를 만들고(작업 컨트롤러이름), 객체(실제 작업을 하는 컨트롤러 객체 ex-> TestContoller의 객체) 생성
- 컨트롤러의 메소드 목록을 저장한다.(컨트롤러의 메서드 명은 반드시 url-pattern에 맞게 작성, request와 response를 반드시 인자로 받아야 한다. 적어도 이 프레임 웤에서는)
- 메소드를 호출한다. -> invoke
- 메소드 결과를 받아서 포워딩 or 리다이렉트한다.

1. DispatcherServlet이라는 메인 컨트롤이 처음 생성될 때, init()가 실행된다. -> 첫 시작 or restart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<!-- Servlet mapping -->
<servlet>
<!-- 서블릿 이름 -->
<servlet-name>DispatcherServlet</servlet-name>
<!-- 서블릿의 경로 -->
<servlet-class>controller.DispatcherServlet</servlet-class>
<init-param>
<!-- 메인 컨트롤러(서블릿)이 생성될 때 함께 생성될 작업 컨트롤러 정보 -->
<param-name>action</param-name>
<param-value>mycontroller.VisitController</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
|
cs |
6 : web.xml파일에 정의한 <param-value>값을 읽어와서 action에 저장한다.
7: 값이 여러개라면 ,를 기준으로 문자열을 분리해서 배열로 저장한다.
16: 혹시라도 읽어 온 <param-value>값에 공백이나 엔터가 있으면 클래스를 생성할 때 에러가 나므로 제거한다.
21: 읽어 온 이름을 사용해서 클래스를 생성한다. -> 필자가 실습한 내용을 기반으로 하면 c = class VisitController라는 이름의 클래스가 생성될 것이다.
24: 생성한 클래스의 객체를 생성한다. ob
32: 생성한 클래스 c에 정의된 메소드를 method_array에 저장한다. 메소드가 하나만 있을리가 없으니 배열 형태로 저장. 메소드의 이름을 저장하는 것이 아니라 메소드에 대한 정보를 저장하는 것 같음. 아래에 자세한 풀이가 있으니 계속 읽어보자.
34~41: 배열에 있는 값을 하나씩 꺼내서 전역으로 생성한 method_list에 추가해놓는다.
map에 저장할 key값과 value를 구한다.
key = 클래스 이름.메서드명
value = 위에서 생성한 클래스 c의 객체
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
@Override
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
super.init(config);
String action = config.getInitParameter("action");
// action = "action.TestController,action.TestController2";
String [] action_array = action.split(",");
// action_array = {"action.BookController","action.TestController\r\n"};
for(String action_name : action_array) {
// action_name = "action.TestController";
try {
//공백 및 엔터 제거
action_name = action_name.trim().replaceAll("\r\n", "").trim();
//Reflection
//이름(action_name)으로 객체를 만들기 위한 방법
Class c = Class.forName(action_name);
System.out.println("----------[before]------------------");
Object ob = c.newInstance();
System.out.printf("--[%s] 생성--\n",action_name);
System.out.println("----------[after ]------------------");
//방법1
//object_list.add(ob);
Method [] method_array = c.getDeclaredMethods();
for(Method method : method_array) {
method_list.add(method);
//방법2
String key = method.getDeclaringClass().getName() + "." + method.getName();
object_map.put(key, ob);
//System.out.println(method.getDeclaringClass().getName() + "." + method.getName());
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
|
cs |
2. 생성 및 초기화 이후에 들어온 요청에 대해서는 서블릿의 생명주기에 따라서 바로 서비스를 할당한다.
6: 요청사항이 담겨 있는 URI를 읽어 들인다. "/2022_0623_MVCPatternTest/view.do"의 형태로 파라미터는 읽히지 않음.
12: init()에서 생성 및 저장한 메소드 리스트를 하나씩 꺼내어 확인한다.
14: 만약 메소드에 RequestMapping어노테이션이 존재한다면,,, 19~54행의 명령을 수행한다.
16:메서드의 어노테이션 정보를 annotation에 저장한다. annotation.value() => VisitController의 메소드위에 정의한 값을 읽어온다. @RequestMapping("visit/insert_form.do")이거라면 "/visit/insert_form.do"가 반환된다.
19: 만약 요청 정보로 들어온 URI에 annotation.value()값이 포함되어 있다면,,,
36: init()에서 한 것과 같은 방식으로 map의 key값을 구한다. 맵은 key를 알아야 value를 구할 수 있다.
37: 작업 컨트롤러에 정의된 메서드를 찾아갈 수 있도록, method_list에 저장한 method를 invoke(호출)하는데, request, response그리고 클래스에 정의된 메서드 이름을 인자로 전달한다. 메서드 이름으로 찾아가기 때문에 작업 컨트롤러에서 메서드 명을 annotation.value()값에서 ".do"를 뺀 이름으로 명명하는 것 같다.
결과 값으로 String을 받는데, 이 값에 따라서 3가지 경우의 수로 나뉘어 진다.
[forwarding] 79~81행
invoke 할 때, request와 response객체를 같이 보냈고, 작업 컨트롤러가 작업을 마치고 데이터를 request에 바인딩 해주었기 때문에, 메인 컨트롤러는 포워딩만 하면 된다.
[redirect] 73~78
반환 받은 문자열에 "redirect:"라는 값이 포함되어 있다면 이 값을 지우고, 남은 값으로 리다이렉트 한다.
[responseBody]
반환 되는 forward_page의 값이 뷰 정보가 아니라 데이터 자체가 넘어온다. 이를 메인 컨트롤러가 직접 출력해준다.
41행을 보면 하나의 조건문이 더 있다. 메소드위에 정의된 어노테이션 중에서 @ResponseBody()이 있는지 체크하는 것이다. 이 어노테이션이 존재한다면 annotation.produces()값을 가져온다. 까먹었을 까봐 하는 말이지만, annotation은 @RequestMapping에 대한 정보를 저장했다. 결과값은 기본적으로 정의한 "text/html;charset=utf-8;"이 값이 나올테지만, 아래와 같은 방법으로 어노테이션을 추가할 때, 직접 지정할 수도 있다.그러면 44행에서는 기본값이던 새로 재정의했던 저장되어 있는 값을 가져와서 ContentType에 저장한다.
@RequestMapping(value="/visit/check_pwd.do", produces="text/json; charset=utf-8;")
@ResponseBody()
64~70: bResponseBody값이 true라면 메인 컨트롤러가 직접 해당 페이지로 값을 출력해준다. -> response.getWriter().print()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
//URI Pattern분석
String uri = request.getRequestURI();
String forward_page="";
boolean bResponseBody=false;
String contentType="";
for(Method method : method_list) {
if(method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
// uri="/2020_0730_MVCFramework/list.do"
// annotation.value()="/list.do"
if(uri.contains(((RequestMapping)annotation).value())){
try {
/*
//방법1 (모든 메소드를 비교해본다.)
for(Object ob :object_list) {
try {
//invoke실패: ob method를 포함한객체가 아닐때
forward_page = (String) method.invoke(ob, request,response);
break;
}catch(Exception e) {
//e.printStackTrace();
}
}
*/
//방법2(특정 객체가 포함된 메소드를 호출(call(x), invoke(o))한다.)
String key = method.getDeclaringClass().getName() + "." + method.getName();
forward_page = (String) method.invoke(object_map.get(key), request,response);
//어노테이션에 responsebody값이 있다면,,,,
if(method.isAnnotationPresent(ResponseBody.class)) {
//RequestMapping어노테이션에 정의된 produces값을 읽는다.
contentType = ((RequestMapping)annotation).produces();
bResponseBody = true;
}
break;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//forward or redirect내용이 없으면 끝내라...
//if(forward_page.isEmpty())return;
//바인딩 된 뷰 정보가 아니라 데이터를 직접 받은 경우 메인 컨트롤러가 직접 데이터를 사용자에게 응답한다.
if(bResponseBody) {
response.setContentType(contentType);
response.getWriter().print(forward_page);
return;
}
//접두어를 갖고 있다면 접두어를 지우고 리다이렉트 한다.
if(forward_page.contains("redirect:")) {
String redirect_page = forward_page.replaceAll("redirect:", "");
response.sendRedirect(redirect_page);
}else {
//forward
RequestDispatcher disp = request.getRequestDispatcher(forward_page);
disp.forward(request, response);
}
}
|
cs |
[ 환경설정 ]
model2를 이 프레임웤으로 바꿀때는 mymvc.jar파일을 lib에 추가하고 web.xml파일을 덮어쓰기 한다.
web.xml에 생성되는 클래스에 대해서 param-value값만을 사용할 컨트롤러 이름으로 바꾼다.
'BackEnd > WEB' 카테고리의 다른 글
모든 개발자를 위한 HTTP 웹 기본 지식_인터넷 네트워크 (0) | 2023.03.24 |
---|---|
국비_Day90 (0) | 2022.07.11 |
MVC_국비Day79 (0) | 2022.06.23 |
MyBatis_SubQuery_국비Day75 (0) | 2022.06.17 |
mybatis_검색기능_국비Day74 (0) | 2022.06.16 |