본문 바로가기
코딩 공부/web & Java

[Spring] 직렬화(Serialize)

by 현장 2023. 11. 15.

직렬화(Serialize)

직렬화란 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술과 바이트로 변환된 데이터를 다시 객체로 변환하는 기술인 역직렬화를 아울러서 이야기합니다.

 

시스템적으로 이야기하자면 JVM(Java Virtual Machine)의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술과 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태를 같이 이야기합니다.

🏷️ 직렬화 사용 방법

✅ 직렬화 조건

자바 기본(primitive) 타입과 java.io.Serializable 인터페이스를 상속받은 객체는 직렬화 할 수 있는 기본 조건을 가집니다.

public User implements Serializable {
    private Integer id;
    private String name;
    private LocalDate birthDate;
    
    public User(Integer id, String name, LocalDate birthDate) {
        this.id = id;
        this.name = name;
        this.birthDate = birthDate;
    }
    
    // getter, toString 생략
}

 

Spring Security에서는 UserDetails라는 Serializable이 extends된 인터페이스를 사용하기도 합니다.

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

 

✅ 직렬화 방법

자바 직렬화는 방법은 java.io.ObjectOutputStream 객체를 이용합니다.

public static void main(String[] args){
    User user = new User(1, "Hyeon", LocalDate.now().minusYears(27));
    byte[] serializedMember;
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(user);
            // serializedMember -> 직렬화된 user 객체
            serializedMember = baos.toByteArray();
        }
    }
    // 바이트 배열로 생성된 직렬화 데이터를 base64로 변환
    System.out.println(Base64.getEncoder().encodeToString(serializedMember));
}

 

위 예제에서 객체를 직렬 화하여 바이트 배열(byte []) 형태로 변환하였습니다.

 

Spring Security로 UserDetail을 사용한 경우

@Bean
public InMemoryUserDetailsManager createUserDetailMamager() {
    UserDetails userDetails = createNewUser("hyeon", "testpassword1");
    UserDetails userDetails2 = createNewUser("Soo", "testpassword2");
    return new InMemoryUserDetailsManager(userDetails, userDetails2);
}

private UserDetails createNewUser(String username, String password) {
    Function<String, String> passwordEncoder
            = input -> passwordEncoder().encode(input);

    UserDetails userDetails = User.builder()
            .passwordEncoder(passwordEncoder) // password 인코딩
            .username(username)
            .password(password)
            .roles("USER", "ADMIN")
            .build();
    return userDetails;
}

@Bean
public PasswordEncoder passwordEncoder() { // 인코더 메서드
    return new BCryptPasswordEncoder();
}

 

UserDetail로 반환하여 아이디를 생성합니다.

🏷️ 역직렬화(Deserialize) 사용 방법

✅ 역직렬화 조건

1. 직렬화 대상이 된 객체의 클래스가 클래스 패스에 존재해야 하며 import 되어 있어야 합니다.

 

✔️ 중요한 점은 직렬화와 역직렬화를 진행하는 시스템이 서로 다를 수 있다는 것을 반드시 고려해야 합니다.
(같은 시스템 내부이라도 소스 버전이 다를 수 있습니다.)

 

2. 자바 직렬화 대상 객체는 동일한 serialVersionUID 를 가지고 있어야 합니다.

  private static final long serialVersionUID = 1L;

 

✅ 역직렬화 예제

// 직렬화 예제에서 생성된 base64 User데이터 
String base64User = "...생략";
byte[] serializedUser = Base64.getDecoder().decode(base64User);
try (ByteArrayInputStream bais = new ByteArrayInputStream(serializedUser)) {
    try (ObjectInputStream ois = new ObjectInputStream(bais)) {
        // 역직렬화된 User 객체를 읽어온다.
        Object objectUser = ois.readObject();
        User user = (User) objectUser;
        System.out.println(User);
    }
}

🏷️ 다른 데이터 직렬화

1. CSV

데이터를 표현하는 가장 많이 사용되는 방법 중 하나로 콤마(,) 기준으로 데이터를 구분하는 방법입니다.

1,Hyeon,2015-11-15
// 자바에서 사용방법
User user = new User(1, "Hyeon", LocalDate.now().minusYears(27));
// User객체를 csv로 변환
String csv = String.format("%d,%s,%s",user.getId(), user.getName(), user.getBirthDate());
System.out.println(csv);

2. JSON

최근에 가장 많이 사용하는 포맷으로 자바스크립트(ECMAScript)에서 쉽게 사용 가능하고, 다른 데이터 포맷 방식에 비해 오버헤드가 적기 때문에 때문에 인기가 많습니다.

 

// 자바에서 사용방법
User user = new User(1, "Hyeon", LocalDate.now().minusYears(27));
// User객체를 json로 변환
String json = String.format(
                "{\"id\":\"%d\",\"name\":\"%s\",\"birthDate\":%s}",
                user.getId(), 
                user.getName(), 
                user.getBirthDate());
System.out.println(json);

 

🏷️ 직렬화를 사용하는 이유

복잡한 데이터 구조의 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화를 가능합니다. 

당연하게 보이는 장점 중에 하나지만 데이터 타입이 자동으로 맞춰지기 때문에 관련 부분을 큰 신경을 쓰지 않아도 됩니다. 

 

🏷️ 직렬화를 언제 사용되는가?

 JVM의 메모리에서만 상주되어있는 객체 데이터를 그대로 영속화(Persistence)가 필요할 때 사용됩니다.

🏷️ 직렬화를 어디서 사용되는가?

서블릿 세션 (Servlet Session)

서블릿 기반의 WAS(톰캣, 웹로직 등)들은 대부분 세션의 자바 직렬화를 지원하고 있습니다. 물론 단순히 세션을 서블릿 메모리 위에서 운용한다면 직렬화를 필요로 하지 않지만, 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하게 되면 세션 자체가 직렬화가 되어 저장되어 전달됩니다.

캐시 (Cache)

 캐시(Ehcache, Redis, Memcached, …) 라이브러리를 이용하면 DB에 대한 리소스를 절약할 수 있기 때문에 시스템을 많이 이용하게 됩니다.  캐시 할 부분을 자바 직렬화된 데이터를 저장해서 사용됩니다. 물론 자바 직렬 화만 이용해서만 캐시를 저장하지 않지만 가장 간편하기 때문에 많이 사용됩니다.

📖 Reference

우아한 기술 블로그

'코딩 공부 > web & Java' 카테고리의 다른 글

[Spring] HAL(Json Hypertext Application Language)  (1) 2023.11.19
[Spring] Actuator  (0) 2023.11.16
[Spring] HATEOAS  (1) 2023.11.14
[Spring] REST API 버전 관리  (0) 2023.11.12
[Spring] 국제화  (0) 2023.11.12