프로젝트를 진행하며 자바와 외부 API의 소통을 하려면 자바에서 사용하는 객체를 전송하는게 아닌 API서버에서 원하는 정보를 보내줘야한다. 그러면 객체를 자바 외부로 전송하려면 어떻게 해야할까?
Java I/O처리는 정수, 문자열, 바이트 단위의 처리만 지원하기 때문에 복잡한 객체의 내용을 네트워크 상으로 전송하기 위해서는 변환처리가 필요한데(Object - > byte) 이를 직렬화라고 한다. 역직렬화는 변환된 바이트 형태를 다시 객체로 변환 시키는 것이다.
시스템 적으로 본다면 JVM의 힙, 스택 메모리에 있는 클래스 인스턴스를 바이트 형태로 변환하는 것이 직렬화이고, 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM에 올리는 것을 역직렬화라고 한다.
자바 직렬화의 조건
- Primitive 타입이거나 java.io.Serializable 인터페이스의 구현체 클래스이거나, 이를 상속받아야 한다.
- 기본형이 아닌 객체도 멤버로 포함하여 직렬화가 가능하지만 멤버로 사용되는 객체 class도 Seriablizable Interface를 구현해야 한다.
- 직렬화를 할 때 직렬화에 포함하지 않을 멤버는 transient , static키워드를 사용해 Serializable 대상에서 제외 시킬 수 있다.
Serializable 인터페이스는 현재 클래스의 객체가 직렬화를 제공할 수 있음을 JVM에게 알려주는 역할을 한다.
class User implements Serializable{
private String name;
private Integer age;
private Address address;
public User(String name, Integer age, Address address){
this.name = name;
this.age = age;
this.address = address;
}
}
public Address implements Serializable{
private String contry;
private String bunzi;
public Address(String contry, String bunzi){
this.contry = contry;
this.bunzi = bunzi;
}
}
자바의 직렬화
java.io.ObjectOutputStream을 이용해 직렬화를 진행한다.
public class Main {
public static void main(String[] args) throws IOException {
User user = new User("jack", 12);
byte[] serializedUser;
try(ByteArrayOutputStream out = new ByteArrayOutputStream()){
try(ObjectOutputStream os =new ObjectOutputStream(out)){
os.writeObject(user);
serializedUser = out.toByteArray();
}
}
System.out.println(Base64.getEncoder().encodeToString(serializedUser));
}
}
rO0ABXNyAA5zYW1zdW5nMDEuVXNlcsrqQpH48pRCAgACTAADYWdldAATTGphdmEvbGFuZy9JbnRlZ2VyO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAMdAAEamFjaw==
자바 역직렬화의 조건
- 역직렬화 대상이 된 객체의 클래스가 클래스 패스에 존재해야하고, import 되어있어야 한다.
- 직렬화와 역직렬화를 진행하는 시스템이 다를 수 있는 것을 고려해야하고 직렬화한 클래스의 속성과 역직렬화를 수행하는 클래스 속성이 일치해야하는데 이는 동일한 serialVersionUID를 갖고 있는지로 판단한다.
자바 역직렬화
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String base64User = "rO0ABXNyAA5zYW1zdW5nMDEuVXNlcsrqQpH48pRCAgACTAADYWdldAATTGphdmEvbGFuZy9JbnRlZ2VyO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAMdAAEamFjaw==";
byte[] serializedUser = Base64.getDecoder().decode(base64User);
try(ByteArrayInputStream in = new ByteArrayInputStream(serializedUser)){
try(ObjectInputStream os =new ObjectInputStream(in)){
Object objectUser = os.readObject();
User user = (User)objectUser;
System.out.println(user);
}
}
}
}
package.User@6ea12c19
Spring Boot의 직렬화 및 역직렬화
Rest API 측면에서 직렬화는 스프링 부트가 자바 객체를 JSON으로 변환하는 것이고, 역직렬화는 JSON을 객체로 변환하는 것이다
직렬화 사용처
서블릿 세션
세션을 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션을 선택하게 되면 세션 자체가 직렬화된채로 저장된다.
스프링 시큐리티에서도 세션을 이용할 때 세션 저장을 할 객체는 Serializable 처리를 해줘야한다.
캐시
똑같은 SQL 호출시 캐싱을 통해 기존에 담아두었던 데이터를 재사용하는데 이때 캐시를 사용하고, 캐시의 구조가 직렬화 구조다.
직렬화 사용시 주의점
SUID(SerialVersionUID)는 Default로 클래스의 기본 해쉬값을 사용하므로 설정을 안하면 클래스에 프로퍼티가 추가되거나 타입이 변경되면InvalidClass Exception이 발생하므로 개발하며 직접 관리를 하는게 좋다.
외부서버에 저장하지만 장기적으로 보관할때는 사용안하는 편이 나은데, 역직렬화시 장기간 직렬화되어 저장된 정보는 오염된 데이터일 가능성이 높기 때문이다.