JAVA 일반 Java Serialization 알자

2015.10.14 12:51

졸리운_곰 조회 수:534

 

 

 

Java Serialization 알자

이 자료는 자바랜드(www.javaland.co.kr)의 박정기께서 기고하신 내용입니다. 

 

 

 

Java Serialization맛보기

Java Serializatoin은 자바 객체를 저장하거나 전송하기 위하여 자바 객체의 코드를 다시 복원가능한 형태의 Stream으로 직렬화 시켜주는 것을 말한다. 

 

 

가장 간단한 형태부터 시작해서 자바 시리얼라이제이션의 예를 살펴 보도록 하겠다. 

 

<< swrite.java>> 

import java.lang.*; 

import java.io.*; 

import java.util.*; 

 

class swrite 

    public static void main(String args[]) 

    { 

       try{       

        FileOutputStream f = new FileOutputStream("tmp"); 

        ObjectOutput s = new ObjectOutputStream(f); 

        s.writeObject("Today"); 

        s.writeObject(new Date()); 

        s.flush(); 

       } 

         catch(IOException e) { }     

         System.out.println("Today"); 

         System.out.println(new Date()); 

    } 

 

 

위의 프로그램은 File Stream을 열어서 tmp라는 파일에 2개의 객체("Today"라는 String 객체와 Date 객체)를 저장하고 있다.  

여기서 객체저장을 위해 writeObject라는 메쏘드가 사용되고 있다는 것을 알 수 있다. 

 

위의 프로그램을 실행하면 2 객체가 시리얼라이제이션이 일어나서 다시 복원가능한 형태로되어 직렬화되어

tmp라는 파일로 저장된다. 

 

객체를 파일로 저장하다니? 얼마나 놀라운가? 

(제대로 다시 복원만 된다면...) 

 

자! 그럼 tmp 파일에서 객체를 살려보자!!! 

 

<< sread.java >> 

 

import java.lang.*; 

import java.io.*; 

import java.util.*; 

 

class sread 

    public static void main(String args[]) 

    { 

         try {       

           FileInputStream in = new FileInputStream("tmp"); 

           ObjectInput s = new ObjectInputStream(in); 

           String today = (String)s.readObject(); 

           Date date = (Date)s.readObject(); 

            

           System.out.println(today); 

           System.out.println(date); 

         } 

         catch(IOException e) { }     

         catch(ClassNotFoundException e) {} 

    } 

 

위의 파일은 아까 저장한 tmp파일에서 객체를 복원하여  

today와 date가 저장 직전의 상태로 복원되었다. 

여기서 readObject라는 메쏘드가 사용되었음을 알 수 있다. 

 

객체를 저장한 tmp 파일은 직렬화되어있으므로  

그냥 내용을 사용자가 알 수 없다. 

하지만 2 객체를 훌륭하게 저장하였다는 것을 알 수 있다.

 

 

Java Serializatoin의 개념 이해

앞의 예제는 Java Serializatoin의 맛빼기였다. 

 

그냥 대충 돌아가는 것만 보여준 것이지 개념상  

부족한 내용이 많다. 

 

좀더 심화된 내용으로 Java Serialization을 제대로 이해해 보자! 

 

Java Object Serialization은 자바 객체를 저장 또는 전송을 위하여 자바 코드를 다시 복원 가능한 byte stream 형태로 변환시켜 준다. 이 직렬화 과정을 자세히 말하면 

객체가 다시 원상태로 복원되기 위해서는 객체 내부의 

data들의 reference가 잘 정리되어야 있어야 한다.  

이러한 과정은 직렬화를 통하여 object reference의 tree 

즉, object graph를 형성하므로써 가능하다. 

이 graph 정보를 이용해서 객체를 다시 복원할 수 있는 것이다. 

 

이제 객체를 byte stream으로 변환되는 과정을 marshaling이라 부르고, 반대로 stream에서 객체로 역변환하는 과정을 unmarshaling이라고 한다. 

 

