PC

블로그 이미지

DAVID

160429: 34회차

Programming 2016. 4. 29. 11:59

종료하기 전 티스토리 네이버 로그아웃 할 것


1. 툴

동일

추가시: 


2. 폴더

동일

추가시:


3. 사용할 사이트

동일

추가시:


4. 공부하는 것


오라클 오류나서 

http://bubblecat.co.kr/103


로 고쳤음




2. properties 이용 클래스 

>>>>> src/test/properties/PropertyTest.java 

package test.properties2; 

import java.io.File; 
import java.io.FileInputStream; 
import java.io.IOException; 
import java.sql.Connection; 
import java.sql.DriverManager; 
import java.sql.PreparedStatement; 
import java.sql.ResultSet; 
import java.util.Iterator; 
import java.util.Properties; 

public class PropertyTest { 

    public static void main(String[] args) { 
        Properties prop = new Properties(); 
         
        FileInputStream fis = null;   // 파일을 읽어 오는 역활을 합니다. 
        //  경로  산출 
        File file = new File("jdbc.properties"); 
        System.out.println("절대경로: " + file
.getAbsoluteFile()); 
                
        try { 
            fis = new FileInputStream(file); 
            //속성 파일을 객체로 로딩, 키와 문자열 값으로 저장됩니다. 
            prop.load(fis); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } finally { 
            if (fis != null) try { fis.close(); } catch(IOException ex) {} 
        } 
         
        // properties 객체에서 키목록을 추출합니다. 
        // driver, url, account, password 
        Iterator keyIter = prop.keySet().iterator(); 
        while( keyIter.hasNext() ) { 
            String key = (String)keyIter.next();  //키 추출 
            String value = prop.getProperty(key); //키에 따른 값 추출 
            System.out.println(key + "=" + value); 
        } 
         
        Connection con = null; 
        PreparedStatement pstmt = null; 
        ResultSet rs = null; 
        String sql = ""; 
        String driver = ""; 
        String url = ""; 
        String account = ""; 
        String password=""; 
         
        driver = prop.getProperty("driver"); 
        url = prop.getProperty("url"); 
        account = prop.getProperty("account"); 
        password = prop.getProperty("password"); 

        try{ 
            Class.forName(driver); 
            con = DriverManager.getConnection(url, account, password); 
            sql = "SELECT count(*) cnt FROM tab"; 
            pstmt = con.prepareStatement(sql); 
            rs = pstmt.executeQuery(); 
            if (rs.next()){ 
                System.out.print(account + " 계정의 테이블 갯수: "); 
                System.out.println(rs.getInt("cnt")); 
            } 
        }catch(Exception e){ 
            System.out.println(e); 
        }         
    } 
} 




▷ 아래의 속성 파일을 이용해 사칙연산을 수행하는 자바 프로그램을 
   작성하세요. 

   >>>>> calc.properties 
   su1 = 50 
   su2 = 10 

    
   >>>>> Calc.java 




3. 문자열 추출 

>>>>> StringTest.java 

package test.properties; 

public class StringTest { 

    public static void main(String[] args) { 
        String mapping = "/mvc/hello.do"; 
         
        // 'hello.do' 문자열의 추출 
        System.out.println(mapping); 
        System.out.println("-----------------------"); 
        System.out.print(mapping.charAt(0));   // '/' 
        System.out.print(mapping.charAt(1));   // 'm' 
        System.out.print(mapping.charAt(2));   // 'v' 
        System.out.println(mapping.charAt(3)); // 'c' 
        System.out.println("-----------------------");         
        System.out.println("mapping.substring(5): " + mapping.substring(5)); 
        System.out.println("-----------------------");         
         
        // 문자열이 '/mvc'로 시작하는지 검사하여 boolean값 리턴 
        System.out.println("startsWith: " + mapping.startsWith("/mvc")); 

        // 문자열이 '/mvc'로 끝나는지 검사하여 boolean값 리턴 
        System.out.println("endsWith: " + mapping.endsWith("/hello.do"));         
         
        // 마지막으로 "/"문자가 나타난 index 값 리턴 
        int index = mapping.lastIndexOf("/"); 
        System.out.println("index: " + index); 
         
        System.out.println("substring(index+1): " + mapping.substring(index+1)); 
         
        System.out.println("substring(0, 3): " + mapping.substring(0, 3));   
         
    } 
} 





>>>>> StringTest2.java 

package test.properties; 

public class StringTest2 { 

    public static void main(String[] args) { 
         
        String str = "mail@domain.com"; 
     
        pl("ⓐ '@'문자가 있는지의 여부를 출력하는 루틴 제작"); 
        pl(str.indexOf('@'));      // 4 
        pl(str.indexOf('X'));      // 존재하지 않음으로 -1 
        pl(str.indexOf("domain")); // domain 문자열의 시작위치 5 
        pl(str.indexOf('.'));      // 11 
        pl(str.length());          // 15 
        pl(str.replaceAll("domain", "www.java")); // mail@www.java.com 

        for(int i=0; i < str.length(); i++){ 
            p(str.charAt(i)); 
        } 

    } 
     
    public static void pl(String str){ 
        System.out.println(str); 
    } 

    public static void pl(int i){ 
        System.out.println(i); 
    } 
     
    public static void p(char i){ 
        System.out.print(i); 
    }     
} 





▷ 콘솔상에서 파일명을 입력받아 파일명과 확장자를 추출하여 
   출력하는 프로그램을 작성하세요. 
   단, 잘못된 파일명(abc, .txt, abc.)을 입력했을때 오류메세지 출력후  
   다시 입력받아 처리 할 수 있도록 작성하세요 

    InputStream i = System.in; 
   //BufferedReader  r = new BufferedReader(new InputStreamReader(i)); 
   System.out.println("파일명을 입력하세요"); 
   //String file = r.readLine(); 


   Scanner r = new Scanner(System.in); 
   System.out.println("파일명을 입력하세요"); 
   String file = r.nextLine(); 

   System.out.println(file); 
   
   while(true){ 

     1. 오류파일 검사(indexOf(), startsWith(), endsWith()) 
         1-1 오류파일인경우  
             - 메세지 출력  
             - 다시 file입력받기 
         1-2 정상적인 파일인 경우 
             - .의 위치 추출(indexOf()) 
             - .의 앞부분 문자열값 추출(substring()) 
             - .의 뒤부분 문자열값 추출 
             - 출력하고 break; 

    } 



    
   --- 실행 결과 --- 
   파일명을 입력하세요.: abc.txt 
    
   파일명은 abc 입니다. 
   파일의 확장자는 txt 입니다. 


   파일명을 입력하세요.: abc.html 
    
   파일명은 abc 입니다. 
   파일의 확장자는 html 입니다. 


이거 짰음


package test.properties;


import java.util.Scanner;


/*

 * 

▷ 콘솔상에서 파일명을 입력받아 파일명과 확장자를 추출하여 

   출력하는 프로그램을 작성하세요. 

   단, 잘못된 파일명(abc, .txt, abc.)을 입력했을때 오류메세지 출력후  

   다시 입력받아 처리 할 수 있도록 작성하세요 


 * */


public class StringTested {

public static void main(String[] args) {


Scanner scanner = new Scanner(System.in);


while (true) {

System.out.print("파일명을 입력하세요: ");

String file = scanner.nextLine();

System.out.println(file);


int filesize1 = file.indexOf(".");

String filename = file.substring(0, filesize1);

int filesize2 = file.lastIndexOf(".");

String filetype = file.substring(filesize2 + 1);


if (file.startsWith(".") || file.endsWith(".")) {

System.out.println("다시 입력해 주세요.");

}


else {

System.out.println("파일명: " + filename);

System.out.println("확장자: " + filetype);

break;

}

}

}


}


친절하지 않은 나의 코드
저기 if문 안에 file.size(1) 도 넣을 것



[03] Class class 
package test.properties; 

/** 표준 인터페이스 */ 
interface Action{ 
    public void execute(); 
} 

class Spring implements Action{ 
    public void execute(){ 
        System.out.println("따뜻한 봄 입니다. - 새싹"); 
    } 
} 

class Summer implements Action{ 
    public void execute(){ 
        System.out.println("더운 여름입니다. - 바다"); 
    } 
} 

class Fall implements Action{ 
    public void execute(){ 
        System.out.println("시원한 가을입니다. - 등산"); 
    } 
} 

class Winter implements Action{ 
    public void execute(){ 
        System.out.println("눈이오는 겨울입니다. - X-MAS"); 
    } 
} 

public class ClassTest { 

    public static void main(String[] args) { 
         
        String className = args[0]; // 실행할 클래스명, 패키지 포함 
        
        //Class.forName으로 객체를 생성하면 jvm으로 소스를 로딩한다.
        //클래스를 오브젝트형으로 형변환했당 :>
        
        
        try{ 
            Class object = Class.forName(className); // JVM으로 소스 로딩 
            Action instance = (Action)object.newInstance(); // 객체 생성 
            instance.execute(); 
        }catch(Exception e){ 
            System.out.println(e); 
        } 

    } 

} 

이거 실행하려면 run configuration에 test.properties.Winter 이런 식으로 넣ㅇ ㅓ줘야 한당...
클래스명, 패키지 포함인것


2. 다른 페이지 요청

   
forward
1번을 요청할 때 생긴 resource는 2번을 요청하는 resource랑 같은 애다

redirect
1번을 요청할떄 생긴 resource는 2번을 요청할 떄 생긴 resource랑 다르다
2번 호출하기 전에 1번거는 끝남



(1)   response.sendRedirect( ); 

      -  재요청에대한 응답으로  다른 페이지로 이동합니다.
     
     -  새로운 request객체가 생성되므로  기존 request영역에 저장된 모든 

        데이타가 없어집니다.




 (2)  <jsp:forward page="" />

      - 기존요청에 대신 응답으로 다른페이지로 이동합니다.

     - 기존의 request객체가 살아있으므로 그영역의 저장된 모든 데이타는

       유지됩니다.


---------------------------------------------------------


(3)  <jsp:include page=""/>

    - 다른 자원을 요청 페이지에 포함 시킵니다.



    
서블렛2의 응답이 서블렛1의 응답하고 합쳐져서 보내짐...
(실행된 결과가 합쳐짐)




--------------------
리퀘스트 테스트

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>
<center>
<%
//request.getParameter = 전에 애가 넘겨준 파라미터 받는 용
//request.setAttribute는 getAttribute랑 짝
request.setAttribute("name", "개발자");
out.println("name: " + request.getAttribute("name") + "<br/>");

request.setAttribute("count", 0);//boxing - new Integer(0): 그래서 해시코드가 들어감
out.println("count: " + request.getAttribute("count") + "<br/>");

Object obj = request.getAttribute("count");
int count = (Integer) obj;
count = count + 100;
request.setAttribute("count", count);

out.println("count: " + request.getAttribute("count") + "<br/>");
%>
</center>
</h2>
</body>
</html>
</html>

------------------------------
디티오

package test.request; 

public class PageInfo { //DTO 같은 것
    private int nowPage = 0; 
    private String searchColumn = ""; 
    private String searchWord = ""; 
     
    /** 
     * 기본 생성자 
     */ 
    public PageInfo() { 
        super(); 
        // TODO Auto-generated constructor stub 
    } 

    /** 
     * @param nowPage 
     * @param searchColumn 
     * @param searchWord 
     */ 
    public PageInfo(int nowPage, String searchColumn, String searchWord) { 
        super(); 
        this.nowPage = nowPage; 
        this.searchColumn = searchColumn; 
        this.searchWord = searchWord; 
    } 

    /** 
     * @return the nowPage 
     */ 
    public int getNowPage() { 
        return nowPage; 
    } 

    /** 
     * @param nowPage the nowPage to set 
     */ 
    public void setNowPage(int nowPage) { 
        this.nowPage = nowPage; 
    } 

    /** 
     * @return the searchColumn 
     */ 
    public String getSearchColumn() { 
        return searchColumn; 
    } 

    /** 
     * @param searchColumn the searchColumn to set 
     */ 
    public void setSearchColumn(String searchColumn) { 
        this.searchColumn = searchColumn; 
    } 

    /** 
     * @return the searchWord 
     */ 
    public String getSearchWord() { 
        return searchWord; 
    } 

    /** 
     * @param searchWord the searchWord to set 
     */ 
    public void setSearchWord(String searchWord) { 
        this.searchWord = searchWord; 
    } 

     
} 

-------------------------------------------

pageInfo_test.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="test.request.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>
<center>
<%
// request에 객체 저장 
PageInfo info = new PageInfo();
request.setAttribute("info", info);

// "info"로 저장된 객체를 가져옴 
PageInfo temp = (PageInfo) request.getAttribute("info");

// 객체에 값 저장 
temp.setNowPage(1);
temp.setSearchColumn("title");
temp.setSearchWord("JSP");

// 새로 설정된 값을 가지고 있는 객체를 저장 
request.setAttribute("info", temp);

// "info"로 저장된 객체를 가져옴 
PageInfo pageinfo = (PageInfo) request.getAttribute("info");

// 값 출력 
out.println("nowPage: " + pageinfo.getNowPage() + "<br/>");
out.println("searchColumn: " + pageinfo.getSearchColumn() + "<br/>");
out.println("searchWord: " + pageinfo.getSearchWord() + "<br/>");

response.sendRedirect("test_proc.jsp");
%>
</center>
</h2>

</body>
</html>

-------------------------------------------------------
java.lang.NullPointerException
떠야 정상적임 -  response로 sendRedirect 해줘서 못 받습니다.

이건 forward로 보내줘서 잘 받습니다.

>>>>> pageInfo_test.jsp 

<%@ page language="java" contentType="text/html; charset=UTF-8" 
    pageEncoding="UTF-8" import="test.request.*"%> 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 
<html> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 
<title>Insert title here</title> 
</head> 
<body> 
<h2><center> 
<% 
// request에 객체 저장 
PageInfo info = new PageInfo(); 
request.setAttribute("info", info); 

// "info"로 저장된 객체를 가져옴 
PageInfo temp = (PageInfo)request.getAttribute("info"); 

// 객체에 값 저장 
temp.setNowPage(1); 
temp.setSearchColumn("title"); 
temp.setSearchWord("JSP"); 

// 새로 설정된 값을 가지고 있는 객체를 저장 
request.setAttribute("info", temp); 

// "info"로 저장된 객체를 가져옴 
PageInfo pageinfo = (PageInfo)request.getAttribute("info"); 

// 값 출력 
out.println("nowPage: " + pageinfo.getNowPage() + "<br/>"); 
out.println("searchColumn: " + pageinfo.getSearchColumn() + "<br/>"); 
out.println("searchWord: " + pageinfo.getSearchWord() + "<br/>"); 

//response.sendRedirect("test_proc.jsp"); 
%> 
<jsp:forward page="/request/test_proc.jsp"/> 
</center></h2> 
</body> 
</html> 

-----------------------------
<%@ page language="java" contentType="text/html; charset=UTF-8" 
    pageEncoding="UTF-8" import="test.request.*"%> 
<% 
PageInfo info = (PageInfo)request.getAttribute("info"); 
//sendredirect -null / forward - 값들어감
%>     
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<div align="center"> 
<h2> 
>>> test_proc.jsp 입니다.<br/> 
    nowPage:      <%=info.getNowPage() %> <br/> 
    searchColumn: <%=info.getSearchColumn() %> <br/> 
    searchWord:   <%=info.getSearchWord() %> <br/> 
</h2> 

</div> 
</body>
</html>

이건 proc.jsp



4. Controller의 처리순서 

         Browser --------> Servlet(Controller) 
                              1. HTTP 요청 받음, doGet(), doPost() 메소드 호출 
                              2. 클라이언트가 요구하는 기능을 분석(방명록의 경우 글쓰기등) 
                              3. 요청한 비즈니스 로직을 처리하는 Model 사용, Business Logic Class 
                              4. 결과를 request 또는 session의 setAttribute()메소드를 사용하여 저장 
                              5. 알맞은 뷰 선택 후, 
                              6. 뷰로 포워딩(또는 리다이렉트), jsp 페이지로 이동 
                            ↙ 
                         JSP 
                        ↙  
                     HTTP 응답 
                    ↙ 
         Browser 

요기서 345를 action한테 처리하라고 위임함!

[06] [MVC] MVC의 구현(Manager, Action)
[01] 템플릿 기반의 URI Command Pattern에 기반한 MVC의 구현 

     - URI상에 있는 주소를 얻어와 명령어로 처리하는 패턴입니다. 

이게바로 345를 action한테 처리하라고 위임하는 것이다.

     - Eclipse Setting 
       . Project Type: Dynamic Web Project 
       .         Name: www_mvc  
       . Package Name: mvc.action, mvc.controller 

     - 폴더 구조 
       www_mvc/WebContent/mvc/view        : jsp 파일들  
       www_mvc/WebContent/mvc/template    : template관련 파일들 
       www_mvc/WEB-INF         : web.xml 환경 설정 파일 위치 
       www_mvc/WEB-INF/classes : 서블릿 클래스 위치, 자동 생성 
       www_mvc/WEB-INF/config  : properties 파일등, 기타 리소스 파일  
       www_mvc/WEB-INF/lib     : jar 파일의 라이브러리 위치, 자동 생성 


졸았음... ㅠㅠ


]package mvc.action; 

import java.util.Date; 

public class CommandMgr { 

    /** 
     * 기본 생성자 
     */ 
    public CommandMgr() { 
        super(); 
    } 

    public StringBuffer getHello(){ 
        StringBuffer sb = new StringBuffer(); 
        sb.append("<li> 안녕하세요..MVC 입니다.<br>"); 
        sb.append("<li> Template Page<br>"); 
        sb.append("<li> URI Command Pattern<br>"); 
        sb.append("<li> Properties 파일을 이용한 처리입니다.<br>"); 
         
        return sb; 

    } 

    public String getDate(){ 
        Date dt = new Date(); 
        String str = dt.toLocaleString();  
         
        return str; 
    } 

} 


이건 beans (model)



2. Command Handler 

   - 인터페이스 또는 추상클래스로 구현 합니다. 
    
   - 비즈니스 로직 클래스(자식 클래스)들이 동일한 메소드를 실행 하도록 강제성 
     부여하며 표준 인터페이스 역활을 합니다. 

어떤 액션객체가 수행될 지 모르니까 인터페이스로 상속받아서 하는 것


456은 졸려서 헤멨으니까 점심때 꼭 봐야 함...

프로퍼티스 파일은 - 어떤 애가 실행되면 어떤 게 호출되는지 규약을 적어놓는 곳임 - 컨트롤러가 쓰라고

# command = Action class Mapping List
/mvc/hello.do=mvc.action.HelloAction
/mvc/date.do=mvc.action.DateAction

