객체 직렬화(Object Serialization)
출처: http://www.javastudy.co.kr/docs/lec_java/io/lecture.htm
자바에서는 자바에서 제공하는 기본적인 데이터 유형 이외에도 여러 객체들을 스트림에 쓰고, 읽을 수 있는
기능을 제공하는데 이것이 바로 객체 직렬화를 통해서 가능하다. 이러한 객체 직렬화 기법은 원격 메소드
호출, 보안 및 암호화 등 실제 프로그래밍 시에 유용하게 사용되어 질 수 있다.
객체 직렬화의 비밀
그러면 먼저 객체 직렬화가 어떠한 과정을 거쳐서 이루어 지는지 비밀을 벗겨보도록 하자.
먼저 객체들은 ObjectOutputStream에 의해서 직렬화가 되며, ObjectInputStream에 의해서 직렬화가
해제된다는 사실을 기억하도록 하자. 이러한 일련의 과정을 그림으로 나타내면 다음과 같다.
[그림 객체 직렬화]
여기에 사용되는 ObjectOutputStream과 ObjectInputStream은 모두 java.io 패키지의 일부분이고, 각각 DataOutput과 DataInput클래스를 implement한 ObjectOutput과 ObjectInput을 extends 해서 만들어 진것이다. 그리고 이 두 클래스에는 비원시 객체와 배열등을 스트림에 읽고 쓰기 위한 메쏘드들이 추가되어져 있다.
[그림 클래스 구조도]
객체 직렬화의 과정
지금부터는 보다 자세히 객체의 직렬화 과정에 대해 알아보도록 하자.
객체는 ObjectOutputStream의 writeObject() 메쏘드에 자신을 넘김으로써 직렬화 된다.
WirteObject()메쏘드는 private 필드와 super 클래스로부터 상속받은 필드를 포함, 객체의 모든 것을 기록하게된다. 직렬화 해제는 직렬화와 반대의 과정을 거치게 되는데 ObjectInputStream의 readObject()메쏘드를
호출함으로써 스트림으로부터 읽어 들이고 이를 직렬화가 되기전의 객체로 다시 만들게 된다.
다음은 객체가 직렬화 되고 해제되어 원래의 객체로 복원되는 과정에 대한 간단한 예제이다.
눈여겨 볼 부분은 ObjectInputStream을 생성해서 writeObject()를 사용해서 객체를 직렬화 하는것과 ObjectInputStream을 생성해서 readObject()를 통해서 객체를 복원하는 부분이다. 또한 SerializableClass가 Serializable을 implements한 것을 주의해서 보길 바란다.
import java.io.*; public class ObjectSerializeTest { public static void main(String[] args) throws Exception { // 객체 스트림을 열고, 객체스트림을 통해 객체를 파일에 저장 // 스트림을 닫는다. /* 직렬화 된 객체가 저장된 파일로 부터 객체를 해제시켜 원래의 객체를 복원*/ // 객체 입력 스트림으로부터 객체를 읽어온다. // 스트림을 닫는다. /* 스트림으로부터 읽어들인 객체의 내용을 출력 원래 생성되었던 객체와 같은 값을 갖는다는 것을 알수가 있다. */ /* 하나의 문자열과 정수를 저장하고있는 클래스 class SerializableClass implements Serializable { public String mString; // 생성자 [실행방법] javac.exe ObjectSerializeTest.java
[실행결과] String : Serialize Test Program
|
커스텀 직렬화
- Serializable
스트림을 통해서 직렬화 또는 해제 될수 있는 객체는 Serialiazable을 implements한 객체만이 가능하다.자바 2에서는 java.awt, javax.swing, etc등의 클래스들이 Serializable을 implements하고 있으며, 이러한 상위 클래스들이 implements하므로 하위 클래스들 또한 같이 직렬화/해제의 범주에 들 수가 있다.
다음은 Serializable을 implements하고있어서 객체의 직렬화가 가능한 객체들의 리스트이다.
[직렬화 가능 객체 리스트]
패키지 | Serializable |
java.awt | BorderLayout, CardLayout, CheckboxGroup, Color, Component, ComponentOrientation, Cursor, Dimension, Event, FlowLayout, FocusManager, Font, FontMetrics, GraphicsConfigTemplate, GridBagConstraints, GridBagLayout, GridBagLayoutInfo, GridLayout, ImageMediaEntry, Insets, LightweightDispatcher, MediaTracker, MenuComponent, MenuShortcut, Point, Polygon, Rectangle, ScrollPaneAdjustable, SystemColor |
java.awt.dnd | DropTarget |
java.awt.font | TransformAttribute |
java.awt.geom | AffineTransform |
java.awt.image.renderable | ParameterBlock |
java.beans | PropertyChangesSupport, VetoableChangeSupport, BeanContext, BeanContextChildSupport, BeanContextSupport |
java.io | ExternalizableFile, FilePermission, FilePermissionCollection, ObjectStreamClass |
java.net | InetAddress, SocketPermission, SocketPermissionCollection, URL |
java.rmi | MarshalledObject |
java.rmi.activation | ActivationDesc, ActivationGroupDesc, ActivationGroupID, ActivationID |
java.rmi.dgc | Lease, VMID |
java.rmi.server | ObjID, RemoteObject, UID |
java.security | AllPermissionCollection, BasicPermission, BasicPermissionCollection, CodeSource, GuardedObject, Identity, Key, KeyPair, Permission, PermissionCollection, Permissions, PermissionsHash, SecureRandomSpi, SignedObject, UnresolvedPermission, UnresolvedPermissionCollection |
java.text | BreakIterator, Collector, DateFormatSymbols, DecimalFormatSymbols, Format, SpecialMapping, TextBoundaryData, UnicodeClassMapping, WordBreakTable |
java.util | ArrayList, BitSet, Calendar, Date, EventObject, HashMap, HashSet, Hashtable, LinkedList, Locale, PropertyPermissionCollection, Random, TimeZone, TreeMap, TreeSet, Vector |
javas.swing.table | AbstractTableModel, DefaultTableCellRenderer, DefaultTableColumnModel, DefaultTableModel, TableColumn |
javax.swing.text | AbstractDocument, EditorKit, GapContext, SimpleAttributeSet, StringContent, StyleContext, TabSet, TabStop |
javax.swing.tree | DefaultMutableTreeNode, DefaultTreeModel, DefaultTreeSelectionModel, TreePath |
Serializable은 Cloneable과 같은 마커 인터페이스로서, 어떠한 메쏘드들을 정의해놓은 것이 아닌, serizlVersionUDI라는 변수 하나만을 가지며, 이 객체가 직렬화가 가능하다는 것을 알려주는 역할만을 하는 인터페이스일 뿐이다.
- transient
스트림을 이용해서 직렬화 하는데 있어서, 커다란 프로그램 전체가 직렬화된다면, 여러가지 면에서 많은 낭비일 것이다. 예를 들어, 한 객체가 마우스가 눌려진 위치를 알기 위해서 마우스 클릭시에 위치를 저장하는 필드를 가지고 있다고 가정하자, 이 경우 마우스의 위치값은 프로그램이 돌아가는 상태에서 마우스가 눌려졌을 당시에만 유효한 값으로, 객체가 직렬화 되었다가 해제 되었을 경우에는 쓸모없는 값이 되어버린다.
이런 객체 직렬화에 쓸모없는 값들은 transient로 선언해 줌으로써 객체 직렬화에서 제외되어질수 있다.
- Private transient int x;
이러한 선언은 플랫폼에 따라 다른 값을 가지는 필드나, 현재의 환경에서만 유효한 필드등을 객체 직렬화에서 제외하는데 유용하게 쓰일 수가 있다.
transient선언이 적용된 예이다.
import java.io.*; public class ObjectSerializeTest1 extends TestClass implements Serializable { // 변수 선언, x는 객체직렬화에서 제외되도록 transient로 선언 // 생성자 Serializable을 implements한 i와 x는 받아온 인자를 그대로 대입하고, TestClass를 extends한 j는 /* 객체직렬화를 하기전의 객체의 값들을 알아본뒤에, 해당 객체를 스트림에 직렬화 시킨다.*/ /* 스트림으로 부터 객체를 해제시킨다. */ /* 객체의 값을 알기 위해서 오버라이드 */ public static void main(String[] args) throws Exception { // 객체를 생성시킨뒤, 스트림에 객체를 쓴다.(10은 transient로 선언된 필드의 값임을 유의) // 정보를 저장한 화일을 열고 스트림을 통해 읽은뒤 객체직렬화를 해제시킨다. // print문 수행시 형 변환이 이루어져서 이미 오버라이드된 toString()메쏘드가 수행된다. /* Serialize되지 않은 클래스 객체 직렬화 작업으로부터는 제외, 만약 사용자가 이 클래스를 객체 직렬화에 사용하고 싶다면 Serializable을 implements하면 된다. 만약 그렇게 할수 없을 경우에는 위와 같이 하도록 한다. */ [실행방법] javac.exe ObjectSerializeTest1.java
[실행결과] writeObject
|
- externalizable
객체직렬화의 또 다른 방법으로는 Externalizable 인터페이스를 들수 있는데, 이 방법은 Serializable보다 더 강력하게 객체 직렬화를 제어하고 싶을 경우에 사용되어진다. 어떤 클래스가 이 인터페이스를 구현하게 되면 readExternal()과 writeExternal()메쏘드를 사용하여 객체를 스트림으로부터 읽고 쓸수있다.
Externalizable객체들은 자신의 상태를 직렬화 및 해제하는 과정에서 강력한 제어를 할수 있으며, Serializable 객체에서의 기본 데이터 포멧을 사용하고싶지 않을때에 사용한다.
다음은 externalizable을 사용한 객체 직렬화의 예이다.
import java.io.*; public class ObjectExternalizeTest implements Externalizable { int i; /* 인자없는 생성자(직렬화된 데이터를 읽어들여서 객체를 만들기 위해 필요) */ /* 생성자 */ /* readObject()메쏘드로부터 호출이 되며, 직렬화된 객체를 해제시킨다. */ /* writeObject()메쏘드로부터 호출이 되며, 변수들을 직렬화한다.*/ /* 변수들의 값을 보여준다.*/ public static void main(String[] args) throws Exception { // 객체가 직렬화되어 저장된 파일로부터 스트림을 얻어온다. // 객체 직렬화 이전의 객체와 직렬화를 거친 뒤 다시 해제된 객체의 값을 출력한다.
[실행방법] javac.exe ObjectExternalizeTest.java [실행결과] Execute writeExternal() 1000, Externalizable Test
|
애플릿 직렬화
객체직렬화는 애플릿을 직렬화하는데에도 사용이 되어진다. 이러한 경우 <Applet>태그는 클래스 파일 대신에 직렬화 된 객체 파일을 지정하게 되고, 브라우저는 이러한 태그를 만나면 직렬화된 애플릿을 해제하여 화면에 보여주게 된다. 즉, 그래픽 유저 인터페이스를 생성하기 위한 코드를 전혀 가지고 있지 않으면서도 완벽한 그래픽 유저 인터페이스를 가지게 되는것이다.
일반적으로 직렬화된 애플릿은 *.ser의 확장자를 가진다.
직렬화된 애플릿을 호출하는 html 파일은 다음과 같은 내용을 가진다.
- <Applet Object="SerializableApplet.ser" Width=300 Height=300></Applet>
소켓을 통한 객체 직렬화
소켓을 생성한 뒤 스트림을 얻어서 ObjectInputStream과 ObjectOutputStream을 얻어서 사용한다면,
파일이 아닌 네트웍 너머의 다른 컴퓨터와도 객체를 직접 주고 받을수 있다.
소켓을 통해 직렬화 되어 쓰고 읽혀질 객체는 역시 Serializable을 구현하고 있어야 하며,
사용법은 파일에 쓰고 읽는것과 동일하다. 다음은 서버에서 이미지를 포함하고 있는 Serializable을 구현한
객체를 생성하여 소켓을 이용하여 클라이언트 애플릿에게 직렬화된 객체를 보내고, 애플릿에서는 이를
해제하여 화면에 나타내는 예제이다. 여기서 주의깊게 살펴보아야 할 점은 이미지를 직렬화 하기 위한
방법이다. 이미지는 Serializable을 구현하고 있지 않기 때문에 직접적으로 객체 직렬화에 사용되어질 수는
없다. 하지만, 자바 2에서는 배열을 직렬화에 사용할 수가 있고, 이미지는 각각의 픽셀들이 배열에
저장되어질수 있으므로 직접적으로 이미지를 직렬화 할 수는 없지만 배열을 사용하여 간접적으로나마
이미지를 객체 직렬화에 사용할 수가 있다.
- 서버측 프로그램
import java.awt.*; public class ImageSerializeServer { // 서버는 3434 포트로 임의로 설정 // ImageSerializeServer를 실행한다. /* 생성자. 서버의 소켓을 생성하고 클라이언트의 연결을 대기 */ // 포트를 지정(3434) // 지정된 포트로 서버의 소켓을 생성한다. // 클라이언트의 연결을 대기 /* 클라이언트로 부터의 연결을 기다리면서 무한루프를 돈다. */ // 클라이언트로 부터의 연결이 요청되면 클라이언트의 소켓을 생성 하고 이미지 픽셀을 클라이언트로 보내는 쓰레드를 생성한다. /* 간단한 이미지를 이루는 픽셀을 만들어 이를 네트웍너머의 클라이언트로 보내는 클래스 */ // 변수 선언 // 간단한 이미지 픽셀 public appletConnection (Socket client) { // 객체 직렬화를 위한 출력 스트림을 생성 public void run () { /* Serializable을 구현한 imagePixel객체를 생성해서 Vector에 집어넣는다. 따라서 이미지가 하나가 아닌 여러개 일지라도 객체 직렬화를 통해 전달하는것이 가능하다.*/ // 이미지 픽셀들을 포함하고 있는 벡터를 객체 직렬화 시켜 스트림에 쓴다. /* 객체의 직렬화를 위해 Serializable을 구현한 imagePixel 클래스 */ // 생성자 [실행방법] javac.exe ImageSerializeServer.java Server Running.....(서버가 대기하고 있는 상태)
|
- 클라이언트측 프로그램
import java.applet.*; public class ImageSerializeClient extends Applet { // 기본 포트는 서버와 같이 3434로 하고 host는 localhost로 설정한다. // 변서 선언 public void init() { /* 서버에 접속하여 소켓을 구한뒤에 객체 직렬화를 해제하기 위한 스트림을 얻는다. 이것은 마치 파일로부터 스트림을 얻는 과정과 비슷하다. */ public void start() { // 객체를 스트림으로 부터 읽어들여 직렬화를 해제한다. // 직렬화가 해제된 객체는 Vector에 들어가게 되고 Vector의 요소인 imagePixel을 구한다. // 이렇게 구해진 imagePixel의 이미지 픽셀을 가지고 실제 이미지를 생성 // 생성된 이미지로 그림을 그린다. [실행방법] javac.exe ImageSerializeClient.java
|
- 애플릿 포함 Html 내용
<HTML> <APPLET CODE = "ImageSerializeClient.class" WIDTH = 100 HEIGHT = 100 ></APPLET> </BODY>
|
- 최종 결과 화면
프로그램을 실행하기 위해서는 다음의 과정으로 실행하면 된다.
- 서버측 프로그램인 ImageSerializeServer.java를 컴파일하고, 서버 쪽에서 실행시면, "Server Running..."이라는 메시지가 나타나면서 서버측 프로그램이 제대로 작동을 하는 것이다.
- 다음으로는 클라이언트 애플릿 프로그램인 ImagSerializeClient.java를 컴파일한다.
- 브라우져를 실행시킨뒤에 클라이언트 애플릿 클래스를 실행시키기 위한 html파일이 있는 URL주소를 주소 입력란에 넣는다.
- 이때 서버프로그램이 돌아가는 곳과 애플릿을 위한 코드 및 html파일이 위치한 곳은 같은 Machine상에 있어야 한다.
참고로 이번 예제에서는 이미지 픽셀을 직접 그 값을 대입해서 이미지를 대신했는데, 실제 이미지를 이미지 픽셀로 바꾸는 기능이 자바에서 지원되고 있으므로 이를 간단히 소개토록 하겠다. 이미지는 여러개의 색깔을 표현하고 있는 픽셀들의 집합으로 볼 수 있고, 보다 정교하게 이미지를 다루는 작업을 하고 싶다면, 이미지의 픽셀들을 얻어서 조작을 해야한다. 그렇다면 어떻게 이미지의 픽셀들을 얻을 수 있을까? 그것은 PixelGrabber를 통해서 가능하다. PixelGrabber는 이미지와 해당 이미지의 정보들을 기반으로 이미지 픽셀을 구해준다.
다음은 실제로 이러한 것을 보여주는 코드이다.
. /* imageObject로부터 픽셀들을 얻어서 imagePixel배열에 저장한다. */ imagePixel = new int[imageWidth * imageHeight]; // imagePixel을 초기화 한다. // grabber를 생성해서 픽셀들을 얻는다. if((grabber.status() & ImageObserber.ABORT) != 0) |
참고 문헌
- Java Examples In a Nutshell (O'RELLY)
- Java I/O (O'RELLY)
'Development > Java' 카테고리의 다른 글
java - 한글 초성 검색 (0) | 2013.08.30 |
---|---|
java - 한글 인코딩 유니코드 (0) | 2013.08.30 |
rmi - Hessian Web Service Protocol – Hello World Example (0) | 2013.08.09 |
java - 이미지 사이즈 변경(image resize) (0) | 2013.06.22 |
java - listener(callback) 만들기 2 (0) | 2013.04.08 |