또한 객체가 안전하게 직렬화되기 위해서는 

해당 클래스가 Serializable 인터페이스를 imeplements하고 있어야만 한다. 

 

앞의 강좌 1의 예제에서는... 

알다시피 String 클래스와 Date 클래스 모두 API 레퍼런스를 보면 이미 Serializable 인터페이스를 imeplements하고 있을 확일할 수 있다. 

 

자! 그럼... 

 

이번엔 사용자가 정의한 클래스를 직렬화시켜서 

파일로 저장하고 다시 복원하는 예제를 보도록 하자! 

 

먼저 전송할 사용자가 만든 Test 클래스를 다음과 같이 만든다. 

 

<< Test.java >> 

 

public class Test implements java.io.Serializable 

   public String str; 

   public transient int ivalue; 

   public Test(String s, int i) 

   { 

       str = s; 

       ivalue = i; 

   } 

 

당연히 java.io.Serializable 인터페이스를 implements하고 있어야 한다. 

위에서 Test 클래스에 멤버변수로 String 타입의 str과 int 타입의 ivalue가 있음을 볼 수 있다. 

그런데 이기서 transient 키워드에 주목하자! 

transient 키워드는 앞에 지정하면 지정된 항목 내용은  

자바 시리얼라이제이션에서 제외된다.  

즉, 직렬화가 이루어질때 사용자가 문제의 소지가 있는 변수나 메쏘드를 제외시킬 수 있도록 해주는 것이다. 

 

그럼 Test 클래스를 직렬화하여 파일로 저장해보자! 

 

<< write.java >> 

 

import java.io.*; 

 

public class write 

    public static void main(String args[])  

    { 

        try{   

            FileOutputStream fos =

                                 new FileOutputStream("file.out"); 

            ObjectOutputStream oos =

                                 new ObjectOutputStream(fos); 

            oos.writeObject(new Test("testing", 37)); 

            oos.flush(); 

            fos.close(); 

        } 

        catch(Throwable e)  

        { 

            System.err.println(e); 

        }    

    } 

 

위의 소스는 Test 객체를 file.out이라는 이름의 파일로 저장할 것이다. 

역시 writeObject를 사용했고, 초기 값으로 "testing" 이라는 문자열과 37의 값을 지정했다. 

 

자! 그럼 file.out 파일에서 Test 객체로 복원시켜 보자! 

 

<< read.java >> 

 

import java.io.*; 

 

public class read 

   public static void main(String args[]) 

   { 

       Test testobj = null; 

       try 

       { 

           FileInputStream fis =

                                  new FileInputStream("file.out"); 

           ObjectInputStream ois =

                                  new ObjectInputStream(fis); 

           testobj = (Test)ois.readObject(); 

           fis.close(); 

       } 

       catch(Throwable e) 

       { 

           System.err.println(e); 

       } 

        

       System.out.println(testobj.str); 

       System.out.println(testobj.ivalue); 

   } 

 

위의 소스를 컴파일하여 실행시켜 보면 

file.out 파일에서 readObject 메쏘드를 사용해서 Test 객체를 복원해 낸다. 

 

결과를 출력해보면 다음과 같다!!! 

 

testing 

 

여기서 testobj.str의 값은 저장하기 이전 상태 그대로지만 testobj.ivalue의 값은 처음에 37로 지정했는데 0으로 출력되었다. 

 

이것은 test 클래스에서 transient 키워드가 지정되었기 때문에 시리얼라이제이션에서 제외되었기 때문에 본래 값을 보존할 수 없었던 것이다.

 

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

 

 

Java Object Serializatoin

Java Object Serializatoin의 3회 강좌를 시작한다. 

 

이번에는 객체를 소켓을 통해서 전송하는 방법에 대해서 다루겠다. 

이때 주의해할 점이 있는데... 이것은 

예제를 통해서 공부해 보도록 하겠다. 

 