/mvc/*.do = ㅁㅁㅁㅁㅁ
이렇게 짜면 뭐가 오든지  컨트롤러에서 액션 처리해주는 것 :>



오 템플릿 넘나 좋은것
여기다가 위 아래 페이지 인크루드하고 내용도 인크루드하면 내용에다가 위 아래 안해줘도 됨



-----------------------------------------------------------
4. Controller의 처리순서 

         Browser --------> Servlet(Controller) 
                              1. HTTP 요청 받음, doGet(), doPost() 메소드 호출 
                              2. 클라이언트가 요구하는 기능을 분석(방명록의 경우 글쓰기등) 
                              3. 요청한 비즈니스 로직을 처리하는 Model 사용, Business Logic Class 
                              4. 결과를 request 또는 session의 setAttribute()메소드를 사용하여 저장 
                              5. 알맞은 뷰 선택 후, 
                              6. 뷰로 포워딩(또는 리다이렉트), jsp 페이지로 이동 
                            ↙ 
                         JSP 
                        ↙  
                     HTTP 응답 
                    ↙ 
         Browser 

345 - 액션에게 위임하고
6-컨트롤러가 직접 처리한다

1. 프로퍼티 파일에 명령어하고 액션이 정이되었나 봐야함

# action-config.properties
# command = Action class Mapping List
/mvc/hello.do=mvc.action.HelloAction
/mvc/date.do=mvc.action.DateAction
# /mvc/*.do=까지 하면 web.xml을 호출해줌

2. implements action을 하고 있는 액션 클래스가 있나 봐야함
2. 저 안에서 모델 사용할 객체를 생성하고 리턴값을 제대로 줬나 봐야함
package mvc.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DateAction implements Action {

@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Throwable {
// TODO Auto-generated method stub
CommandMgr mgr = new CommandMgr();
String date = mgr.getDate();
// 모델 사용할 객체 생성

request.setAttribute("date", date);

return "/mvc/view/date.jsp";

}

}


그리고 나서 서버를 재구동 했나 봐야함


-----------------------------------------------------------

컨트롤러는 HttpServlet을 구현해야함!

아--------------------

액션 만드는 법

package mvc.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyinfoAction implements Action {

@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Throwable {
//3. model 사용
CommandMgr mgr = new CommandMgr();
StringBuffer myinfo = mgr.getMyinfo();
//4. 결과를 request에 저장
request.setAttribute("myinfo", myinfo);
//5. view 페이지 리턴
return "/mvc/view/myinfo.jsp";
}

}


----------------------------------------------






























-----------------------------------------------------------
-----------------------------------------------------------
-----------------------------------------------------------
-----------------------------------------------------------
-----------------------------------------------------------
-----------------------------------------------------------
-----------------------------------------------------------
-----------------------------------------------------------


















5. 수업

진도: 

hw: 


6. 할것


점심때 456 복습할 것! :>



Class.forName

            Class object = Class.forName(className); // JVM으로 소스 로딩 
            Action instance = (Action)object.newInstance(); // 객체 생성 
            instance.execute(); 
이거

클래스로더 1, 동적인 클래스 로딩과 클래스로더

Java 2000.07.16 07:52
동적인 클래스 로딩

자바는 동적으로 클래스를 읽어온다. 즉, 런타임에 모든 코드가 JVM에 링크된다. 모든 클래스는 그 클래스가 참조되는 순간에 동적으로 JVM에 링크되며, 메모리에 로딩된다. 자바의 런타임 라이브러리([JDK 설치 디렉토리]/jre/lib/rt.jar) 역시 예외가 아니다. 이러한 동적인 클래스 로딩은 자바의 클래스로더 시스템을 통해서 이루어지며, 자바가 기본적으로 제공하는 클래스로더는 java.lang.ClassLoader를 통해서 표현된다. JVM이 시작되면, 부트스트랩(bootstrap) 클래스로더를 생성하고, 그 다음에 가장 첫번째 클래스인 Object를 시스템에 읽어온다.

런타임에 동적으로 클래스를 로딩하다는 것은 JVM이 클래스에 대한 정보를 갖고 있지 않다는 것을 의미한다. 즉, JVM은 클래스의 메소드, 필드, 상속관계 등에 대한 정보를 알지 못한다. 따라서, 클래스로더는 클래스를 로딩할 때 필요한 정보를 구하고, 그 클래스가 올바른지를 검사할 수 있어야 한다. 만약 이것을 할 수 없다면, JVM은 .class 파일의 버전이 일치하지 않을 수 있으며, 또한 타입 검사를 하는 것이 불가능할 것이다. JVM은 내부적으로 클래스를 분석할 수 있는 기능을 갖고 있으며, JDK 1.1부터는 개발자들이 리플렉션(Reflection)을 통해서 이러한 클래스의 분석을 할 수 있도록 하고 있다.

로드타임 동적 로딩(load-time dynamic loading)과 런타임 동적 로딩(run-time dynamic loading)

클래스를 로딩하는 방식에는 로드타임 동적 로딩(load-time dynamic loading)과 런타임 동적 로딩(run-time dynamic loading)이 있다. 먼저 로드타임 동적 로딩에 대해서 알아보기 위해 다음과 코드를 살펴보자.

  public class HelloWorld {
     public static void main(String[] args) {
        System.out.println("안녕하세요!");
     }
  }

HelloWorld 클래스를 실행하였다고 가정해보자. 아마도, 명령행에서 다음과 같이 입력할 것이다.

  $ java HelloWorld

이 경우, JVM이 시작되고, 앞에서 말했듯이 부트스트랩 클래스로더가 생성된 후에, 모든 클래스가 상속받고 있는 Object 클래스를 읽어온다. 그 이후에, 클래스로더는 명령행에서 지정한 HelloWorld 클래스를 로딩하기 위해, HelloWorld.class 파일을 읽는다. HelloWorld 클래스를 로딩하는 과정에서 필요한 클래스가 존재한다. 바로 java.lang.String과 java.lang.System이다. 이 두 클래스는 HelloWorld 클래스를 읽어오는 과정에서, 즉 로드타임에 로딩된다. 이 처럼, 하나의 클래스를 로딩하는 과정에서 동적으로 클래스를 로딩하는 것을 로드타임 동적 로딩이라고 한다.

이제, 런타임 동적 로딩에 대해서 알아보자. 우선, 다음의 코드를 보자.

  public class HelloWorld1 implements Runnable {
     public void run() {
        System.out.println("안녕하세요, 1");
     }
  }
  public class HelloWorld2 implements Runnable {
     public void run() {
        System.out.println("안녕하세요, 2");
     }
  }

이 두 클래스를 Runnable 인터페이스를 구현한 간단한 클래스이다. 이제 실제로 런타임 동적 로딩이 일어나는 클래스를 만들어보자.

  public class RuntimeLoading {
     public static void main(String[] args) {
        try {
           if (args.length < 1) {
              System.out.println("사용법: java RuntimeLoading [클래스 이름]");
              System.exit(1);
           }
           Class klass = Class.forName(args[0]);
           Object obj = klass.newInstance();
           Runnable r = (Runnable) obj;
           r.run();
        } catch(Exception ex) {
           ex.printStackTrace();
        }
     }
  }

위 코드에서, Class.forName(className)은 파리미터로 받은 className에 해당하는 클래스를 로딩한 후에, 그 클래스에 해당하는 Class 인스턴스(로딩한 클래스의 인스턴스가 아니다!)를 리턴한다. Class 클래스의 newInstance() 메소드는 Class가 나타내는 클래스의 인스턴스를 생성한다. 예를 들어, 다음과 같이 한다면 java.lang.String 클래스의 객체가 생성된다.

  Class klass = Class.forName("java.lang.String");
  Object obj = klass.newInstance();

따라서, Class.forName() 메소드가 실행되기 전까지는 RuntimeLoading 클래스에서 어떤 클래스를 참조하는 지 알수 없다. 다시 말해서, RuntimeLoading 클래스를 로딩할 때는 어떤 클래스도 읽어오지 않고, RuntimeLoading 클래스의 main() 메소드가 실행되고 Class.forName(args[0])를 호출하는 순간에 비로서 args[0]에 해당하는 클래스를 읽어온다. 이처럼 클래스를 로딩할 때가 아닌 코드를 실행하는 순간에 클래스를 로딩하는 것을 런타임 동적 로딩이라고 한다.

다음은 RuntimeLoading 클래스를 명령행에서 실행한 결과를 보여주고 있다.

  $ java RuntimeLoading HelloWorld1
  안녕하세요, 1

Class.newInstance() 메소드와 관련해서 한 가지 알아둘 점은 해당하는 클래스의 기본생성자(즉, 파라미터가 없는)를 호출한다는 점이다. 자바는 실제로 기본생성자가 코드에 포함되어 있지 않더라도 코드를 컴파일할 때 자동적으로 기본생성자를 생성해준다. 이러한 기본생성자는 단순히 다음과 같이 구성되어 있을 것이다.

  public ClassName() {
     super();
  }

ClassLoader

자바는 클래스로더를 사용하고, 클래스를 어떻게 언제 JVM으로 로딩하고, 언로딩하는지에 대한 특정한 규칙을 갖고 있다. 이러한 규칙을 이해해야, 클래스로더를 좀 더 유용하게 사용할 수 있으며 개발자가 직접 자신만의 커스텀 클래스로더를 작성할 수 있게 된다.

클래스로더의 사용

이 글을 읽는 사람들은 거의 대부분은 클래스로더를 프로그래밍에서 직접적으로 사용해본 경험이 없을 것이다. 클래스로더를 사용하는 것은 어렵지 않으며, 보통의 자바 클래스를 사용하는 것과 완전히 동일하다. 다시 말해서, 클래스로더에 해당하는 클래스의 객체를 생성하고, 그 객체의 특정 메소드를 호출하기만 하면 된다. 간단하지 않은가? 다음의 코드를 보자.

  ClassLoader cl = . . . // ClassLoader의 객체를 생성한다.
  Class klass = null;
  try {
     klass = cl.loadClass("java.util.Date");
  } catch(ClassNotFoundException ex) {
     // 클래스를 발견할 수 없을 경우에 발생한다.
     ex.printStackTrace();
  }

일단 클래스로더를 통해서 필요한 클래스를 로딩하면, 앞의 예제와 마찬가지로 Class 클래스의 newInstance() 메소드를 사용하여 해당하는 클래스의 인스턴스를 생성할 수 있게 된다. 형태는 다음과 같다.

  try {
     Object obj = klass.newInstance();
  } catch(InstantiationException ex) {
     ....
  } catch(IllegalAccessException ex) {
     ....
  } catch(SecurityException ex) {
     ....
  } catch(ExceptionIninitializerError error) {
     ...
  }

위 코드를 보면, Class.newInstance()를 호출할 때 몇개의 예외와 에러가 발생하는 것을 알 수 있다. 이것들에 대한 내용은 Java API를 참고하기 바란다.

자바 2의 클래스로더

자바 2 플랫폼에서 클래스로더의 인터페이스와 세만틱(semantic)은 개발자들이 자바 클래스로딩 메커니즘을 빠르고 쉽게 확장할 수 있도록 하기 위해 몇몇 부분을 재정의되었다. 그 결과로, 1.1이나 1.0에 맞게 작성된 (커스텀 클래스로더를 포함한) 클래스로더는 자바 2 플랫폼에서는 제기능을 하지 못할 수도 있으며, 클래스로더 사용하기 위해 작성했던 코드를 재작성하는 것이 그렇게 간단하지만은 않다.

자바 1.x와 자바 2에서 클래스로더에 있어서 가장 큰 차이점은 자바 2의 클래스로더는 부모 클래스로더(상위 클래스가 아니다!)를 갖고 있다는 점이다. 자바 1.x의 클래스로더와는 달리, 자바 2의 클래스로더는 부모 클래스로더가 먼저 클래스를 로딩하도록 한다. 이를 클래스로더 딜리게이션 모델(ClassLoader Delegation Model)이라고 하며, 이것이 바로 이전 버전의 클래스로더와 가장 큰 차이점이다.

자바 2의 클래스로더 딜리게이션 모델에 대해 구체적으로 알아보기 위해 로컬파일시스템과 네트워크로부터 클래스를 읽어와야 할 필요가 있다고 가정해보자. 이 경우, 쉽게 로컬파일시스템의 jar 파일로부터 클래스를 읽어오는 클래스로더와 네트워크로부터 클래스를 읽어오는 클래스로더가 필요하다는 것을 생각할 수 있다. 이 두 클래스로더를 각각 JarFileClassLoader와 NetworkClassLoader라고 하자.

JDK 1.1에서, 커스텀 클래스로더를 만들기 위해서는 ClassLoader 클래스를 상속받은 후에 loadClass() 메소드를 오버라이딩하고, loadClass() 메소드에서 바이트코드를 읽어온 후, defineClass() 메소드를 호출하면 된다. 여기서 defineClass() 메소드는 읽어온 바이트코드로부터 실제 Class 인스턴스를 생성해서 리턴한다. 예를 들어, JarFileClassLoader는 다음과 같은 형태를 지닐 것이다.

  public class JarFileClassLoader extends ClassLoader {
     ...
     private byte[] loadClassFromJarFile(String className) {
        // 지정한 jar 파일로부터 className에 해당하는 클래스의
        // 바이트코드를 byte[] 배열로 읽어온다.
        ....
        return byteArr;
     }
     
     public synchronized class loadClass(String className, boolean resolveIt)
        throws ClassNotFoundException {
        
        Class klass = null;
        
        // 클래스를 로드할 때, 캐시를 사용할 수 있다.
        klass = (Class) cache.get(className);
        
        if (klass != null) return klass;
        
        // 캐시에 없을 경우, 시스템 클래스로더로부터
        // 지정한 클래스가 있는 지 알아본다.
        try {
           klass = super.findSystemClass(className);
           return klass;
        } catch(ClassNotFoundException ex) {
           // do nothing
        }
        
        // Jar 파일로부터 className이 나타내는 클래스를 읽어온다.
        byte[] byteArray = loadClassFromJarFile(className);
        klass = defineClass(byteArray, 0, byteArray.length);
        if (resolve)
           resolveClass(klass);
        cache.put(className, klass); // 캐시에 추가
        return klass;
     }
  }

위의 개략적인 코드를 보면, 시스템 클래스로더에게 이름이 className인 클래스가 존재하는 지 요청한다. (여기서 시스템 클래스로더 또는 primordial 시스템 클래스로더는 부트스트랩 클래스로더이다). 그런 후에, 시스템 클래스로더로부터 클래스를 읽어올 수 없는 경우 Jar 파일로부터 읽어온다. 이 때, className은 완전한 클래스 이름(qualified class name; 즉, 패키지이름을 포함한)이다. NetworkClassLoader 클래스 역시 이 클래스와 비슷한 형태로 이루어져 있을 것이다. 이 때, 시스템 클래스로더와 그 외의 다른 클래스로더와의 관계는 다음 그림과 같다.


위 그림을 보면, 각각의 클래스로더는 오직 시스템 클래스로더와 관계를 맺고 있다. 다시 말해서, JarFileClassLoader는 NetworkClassLoader나 AppletClassLoader와는 관계를 맺고 있지 않다. 이제, A라는 클래스가 내부적으로 B라는 클래스를 사용한다고 가정해보자. 이 때, 만약 A 클래스는 네트워크를 통해서 읽어오고, B라는 클래스는 Jar 파일을 통해서 읽어와야 한다면? 이 경우에 어떻게 해야 하는가? 쉽사리 해결책이 떠오르지 않을 것이다. 이러한 문제는 JarFileClassLoader와 NetworkClassLoader 간에 유기적인 결합을 할 수 없기 때문에 발생한다.

자바 2에서는 이러한 문제를 클래스로더 딜리게이션 모델을 통해서 해결하고 있다. 즉, 특정 클래스로더 클래스를 읽어온 클래스로더(이를 부모 클래스로더라고 한다)에게 클래스 로딩을 요청하는 것이다. 다음의 그림을 보자.


이 그림은 자바 2에서 클래스로더간의 관계를 보여주고 있다. 이 경우, NetworkClassLoader 클래스는 JarFileClassLoader가 로딩하고, JarFileClassLoader 클래스는 AppClassLoader가 로딩하였음을 보여준다. 즉, JarFileClassLoader는 NetworkClassLoader의 부모 클래스로더가 되고, AppClassLoader는 JarFileClassLoader의 부모 클래스로더가 되는 것이다.

이 경우, 앞에서 발생했던 문제가 모두 해결된다. A 클래스가 필요하면, 가장 먼저 NetworkClassLoader에 클래스로딩을 요청한다. 그럼, NetworkClassLoader는 네트워크로부터 A 클래스를 로딩할 수 있으므로, A 클래스를 로딩한다. 그런 후, A 클래스는 B 클래스를 필요로 한다. B 클래스를 로딩하기 위해 NetworkClassLoader는 JarFileClassLoader에 클래스 로딩을 위임(delegation)한다. JarFileClassLoader는 Jar 파일로부터 B 클래스를 읽어온 후 NetworkClassLoader에게 리턴할 것이며, 따라서 NetworkClassLoader는 Jar 파일에 있는 B 클래스를 사용할 수 있게 된다. 앞의 JDK 1.1에서의 클래스로더 사이의 관계에 비해 훨씬 발전적인 구조라는 것을 알 수 있다.

앞에서 말했듯이, 자바 2에서는 몇몇 클래스로더 메커니즘을 재정의하였다. 이 때문에, JDK 1.1에서의 클래스로더에 관한 몇몇개의 규칙이 깨졌다. 먼저, loadClass() 메소드를 더 이상 오버라이딩(overriding) 하지 않고, 대신 findClass()를 오버라이딩한다. loadClass() 메소드는 public에서 protected로 변경되었으며, 실제 JDK1.3의 ClassLoader 클래스의 소크 코드를 보면 다음과 같이 정의되어 있다.

  // src/java/lang/ClassLoader.java
  public abstract class ClassLoader {
      /*
       * The parent class loader for delegation.
       */
      private ClassLoader parent;
      
      protected synchronized Class loadClass(String name, boolean resolve)
      throws ClassNotFoundException
      {
          // First, check if the class has already been loaded
          Class c = findLoadedClass(name);
          if (c == null) {
              try {
                  if (parent != null) {
                      c = parent.loadClass(name, false);
                  } else {
                      c = findBootstrapClass0(name);
                  }
              } catch (ClassNotFoundException e) {
                  // If still not found, then call findClass in order
                  // to find the class.
                  c = findClass(name);
              }
          }
          if (resolve) {
              resolveClass(c);
          }
          return c;
      }
      ....
  }

위 코드를 보면 부모 클래스로더로부터 먼저 클래스 로딩을 요청하고, 그것이 실패할 경우(즉, catch 블럭)에 비로소 직접 클래스를 로딩한다. 여기서 그렇다면 부모 클래스는 어떻게 결정되는 지 살펴보자. 먼저 JDK 1.3의 ClassLoader 클래스는 다음과 같은 두 개의 생성자를 갖고 있다.

  protected ClassLoader(ClassLoader parent) {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = parent;
      initialized = true;
  }
  protected ClassLoader() {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = getSystemClassLoader();
      initialized = true;
  }

이 두 코드를 살펴보면, 부모 클래스로더를 지정하지 않을 경우, 시스템 클래스로더를 부모 클래스로더로 지정하는 것을 알 수 있다. 따라서 커스텀 클래스로더에서 부모 클래스로더를 지정하기 위해서는 다음과 같이 하면 된다.

  public class JarFileClassLoader extends ClassLoader {
     public JarFileClassLoader () {
        super(JarFileClassLoader.class.getClassLoader());
        // 다른 초기화 관련 사항
     }
     ....
     public Class findClass(String name) {
        // 지정한 클래스를 찾는다.
     }
  }

모든 클래스는 그 클래스에 해당하는 Class 인스턴스를 갖고 있다. 그 Class 인스턴스의 getClassLoader() 메소드를 통해서 그 클래스를 로딩한 클래스로더를 구할 수 있다. 즉, 위 코드는 JarFileClassLoader 클래스를 로딩한 클래스로더를 JarFileClassLoader 클래스로더의 부모 클래스로더로 지정하는 것이다. (실제로 커스텀 클래스로더를 구현하는 것에 대한 내용은 이 Article의 시리중에서 3번째에 알아보기로 한다).

