본문으로 건너뛰기
김신건의 로그

[Java] JavaBean 프로퍼티 규약

· 수정 · 📖 약 2분 · 819자/단어 #java #javabean #convention #reflection
JavaBean, Java Bean, POJO, 자바빈, getter setter convention, @ConstructorProperties

정의

JavaBean 은 다음 세 가지 규약을 만족하는 클래스:

  1. public no-args constructor (인자 없는 public 생성자)
  2. 프로퍼티 접근자: getX()/setX() 명명 규칙
  3. Serializable 구현 (선택, 일부 컨텍스트에서 필수)

원래는 1996 년 데스크톱 컴포넌트 모델용이었지만, 지금은 reflection 기반 framework 가 객체 상태에 접근하는 표준 인터페이스 로 살아남았다. Jackson, JPA/Hibernate, Spring Data Binding, Bean Validation 등이 이 규약에 의존.

POJO (Plain Old Java Object) 는 더 느슨한 개념. 특정 framework 인터페이스를 상속하지 않은 평범한 객체. JavaBean 은 POJO 의 한 종류.

명명 규칙

public class User implements Serializable {
    private String name;
    private int age;
    private boolean active;

    public User() {}                              // no-args ctor

    public String getName() { return name; }      // getter
    public void setName(String name) { this.name = name; }  // setter

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    public boolean isActive() { return active; }  // boolean 은 is- prefix
    public void setActive(boolean active) { this.active = active; }
}
  • getX() / setX(T): 일반 프로퍼티
  • isX() / setX(boolean): boolean 프로퍼티 (getX() 도 허용되나 isX() 가 관례)
  • property 이름은 메서드명에서 prefix 제거 후 첫 글자만 소문자 : getURL()URL, getFirstName()firstName
  • getXxx() 만 있고 setXxx() 없으면 read-only property

Introspector

java.beans.Introspector 가 reflection 으로 메서드를 스캔해 BeanInfo 를 만든다:

BeanInfo info = Introspector.getBeanInfo(User.class);
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
    System.out.println(pd.getName() + " : " + pd.getPropertyType());
}
// active : boolean
// age : int
// name : String
// class : Class  ← Object.getClass() 도 잡힘

Spring 의 BeanWrapper, Jackson 의 ObjectMapper, JPA 의 EntityManager 모두 내부적으로 이 메커니즘을 변형해 사용.

java
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

public class IntrospectDemo {
  public static class User {
      private String name;
      private boolean active;
      public String getName() { return name; }
      public void setName(String n) { this.name = n; }
      public boolean isActive() { return active; }
      public void setActive(boolean a) { this.active = a; }
  }

  public static void main(String[] args) throws Exception {
      BeanInfo info = Introspector.getBeanInfo(User.class, Object.class);
      for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
          System.out.printf("%-10s %s%n", pd.getName(), pd.getPropertyType().getSimpleName());
      }
  }
}
결과
active     boolean
name       String

IntrospectorgetXxx/setXxx/isXxx 메서드를 스캔해 property 로 인식.

no-args constructor 의 이유

framework 는 객체를 두 단계 로 생성:

  1. Class.newInstance() 로 빈 객체 생성 (no-args ctor 필요)
  2. setter 로 필드 채우기

DB row → entity, JSON → DTO, form → command object 같은 변환 패턴. JPA 는 @Entity 에 no-args ctor 를 명시적으로 요구한다.

이게 불변 객체와 충돌. 필드를 final 로 만들고 ctor 에서만 set 하려면 no-args ctor 가 불가능. 해결책 두 가지:

@ConstructorProperties

public class User {
    private final String name;
    private final int age;

    @ConstructorProperties({"name", "age"})
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
}

Jackson 이 @ConstructorProperties 를 보고 ctor 인자명 → JSON 키 매핑. JPA 는 인식 안 함 (proxy 생성을 위해 no-args 가 여전히 필요).

record (JDK 14+)

public record User(String name, int age) {}

자동 생성:

  • private final 필드
  • 모든 필드를 받는 canonical constructor
  • name(), age() accessor (getName() 아님 주의)
  • equals/hashCode/toString

대부분 modern framework 가 record 를 직접 지원 (Jackson 2.12+, Spring Boot 2.6+). JPA 는 아직 entity 로는 부적합 (mutable 필요).

record 와 JavaBean 의 차이: accessor 이름이 name() 이지 getName() 이 아님. JavaBean 규약을 따르지 않으므로 일부 레거시 framework 는 record 인식 못 함.

Lombok 과의 관계

@Data  // = @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private int age;
}

Lombok 은 컴파일 타임에 JavaBean 보일러플레이트를 자동 생성. JavaBean 규약을 매뉴얼하게 지키는 것과 동등.

다만 @Data 는 mutable + equals/hashCode 자동 구현 둘 다 들어가서 JPA entity 에 쓰면 양방향 연관관계에서 무한 루프 / 영속성 컨텍스트 문제 발생. JPA entity 에는 @Getter + @Setter 정도만 권장.

framework 별 의존

Framework요구사항비고
JPA / Hibernateno-args ctor (public 또는 protected), getter/setterproxy 생성을 위해
Jacksonno-args ctor 또는 @JsonCreator / @ConstructorPropertiesrecord 도 지원
Spring @ConfigurationPropertiessetter (또는 record)환경 변수 → 객체 binding
Bean Validationgetter필드/메서드 어디든 어노테이션 가능
Spring MVC (@ModelAttribute)no-args ctor + setterform binding
Spring JdbcTemplate + BeanPropertyRowMapperno-args ctor + setter (또는 record)RowMapper 가 reflection 사용

베스트 프랙티스

  • API 응답 / 요청 DTO: record 우선
  • JPA entity: 클래스 + @Getter + protected no-args ctor (Lombok @NoArgsConstructor(access = PROTECTED))
  • @ConfigurationProperties: JDK 16+ 에선 record 권장 (spring.application.name 같은 단순 binding)
  • 불변 DTO 가 필요한데 record 가 안 맞을 때: @ConstructorProperties 또는 builder pattern

관련 위키

이 글의 용어 (5개)
[Spring] Bean Validation: @Valid, @NotBlank, customspring
정의 Spring은 Jakarta Bean Validation (JSR-380)을 표준으로 사용. , , 등 어노테이션으로 선언적 검증. Spring MVC가 로 자동 통합. 설…
[Spring] IoC와 DI: Bean, ApplicationContext, @Autowiredspring
정의 IoC (Inversion of Control)는 객체 생성/생명주기 제어를 프레임워크가 가져가는 원칙. DI (Dependency Injection)는 그 구현 방법. S…
[Spring] JdbcTemplate, NamedParameterJdbcTemplatespring
정의 JdbcTemplate은 JPA 없이 raw SQL을 안전하게 다루는 Spring 표준. boilerplate 제거 (try-with-resources, exception …
[Spring] Spring Data JPA: Repository, Query Methodsspring
정의 Spring Data JPA는 JPA(Hibernate) 위에 Repository 추상화를 얹어 boilerplate를 제거. 인터페이스만 정의하면 Spring이 구현체 자…
[Spring] Spring MVC: @RestController, @RequestMappingspring
정의 Spring MVC는 Servlet 기반 동기 웹 프레임워크. 이 요청을 받아 핸들러(컨트롤러 메서드)에 라우팅. REST API와 전통 MVC(HTML) 모두 지원. We…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기