UpdateProfileReqDTO 리팩토링 with SonarLint

Kevin·2023년 11월 20일
post-thumbnail

🧑‍🏫SonarLint 선생님의 회초리

내 코드

/**
 * UpdateProfileReqDTO : 프로필 수정 요청 DTO
 *
 * @author kevin
 * @version 1.0
 */
@NoArgsConstructor
@Getter @Setter
public class UpdateProfileReqDTO implements Serializable {

    private String profileContent;

    private MultipartFile profileImage;
}

1️⃣ 첫번째 회초리 : Fields in a "Serializable" class should either be transient or serializable

By contract, fields in a Serializable class must themselves be either Serializable or transient. Even if the class is never explicitly serialized or deserialized, it is not safe to assume that this cannot happen. For instance, under load, most J2EE application frameworks flush objects to disk.

An object that implements Serializable but contains non-transient, non-serializable data members (and thus violates the contract) could cause application crashes and open the door to attackers. In general, a Serializable class is expected to fulfil its contract and not exhibit unexpected behaviour when an instance is serialized.

This rule raises an issue on:
non-Serializable fields,
collection fields when they are not private (because they could be assigned non-Serializable values externally),
when a field is assigned a non-Serializable type within the class.


SonarLint 선생님 회초리 요약

직렬화 가능한 클래스의 필드는 해당 필드 자체가 직렬화 가능하거나 transient로 선언되어야 한다.
이 규칙은 클래스가 명시적으로 직렬화 또는 역직렬화되지 않더라도 해당 클래스의 인스턴스가 직렬화되어 저장되거나 전송될 수 있음을 감안한 것이다.

직렬화되지 않은 필드,

컬렉션 필드가 비공개가 아닌 경우 (외부에서 비직렬화 가능한 값을 할당할 수 있기 때문),

클래스 내에서 필드가 비직렬화 가능한 유형으로 할당되는 경우에 문제를 일으킬 수 있다.


SonarLint 선생님 추천 예시 코드

// Before
public class Address {
    ...
}

public class Person implements Serializable {
  private static final long serialVersionUID = 1905122041950251207L;

  private String name;
  private Address address;  // Noncompliant, Address is not serializable
}

// After
public class Address {
    ...
}

public class Person implements Serializable {
  private static final long serialVersionUID = 1905122041950251207L;

  private String name;
  private Address address; // Compliant, writeObject and readObject handle this field

  private void writeObject(java.io.ObjectOutputStream out) throws IOException {
    // Appropriate serialization logic here
  }

  private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
    // Appropriate deserialization logic here
  }
}

내 생각 및 적용 후 코드

선생님께서는 MultipartFile profileImage; 코드에서 MultipartFile은 직렬화가 불가능하기에 transient 로 선언하거나, 직접 해당 File을 직렬화하는 것을 말씀하고 계신다.

해당 DTO는 역직렬화하기 위한 코드이다. 그렇기에 선생님의 말씀을 굳이 따를 필요는 없다고 생각했으나 여기서 다른 궁금증이 들었다.

나는 @ModelAttribute를 사용해서 클라이언트로부터 해당 DTO로 요청을 받게 되는데, 왜 이 때 Serailizer가 굳이 필요한가?

@ModelAttribute를 사용할 때 먼저, Spring은 URL 파라미터 또는 POST Form Data 형태의 파라미터를 RequestDTO에 자동으로 바인딩해 주는데, 이때 반환되는 객체를 커맨드 객체라고 부른다.

그리고 커맨드 객체를 구성하기 위한 매핑 규칙은 다음과 같다.


1. NoArgsConstructor과 AllArgsConstructor 둘 다 있는 경우

NoArgsConstructor 호출하고, setter 호출하여 param을 필드에 각각 초기화한다.


2. AllArgsConstructor만 있는 경우

AllArgsConstructor 호출하여 param을 필드에 각각 초기화한뒤, setter 호출하여 param을 필드에 각각 다시 초기화하여 덮어 씌운다.

그러면 굳이 직렬화를 구현안하고, 아래와 같이 구성을 하면 코드가 동작한다.

@NoArgsConstructor
@Getter @Setter
public class UpdateProfileReqDTO {

    private String profileContent;

    private MultipartFile profileImage;
}
profile
Hello, World! \n

0개의 댓글