JVM에서 부모 클래스로더를 갖지 않은 유일한 클래스로더는 부트스트랩 클래스로더이다. 부트스트랩 클래스로더는 자바 런타임 라이브러리에 있는 클래스를 로딩하는 역할을 맡고 있으며, 항상 클래스로더 체인의 가장 첫번째에 해당한다. 기본적으로 자바 런타임 라이브러리에 있는 모든 클래스는 JRE/lib 디렉토리에 있는 rt.jar 파일에 포함되어 있다.

결론

이번 Article에서는 자바에서 클래스 로딩이 동적으로 이루어지면, 클래스 로딩 방식에서는 로드타임 로딩과 런타임 로딩의 두 가지 방식이 있다는 것을 배웠다. 그리고 자바 2에서의 클래스로딩이 클래스로더 딜리게이션 모델(Classloader Delegation Model)을 통해서 이루어진다는 점과 이 모델에 자바 1.x에서의 클래스로딩 메커니즘과 어떻게 다르며, 어떤 장점이 있는 지 알아보았다. 다음 Article에서는 자바 2에서 기본적으로 제공하는 클래스로더에 대해서 알아보기로 한다.


출처: http://javacan.tistory.com/entry/1




Class.forName("package.ClassName")를 실행하는 경우 문자열로 전달되는 클래스가 존재하는 클래스를 메모리에 로드하는 역할을 한다. http://stackoverflow.com/questions/4202252/how-does-class-forname-work 글을 보면 대략적으로 이해할 수 있다.

Class.forName("com.mysql.jdbc.Driver");

위와 같이 구현할 경우 "com.mysql.jdbc.Driver" 클래스가 메모리에 로드된다. 메모리에 로드되면서 static 절이 실행된다. 그렇다면 "com.mysql.jdbc.Driver" 클래스 내부 소스를 보면 다음과 같다.

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
           java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }


    [...]
}

Mysql JDBC Driver 소스코드는 http://www.docjar.com/html/api/com/mysql/jdbc/Driver.java.html 에서 확인할 수 있다.

이와 같이 DriverManager.registerDriver() 메소드를 통해 자기 자신을 등록하는 과정을 거친다.

Class.forName("com.mysql.jdbc.Driver")만 실행할 경우 특별히 다른 과정을 거치지 않았음에도 불구하고 JDBC Driver가 자동으로 등록되는 이유이다.

이와 같이 등록한 JDBC Driver는 Connection conn = DriverManager.getConnection(url + dbName,userName, password);에서 데이터베이스 Connection을 생성하는 시점에 사용되게 된다.

http://stackoverflow.com/questions/5484227/jdbc-class-forname-vs-drivermanager-registerdriver 이 문서도 읽어보면 좋겠다.

지금까지 아무 생각없이 그냥 그려려니 하면서 사용했는데 호기심에 찾아봤는데 나름 재미있네.

그럼 다음은 Class.forName("com.mysql.jdbc.Driver")와 Class.forName("com.mysql.jdbc.Driver").newInstance()의 무슨 차이가 있는지도 함 찾아보면 재미있겠다.


출처: https://slipp.net/questions/276



'Programming' 카테고리의 다른 글

160503: 36회차  (1) 2016.05.03
160502: 35회차  (0) 2016.05.02
160428: 33회차  (0) 2016.04.28
160427: 32회차  (1) 2016.04.27
160426: 31회차  (1) 2016.04.26
Posted by DAVID
블로그 이미지

by DAVID

공지사항

    최근...

  • 포스트
  • 댓글
  • 트랙백
  • 더 보기

태그

글 보관함

«   2025/07   »
일 월 화 수 목 금 토
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

링크

카테고리

PC (112)
Programming (109)

카운터

Total
Today
Yesterday
방명록 : 관리자 : 글쓰기
DAVID's Blog is powered by daumkakao
Skin info material T Mark3 by 뭐하라
favicon

PC

  • 태그
  • 링크 추가
  • 방명록

관리자 메뉴

  • 관리자 모드
  • 글쓰기
  • PC (112)
    • Programming (109)

카테고리

PC화면 보기 티스토리 Daum

티스토리툴바