티스토리 뷰

반응형

✏️ 들어가기 전에 — Lombok의 @ToString이란?

Lombok@ToString을 클래스에 붙이면 컴파일 시점에 모든 필드를 포함한 toString() 메서드를 자동으로 생성해준다.

@ToString
public class User {
    private Long id;
    private String name;
    private String password;
}

위 코드는 컴파일 후 아래와 동일하게 동작한다.
편리하지만 password처럼 민감한 필드까지 그대로 출력된다는 문제가 있다. 이를 해결하기 위한 방법이 exclude@ToString.Exclude다.

@Override
public String toString() {
    return "User(id=" + id + ", name=" + name + ", password=" + password + ")";
}

 

✏️ 방법 1 — @ToString(exclude = {...}) : 클래스 레벨

클래스 선언부의 어노테이션에 제외할 필드명을 문자열 배열로 나열하는 방식이다. 생성되는 toString()은 아래와 같다.

@ToString(exclude = {"password", "clientSecret"})
public class UserConfig {
    private Long id;
    private String name;
    private String password;      // toString()에서 제외
    private String clientSecret;  // toString()에서 제외
    private String redirectUri;
}
@Override
public String toString() {
    return "UserConfig(id=" + id + ", name=" + name + ", redirectUri=" + redirectUri + ")";
}

 

✏️ 방법 2 — @ToString.Exclude : 필드 레벨

 

제외하고 싶은 필드 바로 위에 직접 어노테이션을 붙이는 방식이다. 생성되는 toString()은 방법 1과 동일하다.

@ToString
public class UserConfig {
    private Long id;
    private String name;

    @ToString.Exclude
    private String password;      // 이 필드만 제외

    @ToString.Exclude
    private String clientSecret;  // 이 필드만 제외

    private String redirectUri;
}

 

✏️ 결과는 같다 — 그렇다면 진짜 차이는?

1. 리팩토링 안전성

@ToString(exclude = {...})은 필드명을 문자열로 지정한다. 문자열은 컴파일러가 추적하지 않기 때문에, 필드명을 바꿔도 exclude 배열은 자동으로 갱신되지 않는다.

// Before
@ToString(exclude = {"clientSecret"})
public class OAuthConfig {
    private String clientSecret;
}

 

clientSecret이라는 필드가 없어졌으므로 exclude는 아무 효과도 없게 된다. 컴파일 에러도 발생하지 않고, IDE 경고도 없다. secretKey가 toString()에 그대로 노출되는 버그가 조용히 발생한다.

// After - 필드명을 변경했지만 exclude 배열은 그대로
@ToString(exclude = {"clientSecret"}) // ← "clientSecret"은 이제 존재하지 않는 필드명
public class OAuthConfig {
    private String secretKey; // 리팩토링으로 이름 변경
}

 

반면 @ToString.Exclude는 필드 선언 자체에 붙어 있으므로, IntelliJ에서 필드명을 Rename Refactoring하면 어노테이션도 해당 필드와 함께 이동한다. 필드가 삭제되면 어노테이션도 함께 사라진다.

@ToString
public class OAuthConfig {
    @ToString.Exclude
    private String secretKey; // 필드명을 바꿔도 @ToString.Exclude는 항상 이 필드에 붙어있다
}

2. 가독성

@ToString(exclude = {...})는 제외 규칙이 클래스 상단에 몰려 있다. 필드가 많아질수록 클래스 상단의 문자열 배열이 길어지고, 실제 필드 선언부를 봤을 때 이 필드가 로그에 찍히는지 아닌지를 즉시 파악하기 어렵다. 클래스 상단과 필드 선언부를 번갈아 확인해야 한다.

@ToString(exclude = {"password", "clientSecret", "refreshToken", "apiKey"})
public class SomeService {
    private String username;
    private String password;
    private String clientId;
    private String clientSecret;
    private String accessToken;
    private String refreshToken;
    private String apiKey;
    private String redirectUri;
}

@ToString.Exclude는 필드 선언 바로 위에 어노테이션이 있으므로, 코드를 읽는 순간 이 필드가 toString()에서 제외된다는 사실을 즉시 파악할 수 있다.

@ToString
public class SomeService {
    private String username;

    @ToString.Exclude
    private String password;       // 한눈에 파악 가능

    private String clientId;

    @ToString.Exclude
    private String clientSecret;   // 한눈에 파악 가능

    private String accessToken;

    @ToString.Exclude
    private String refreshToken;   // 한눈에 파악 가능

    @ToString.Exclude
    private String apiKey;         // 한눈에 파악 가능

    private String redirectUri;
}

3. 신규 필드 추가 시 실수 가능성

@ToString(exclude = {...}) 방식에서는 민감한 필드를 새로 추가할 때 exclude 배열에 추가하는 것을 빠뜨릴 수 있다.

@ToString(exclude = {"password", "clientSecret"})
public class OAuthConfig {
    private String password;
    private String clientSecret;
    private String newSensitiveKey; // 새로 추가했지만 exclude에 깜빡 추가 안 함 → 노출
}

@ToString.Exclude 방식은 필드 바로 위에 붙이므로 추가와 동시에 처리하게 되어 누락 가능성이 낮다.

@ToString
public class OAuthConfig {
    private String password;
    private String clientSecret;

    @ToString.Exclude               // 추가와 동시에 처리
    private String newSensitiveKey;
}

 

✏️  한 발 더 — onlyExplicitlyIncluded + @ToString.Include

 

exclude 방식의 근본적인 문제는 기본적으로 모든 필드가 출력된다는 것이다. 새 필드를 추가할 때마다 민감 여부를 판단해서 exclude에 추가해야 한다. 보안이 중요한 클래스라면 반대로 접근하는 것이 더 안전하다. 기본적으로 모든 필드를 숨기고, 명시적으로 허용한 필드만 출력하는 방식이다.
이 방식의 핵심 장점은 새로운 필드가 추가되더라도 기본적으로 출력에서 제외된다는 점이다. 개발자가 실수로 민감한 필드를 toString()에 노출시키는 사고를 구조적으로 방지할 수 있다.

@ToString(onlyExplicitlyIncluded = true) // 기본적으로 모든 필드 숨김
public class OAuthConfig {

    @ToString.Include
    private String clientId;       // 출력 O — 명시적으로 허용

    private String clientSecret;   // 출력 X — 명시 안 했으므로 자동 제외

    @ToString.Include
    private String redirectUri;    // 출력 O — 명시적으로 허용

    private String password;       // 출력 X — 명시 안 했으므로 자동 제외

    private String newSecretField; // 출력 X — 새로 추가해도 기본 제외
}

 

✏️  전체 비교 요약

구분 @ToString(exclude={}) @ToString.Exclude onlyExplicitlyIncluded
적용 위치 클래스 선언부 개별 필드 클래스 선언부 + 필드
기본 동작 전체 출력, 지정 필드 제외 전체 출력, 지정 필드 제외 전체 숨김, 지정 필드만 출력
리팩토링 안전성 문자열이라 자동 추적 안 됨 IDE 리팩토링 시 자동 반영 IDE 리팩토링 시 자동 반영
신규 필드 추가 시 수동으로 exclude에 추가 필요 수동으로 어노테이션 추가 필요 자동으로 숨겨짐
가독성 클래스 상단에 집중 필드 단위로 분산 필드 단위로 분산
권장 상황 제외 필드가 1~2개이고 변경이 거의 없을 때 제외 필드가 여러 개이고 리팩토링이 잦을 때 보안이 중요한 설정 클래스

 

✏️ 정리

 

셋 중 어느 방법을 선택하느냐는 결국 새 필드가 추가됐을 때 기본값을 노출로 할 것인가, 숨김으로 할 것인가의 문제다.

일반적인 클래스라면 @ToString.Exclude가 리팩토링 안전성과 가독성 면에서 @ToString(exclude = {...})보다 낫다.
하지만 API 키, 시크릿, 비밀번호 등 민감한 설정값이 많은 클래스라면 onlyExplicitlyIncluded + @ToString.Include 조합으로 출력을 허용 목록(allowlist) 방식으로 관리하는 것이 가장 방어적인 선택이다.

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함