BackEnd/WEB

DispatcherServlet

Leo.K 2022. 6. 23. 18:17
  1. src/main/java에 annotation, controller, filter폴더를 복사
  2. WEB-INF에 web.xml복사

메인 컨트롤러는 DisPatcherServlet(사용자 요청을 최초로 받고, 이를 분석해서 적당한 컨트롤러에게 작업을 지시)이다. 이 컨트롤러가 작업을 지시할 컨트롤러(실제로 일을 하는 컨트롤러)를 초기에 메인 컨트롤러가 생성될 때 함께 생성한다. 이를 자동으로 하게 만드려고 web.xml파일에 init-param을 지정하는 것이다.

원리를 아는 것도 중요하지만 사용방법을 익혀라!

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