먼저 전송할 객체 클래스를 다음과 같이 만들었다고 하자! 

 

 

<< MyObject.java>> 

 

import java.io.*; 

 

public class MyObject implements Serializable  

   String name; 

   int count; 

 

   MyObject()    // 컨스트럭터 

   { 

       setName(); 

   } 

 

   public void setName()  

   { 

       count++; 

       name = "MyObject " + count; 

   } 

                                                                  

   public String toString()  

   {  

       return name;  

   } 

 

==== 

 

역시... Serializable 인터페이스를 implements하고 있으며... 변수로 name과 count를 가지고 있다. 

컨스트럭터에서 setName() 메쏘드를 호출하여 count를 증가시키고 name의 String에도 숫자가 추가되고 있다. 

 

그러면 위의 객체를 스트림을 통하여 소켓으로 전송하기위하여 먼저 서버를 만들어보자! 

 

<< JabberServer.java >> 

 

import java.io.*; 

import java.net.*; 

 

public class JabberServer  

{  

    static final int port = 8080; 

    public static void main(String[] args )  

    { 

        try  

        {  

             MyObject o = new MyObject(); 

             ServerSocket s = new ServerSocket(port); 

             System.out.println("Server Started: " + s); 

             Socket socket = s.accept(); 

             System.out.println("Connection accepted,

                                         socket: " + socket); 

             ObjectOutputStream ToClient =

                                  new ObjectOutputStream(

                                        socket.getOutputStream()); 

             DataInputStream FromClient =

                                  new DataInputStream(

                                        socket.getInputStream()); 

 

             while (o.count<11)  

             {  

                  System.out.println("writing " + o); 

                  o.setName();  

                  /**

                    Object reference를 reset한다. 

                    (이 부분이 포인트) 

                    이것을 안하면 첫번째 전송한 객체의  

                    reference로  계속 전송된다.

                    그래서 갱신된 data가 반영되지 못하는

                    현상이 생긴다. 

                  */

                  ToClient.reset();  

                  ToClient.writeObject(o); 

                  System.out.print("trying to received

                                           acknowledgement ... "); 

                  System.out.println("acknowledgement: " +

                                             FromClient.readInt());  

                  System.out.println("succeeded"); 

             } 

             System.out.println("closing..."); 

             ToClient.close(); 

             socket.close(); 

        }  

        catch(Exception e)  

        { 

             e.printStackTrace(); 

        }  

   } 

 

=== 

 

위 서버 프로그램에서는 서버 소켓을 8080 포트로 열어놓고 클라언트를 기다리다가 클라이언트가 억셉트되면 계속 동작하게 되어있다. 

 

계속 설명하기 전에... 클라이언트 프로그램도 같이 보도록 하자! 

 

<< JabberClient.java >> 

 

import java.net.*; 

import java.io.*; 

 

public class JabberClient 

   static final int port = 8080; 

   public static void main(String args[])  

   { 

       MyObject o; 

       try  

       { 

           InetAddress addr =

                                    InetAddress.getByName(null); 

           System.out.println("addr = " + addr); 

           Socket socket = new Socket(addr, port); 

           System.out.println("socket = " + socket); 

           ObjectInputStream FromServer = new 

           ObjectInputStream(socket.getInputStream()); 

           DataOutputStream ToServer = new 

           DataOutputStream(socket.getOutputStream()); 

 

           int i=0;  

           while(true)  

           { 

               o = (MyObject)FromServer.readObject(); 

               System.out.print("trying to send

                                        acknowledgement ... "); 

               Thread.sleep(500); 

               ToServer.writeInt(i++); 

               System.out.println("succeeded"); 

               System.out.println(o); 

           } 

      }  

      catch (EOFException f)  

      { 

           System.exit(0); 

      }  

      catch(Exception e)  

      { 

           e.printStackTrace(); 

      } 

  } 

}  

 

===  

 

소켓이 맺어진 후에... 

 

서버에서 MyObject 객체를 생성하면 컨스트럭터에의해 카운트가 1이 된다. 

그후 setName() 메쏘드를 호출하고 나면 카운트가 2가 되고  이것을 스트림으로 바꾸어 객체를 전송하게 된다. 

 

클라이언트는 전송된 객체를 받아 ACK 성공 메시지를 뿌리고 카운트 값을 찍는다. 

 

그후 서버에서 setName() 메쏘드를 다시 호출하여 객체의 카운트 상태값을  하나 증가시켜 3으로 만든후 객체를 스트림으로 전송한다. 

 

클라이언트는 두번째 객체를 받아 ACK 성공 메시지를 뿌리고 카운트 값을 찍는다. 

이때 카운트 값이 갱신된 3값으로 찍혀야 하는데... 

여전히 2로 찍혀져 나온다? 

 

왜일까???    

 

이것은 자바의 버그가 아니다. 

 

이것은 자바 시리얼라이제이션에서는 

처음 전송한 객체의 object reference를 인위적으로

reset시켜 주지 않으면 객체의 상태가 바뀌더라도 전송되는 객체의 object reference는  처음 전송한 객체의 object reference로 계속 가지게 되므로  이와 같은 현상이 발생한다. 

 

이문제를 해결하려면... 

객체를 스트림으로 전송하기 전에... 

reset() 메쏘드를 통하여 같은 object reference를 사용하지 않도록 리셋시켜주어야 한다. 

 

즉, 

ToClient.reset(); 부분에 대한 이해가 이번 강좌의 포인트이다. 

이부분이 있고, 없고의 차이를 반드시 이해하기 바란다. 

 

객체를 소켓으로 계속 전송하게 될 경우... 

실수하면 위와같은 버그아닌 오류에 봉착하기 쉽다. 

 

그럼 이상으로 3회 강좌를 마친다.

 

 

 

펌 : http://www.javaland.co.kr/

 

 

 

 

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
164 Java 8 개선 사항 관련 글 모음 졸리운_곰 2016.05.23 271
163 Building a Search Engine With Nutch Solr And Hadoop file 졸리운_곰 2016.04.21 219
162 Nutch and Hadoop Tutorial file 졸리운_곰 2016.04.21 209
161 Latest step by Step Installation guide for dummies: Nutch 0. file 졸리운_곰 2016.04.21 131
160 Nutch 초간단 빌드와 실행 졸리운_곰 2016.04.21 454
159 Nutch로 알아보는 Crawling 구조 - Joinc 졸리운_곰 2016.04.21 352
158 A tiny bittorrent library Java: 자바로 만든 작은 bittorrent 라이브러리 file 졸리운_곰 2016.04.20 242
157 Updating UI in Eclipse RCP 졸리운_곰 2015.11.07 183
156 Eclipse RCP: Display.getDefault().asyncExec still blocking my GUI 졸리운_곰 2015.11.07 135
155 The Eclipse RCP multi-threaded Job and UIJob, use 졸리운_곰 2015.11.07 243
154 SWT: Respond to Long Running Tasks 졸리운_곰 2015.11.07 91
153 SWT and multithreading 졸리운_곰 2015.11.07 73
152 Eclipse RCP multithreading 졸리운_곰 2015.11.07 146
151 Eclipse RCP - Prevent UI freezing while running long operation 졸리운_곰 2015.11.07 114
150 Eclipse RCP Tutorial: How to Add a Progress Bar file 졸리운_곰 2015.11.07 119
149 Eclipse RCP 에서 configuration 설정 파일 사용하지 않기 secret 졸리운_곰 2015.11.07 0
148 Gson User Guide 졸리운_곰 2015.10.26 318
147 Java Google Json (Gson) Type Adapter 졸리운_곰 2015.10.26 145
146 TypeAdapter (Gson 2.3.1 API) - Google Gson 졸리운_곰 2015.10.26 132
145 Gson TypeAdapter Example file 졸리운_곰 2015.10.26 86
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED