본문 바로가기
Lecture/JSP & Java

[본격 게시판짜기 Part2.3 Model2 MVC패턴] Controller구성

by cusmaker 2012. 7. 12.
반응형

2012/06/13 - [Lecture/HTML] - [본격 게시판짜기 Part1.1 - 게시판도 HTML부터] 게시글 리스트

2012/06/13 - [Lecture/HTML] - [본격 게시판짜기 Part1.2 - 게시판도 HTML부터] 글입력폼

2012/06/13 - [Lecture/Javascript-기초] - [본격 게시판짜기 Part1.3 - Dom 맛보기 ] 글입력폼 검사

2012/06/13 - [Lecture/Jsp] - [본격 게시판짜기 Part1.4 - HTML-> JSP] form 파라미터 받기

2012/06/26 - [Lecture/SQL / Oracle] - [본격 게시판짜기 Part1.5 JSP > Oracle] 게시판 DB 테이블 생성

2012/06/26 - [Lecture/Jsp] - [본격 게시판짜기 Part1.6 Oracle > JSP] Database 접속 및 Select

2012/07/05 - [Lecture/Jsp] - [본격 게시판짜기 Part1.7 JSP, SQLDeveloper] 게시글 입력 및 리스트조회기능

2012/07/08 - [Lecture/Jsp] - [본격 게시판짜기 Part1.8 JSP 게시글 조회] 게시글 조회기능 및 게시글 삭제

2012/07/10 - [Lecture/Jsp] - [본격 게시판짜기 Part2.1 Model2 MVC패턴] 뷰(View) 코드 분리

2012/07/10 - [Lecture/Jsp] - [본격 게시판짜기 Part2.2 Model2 MVC패턴] Entity Beans의 사용


안녕하세요 cocy입니다.

저번시간에 View의 코드분리에 이어 

MVC패턴의 C , 즉 컨트롤러 구성을 해보도록 하겠습니다.


컨트롤러에서는 java파일 즉, 클래스파일로 넘어가기때문에 더이상 html이나 javascript코드를 볼 수 없습니다.

코딩에 들어가기전에 웹 요청이 시작되어 MVC를 거쳐 어떻게 종료되는지 간단히 그림으로 설명해 보도록 하겠습니다.

그림 1. MVC 패턴 추상화


그림을 보시면 사용자측(Internet Browser)로부터 요청이 날아갑니다.

여기서 말하는 요청이란 write.jsp와 같이 페이지를 요청하는것인데요,

MVC패턴에선 요청을 jsp페이지에 직접하지않고,

jsp페이지를 거치기전에 Action(클래스명)이라고하는 자바파일 실행하게 됩니다.

이 자바파일에서 모든 비지니스로직(로그인, 데이터가져오기, 세션처리, 유효성검증 등등)을 수행하고 

뷰(jsp)에서 필요한 데이터만 jsp페이지로 넘겨줍니다.


그렇기때문에 요청을 .jsp가 아닌 

.do 또는 .action 와같은 방식으로 URL을 요청합니다.

그럼 이렇게 날아온 요청을 컨트롤러에서는 해당 Action에 맵핑시키기위해 

해당 맵핑정보가 들어있는 properties파일(보통의 텍스트파일)을 읽고

해당 클래스를 사용할 수 있게끔 해준뒤,

해당 클래스를 실행합니다.


그럼 Action에서는 데이터베이스에 접속이 필요하면 

이때 모델을 이용됩니다.

모델은 데이터베이스의 접속(트랜젝션관리, 커넥션풀 관리 등)과 

각종 쿼리들을 처리할 수 있도록 메소드 빈 형식으로 구성됩니다.


하여 데이터베이스에 접근한 뒤 필요한 데이터를 반환하거나 수정, 삭제작업을 하고,

다시 Action으로 돌아와서 뷰에서 필요한 데이터들을 jsp페이지로 보내면서

View로 반환합니다. (이를 dispatcher라고 합니다)


로그인을 예로 들어보겠습니다. 


login.do 요청 > 컨트롤러에서 매핑정보 login.do = LoginAction(properties파일) 을 가지고 해당 LoginAction 실행 >

LogionAction에서 모델에 넘어온 id값과 pw값을 가지고 Model에 쿼리실행 > LoginAction에서 로그인처리 > 

로그인 성공 or 실패 페이지(jsp) 로 반환


이해 되시나요?

코드를 작성하시면 확실히 이해가 되실겁니다.


일단 시작부터 이제 .do명령어를 사용하겠습니다.

이를위해 프로젝트가 시작되는 index.jsp 페이지를 list.jsp로 이름을 바꿔줍니다.

그렇다면 index.jsp를 가리키는 모든 링크나 action들을 list.jsp로 바꿔줘야겠죠?

그리고 나서 index.jsp를 다시만들고 다음구문을 적어줍니다.


<script>location.href="list.do";</script>


현재 위치를 list.do로 이동하라는 구문입니다.

그럼 제일처음 list.do를 인식하는부분이 어디일까요?

welcome 파일리스트가 들어있던 web 프로젝트를 설정하는파일인 web.xml입니다.


해당파일을 수정합시다. 웰컴파일리스트 태그가 닫히는 바로아래 다음 코드를 추가해줍니다.


<servlet>
 
   <servlet-name>ControllerAction</servlet-name>
 
   <servlet-class>com.board.controller.ControllerAction</servlet-class>
 
</servlet>
 
 
<servlet-mapping>
 
   <servlet-name>ControllerAction</servlet-name>
 
   <url-pattern>*.do</url-pattern>
 
</servlet-mapping>


서블릿 태그안에 들어있는 내용은 컨트롤러의 위치를 지정해주는 역할을합니다.

서블릿 네임에 적은 ControllerAction을 사용하면 서블릿 클래스에 적힌 경로를 토대로 실행하겠다는 의미입니다.


그리고 그밑에 서블릿 맵핑은 모든 요청이 아닌 .do 요청으로 들어왔을때만 ControllerAction을 실행시킨다는 의미입니다.

*은 모든 텍스트를 뜻합니다.


그렇다면 이제 web.xml에서의 설정이 끝났고

명시한대로 패키지를 만들어주고 ControllerAction 클래스를 작성합시다.


그림 2. web.xml 컨트롤러 설정



이제 컨트롤러 클래스의 내용을 채울텐데요,

조금 복잡하실 수도 있지만, 어려워 마시고 차근차근 따라해보시기바랍니다.

모든내용은 외우실 필요는 없고, 해당코드가 무엇을 위해 어떻게 동작하는지만 이해하시면 됩니다.


※ 그전에 원활한 진행을 위해 com.board.controller 패키지안에 CommandAction.java로 클래스를 하나 추가해주세요

코드는 다음의 코드를 넣어줍니다.


package com.board.controller;
 
 
 
import javax.servlet.http.HttpServletRequest;
 
import javax.servlet.http.HttpServletResponse;
 
 
 
// 요청 파라미터로 명령어를 전달하는 방식의 슈퍼 인터페이스
 
public interface CommandAction {
 
public String requestPro(HttpServletRequest request, HttpServletResponse response) 
throws Throwable;
 
}



위의 CommandAction은 명령어에서 찾은 클래스를 클래스화 시키기위한것으로 단순히 이해하시면 되겠습니다.

아래는 ControllerAction.java의 전체 소스입니다.


package com.board.controller;
 
import java.io.*; 
import java.util.*; 
import javax.servlet.*; 
import javax.servlet.http.*;
 
public class ControllerAction extends HttpServlet {
 
    private Map commandMap = new HashMap(); // 명령어와 명령어 처리 클래스를 쌍으로 저장
 
    public void init(ServletConfig config) throws ServletException {
 
        // Common properties 
        loadProperties("com/board/properties/Command");
 
    }
 
    // properties 설정 
    private void loadProperties(String path) {
 
        ResourceBundle rbHome = ResourceBundle.getBundle(path);// 누구를 실행할지를 rb에
                                                                // 저장.
 
        Enumeration<String> actionEnumHome = rbHome.getKeys();
 
        while (actionEnumHome.hasMoreElements())
 
        {
 
            String command = actionEnumHome.nextElement();
            ;
 
            String className = rbHome.getString(command);
 
            try {
 
                Class commandClass = Class.forName(className); // 해당 문자열을 클래스로
                                                                // 만든다
 
                Object commandInstance = commandClass.newInstance(); // 해당 클래스의
                                                                        // 객체를
                                                                        // 생성
 
                commandMap.put(command, commandInstance); // Map 객체인 commandMap에
                                                            // 객체 저장
 
            catch (ClassNotFoundException e) {
 
                continue// error
 
                // throw new ServletException(e);
 
            catch (InstantiationException e) {
 
                e.printStackTrace();
 
            catch (IllegalAccessException e) {
 
                e.printStackTrace();
 
            }
 
        }
 
    }
 
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
 
        requestPro(request, response); // get방식과 post방식을 모두 requestPro로 처리
 
    }
 
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
 
        requestPro(request, response);
 
    }
 
    // 사용자의 요청을 분석해서 해당 작업을 처리
 
    private void requestPro(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
 
        String view = null;
 
        CommandAction com = null;
 
        try {
 
            String command = request.getRequestURI();
 
            if (command.indexOf(request.getContextPath()) == 0) {
 
                command = command.substring(request.getContextPath().length());
 
            }
 
            com = (CommandAction) commandMap.get(command);
 
            if (com == null) {
 
                System.out.println("not found : " + command);
 
                return;
 
            }
 
            view = com.requestPro(request, response);
 
            if (view == null) {
 
                return;
 
            }
 
        catch (Throwable e) {
 
            throw new ServletException(e);
 
        }
 
        if (view == null)
 
            return;
 
        RequestDispatcher dispatcher = request.getRequestDispatcher(view);
 
        dispatcher.forward(request, response);
 
    }
 
}



소스를 차근차근 살펴보시면 클래스가 실행되면 init함수로부터 명령어 처리를 위해 

com/board/properties/Command  경로로부터 properties파일을 불러온뒤,

이를 맵에 저장합니다.


그럼 해당 경로대로 패키지를 만들어주고 properties파일을 만들어줘야겠죠?

만들어봅시다.

src 경로에 com.board.properties 로 패키지를 만들고

패키지안에 new > file > Command.properties 로 파일이름을 정하고 생성합니다.

파일 안에 내용은 해당 Action이 추가 될때마다 수정되어야하구요,

지금은 list.do 작업을 하고 있으므로 다음과같이 적어줍니다.


/list.do=com.board.action.ListAction


눈치가 빠르신분들은 com.board.action 패키지에 ListAction을 추가해줘야한다는걸 아셨을겁니다.

하지만 컨트롤러의 소스분석이후에 만들도록 하고 

작업이 끝났으면 저장하시고 컨트롤러 소스로 다시 돌아가도록 하겠습니다.


밑의 loadProperties 함수는 properties파일에서 가져온 맵핑정보의 패키지정보를 바탕으로 

클래스화 시킨뒤 리소스 번들이라고 하는 객체에 저장합니다.


post요청이나 get요청이 들어오면 requestPro라는 함수를 호출하여 처리합니다.


requestPro함수에서는 사용자의 요청 URL을 분석하여

리소스 번들에 저장된 해당 액션 객체를 실행합니다.

해당 액션 객체의 실행이 끝나면 액션에서 리턴되는 뷰(파일경로,이름)로 리턴합니다.


이것으로 컨트롤러의 소스 분석이 끝났습니다.



그림 3. command.properties, ListAction


그럼 이제 properties파일에 명시한 List액션을 패키지를 만들고 추가합니다.

추가한 ListAction 클래스를 컨트롤러와의 연계를 위해 위에 만들어놓았던 CommandAction 인터페이스를 구현합니다.


line 3 :     public class ListAction  implements CommandAction {

그리고나서 CommandAction에서 정의한 requestPro를 오버라이딩합니다.

※ 위의 구문을 추가하고나면 3번라인 앞에 전구에 작은 x표시가 달린 버튼이 생깁니다. 

이를 클릭하면 add unimplemented method 라는 메뉴가 생기고 클릭하면 자동으로 코드가 생성됩니다.



그럼이제 ListAction의 내용을 채워야하는데요 

List액션에서 해야될 일은 model1에서 스크립트릿에 이미 모두 작성해놓았습니다.

list.jsp페이지로가서 스크립트릿의 모든내용을 잘라내고 붙여넣기합니다.

list.jsp페이지에는 더이상 스크립트릿이 없어야합니다.(import구문 포함)


에러가 나는 부분들은 대부분 라이브러리들이 포함이 안되엇기때문입니다.

위와 마찬가지로 노란색전구버튼을 클릭하고 필요한 라이브러리를 임포트합니다.

※ out.println구문은 모두 out앞에 System.을 붙여주세요. 이렇게하면 콘솔창에 메세지가 뜨게됩니다.


그리고 마지막으로 리턴타입에 null로 적힌부분을 "list.jsp"로 바꿔주시기바랍니다.


완성된 코드는 다음과 같습니다.


package com.board.action;
 
import java.sql.*;
 
import java.util.ArrayList;
 
import javax.servlet.http.HttpServletRequest;
 
import javax.servlet.http.HttpServletResponse;
 
import com.board.beans.Board;
 
import com.board.controller.CommandAction;
 
public class ListAction implements CommandAction {
 
    @Override
    public String requestPro(HttpServletRequest request,
 
    HttpServletResponse response) throws Throwable {
 
        try {
 
            String driverName = "oracle.jdbc.driver.OracleDriver";
 
            String url = "jdbc:oracle:thin:@localhost:1521:XE";
 
            ResultSet rs = null;
 
            Class.forName(driverName);
 
            Connection con = DriverManager.getConnection(url, "board""board");
 
            System.out.println("Oracle Database Connection Success.");
 
            Statement stmt = con.createStatement();
 
            String sql = "select * from board order by idx desc";
 
            rs = stmt.executeQuery(sql);
 
            ArrayList<board> articleList = new ArrayList<board>(); // Board형
                                                                    // 배열형식으로 선언
 
            while (rs.next()) {
 
                Board article = new Board(); // 데이터들을 담기위해 Board객체에 메모리를 할당하는
                                                // 코드입니다.
 
                article.setIdx(Integer.parseInt(rs.getString("idx"))); // Integer.parseInt는
                                                                        // String형을
                                                                        // int
                                                                        // 형변환
 
                article.setTitle(rs.getString("title"));
 
                article.setWriter(rs.getString("writer"));
 
                article.setRegdate(rs.getString("regdate"));
 
                article.setCount(Integer.parseInt(rs.getString("count")));
 
                articleList.add(article); // 셋팅된 빈을 리스트에 추가합니다.
 
            }
 
            request.setAttribute("articleList", articleList); // 셋팅된 리스트를 뷰에
                                                                // 포워드합니다.
 
            con.close();
 
        catch (Exception e) {
 
            System.err.println("Oracle Database Connection Something Problem.");
 
            System.out.println(e.getMessage());
 
            e.printStackTrace();
 
        }
 
        return "list.jsp";
 
    }
 
}


드디어 컨트롤러의 구성과 리스트액션의 작업이 끝났습니다.

정상적으로 작동하는지 확인해보시기바랍니다.

작업이 길어진관계로 중간에 테스트를 하지못해 에러가 날 수 있습니다. 

차근차근 스탭별로 따라오셨다면 

주소창에 list.do라는 URL과 함께 model1으로 작업했던 list페이지가 나오는것을 확인 하실 수 있을겁니다.



하지만 아직!

MVC의 모든 구조를 작업하지 않았습니다.

바로 Model부분의 구현인데요

현재 ListAction 안에 데이터베이스 접속하는 로직이 구현되어있습니다.

이를 분리하여 Model을 구성해야 진정한 MVC패턴으로 거듭나게 되는데요,

이작업을 다음시간에 해보도록 하겠습니다.

그전에 나머지 Action들도 작업은 과제로 남기도록 하겠습니다.


수고하셨습니다.


다음글 >> http://cusmaker.tistory.com/88