얼마 전 @Controller, @Service, @Repository등 @Component에 관한 어노테이션에 대한 내용을 정리하고, 심심해서 Lombok 어노테이션인 @AllArgsConstructor, @NoArgsConstructor, @RequiredArgsConstructor은 어떤 차이가 있을까 궁금증이 생겼다.
이런 궁금증이 생기면 안됐다.손대면 안 되는 것을 손대고 말았다.
각 어노테이션
어노테이션을 보면, 무엇이 다를까.
해당 어노테이션에 맞춰 생성자를 다르게 생성해주고 있었으니, 어떤 로직이 과연 다르게 만들고 있는 걸까?
AllArgsConstructor
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface AllArgsConstructor {
String staticName() default "";
AnyAnnotation[] onConstructor() default {};
AccessLevel access() default lombok.AccessLevel.PUBLIC;
@Deprecated
@Retention(RetentionPolicy.SOURCE)
@Target({})
@interface AnyAnnotation {}
}
NoArgsConstructor
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {
String staticName() default "";
AnyAnnotation[] onConstructor() default {};
AccessLevel access() default lombok.AccessLevel.PUBLIC;
boolean force() default false;
@Deprecated
@Retention(RetentionPolicy.SOURCE)
@Target({})
@interface AnyAnnotation {}
}
RequiredArgsConstructor
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface RequiredArgsConstructor {
String staticName() default "";
AnyAnnotation[] onConstructor() default {};
AccessLevel access() default lombok.AccessLevel.PUBLIC;
@Deprecated
@Retention(RetentionPolicy.SOURCE)
@Target({})
@interface AnyAnnotation {}
}
차이점
해당 어노테이션 클래스를 봤을 때, 차이점이 보이지 않았다.
굳이 찾아보면 @NoArgsConstructor 필드 내에 boolean 자료형의 변수 하나가 더 추가되어 있다는 것.
이름만 다르다는 것을 알게 되었다.
끝.
이면 좋겠지만.. 그렇다면, 이 친구들은 어디서 로직을 구분하고 있는 것인가.
내용물이 다 같은데 어떻게 결과물은 다 다른 것인가.
판도라의 상자를 열어버린 느낌이었다.
Lombok
일단, 해당 어노테이션이 언제 실행이 되는지 파악해야 한다.
컴파일 시점에 어노테이션 프로세서를 실행하며, 어노테이션을 분석하게 된다.
그 어노테이션 프로세서를 사용하려면 AbstractProcessor
를 상속받아 프로세서를 구현하는 클래스를 작성해야 하는데, 롬복은 롬복 자체적으로 AbstractProcessor
를 상속받고 있는 LombokProcessor
클래스가 구현하고 있다.
즉, 컴파일이 시작되며 AbstractProcessor
를 상속받고 있는 LombokProcessor
가 실행되면서 JavacAnnotationHandler
를 상속받은 클래스들을 분석합니다. 핸들러를 찾아내며 각각 처리할 어노테이션의 유형을 지정하게 된다.
지금 분석하고 있는 @All, No, RequiredArgsConstructor의 어노테이션은 HandleConstructor에서 처리하고 있는 것을 찾아낼 수 있었다.
약 300줄에 달하는 코드들이 존재하고 있으니, AllArgsConstructor에 해당하는 부분만 찾아보려고 한다.
HandleConstructor
public class HandleConstructor {
...
@Provides
public static class HandleAllArgsConstructor extends JavacAnnotationHandler<AllArgsConstructor> {
private static final String NAME = AllArgsConstructor.class.getSimpleName();
private HandleConstructor handleConstructor = new HandleConstructor();
@Override public void handle(AnnotationValues<AllArgsConstructor> annotation, JCAnnotation ast, JavacNode annotationNode) {
handleFlagUsage(annotationNode, ConfigurationKeys.ALL_ARGS_CONSTRUCTOR_FLAG_USAGE, "@AllArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor");
deleteAnnotationIfNeccessary(annotationNode, AllArgsConstructor.class);
deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel");
JavacNode typeNode = annotationNode.up();
if (!checkLegality(typeNode, annotationNode, NAME)) return;
List<JCAnnotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor", annotationNode);
AllArgsConstructor ann = annotation.getInstance();
AccessLevel level = ann.access();
if (level == AccessLevel.NONE) return;
String staticName = ann.staticName();
if (annotation.isExplicit("suppressConstructorProperties")) {
annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'.");
}
handleConstructor.generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode);
}
}
...
}
300줄이 넘는 코드가 다 연결되어 있지만, 일단 해당 코드만 보자.
해당 메서드, HandleAllArgsConstructor extends JavacAnnotationHandler<AllArgsConstructor>는 JavacAnnotationHandler를 상속받고 있으므로 컴파일을 진행할 때 LombokProcessor
가 실행되면서 찾게 된다.
불러온 어노테이션이 @AllArgsConstructor가 맞는지, 올바른 위치에 존재하는지 등을 다 확인하고 난 후
여러 가지 조건들을 거치고 나면, 하단의 handleConstructor.generateConstructor
를 통해 주어진 파라미터들을 사용하여 생성자를 생성하게 되는 것이다.
- 파라미터 내용
- typeNode: 어노테이션이 적용된 타입의 노드.
- level: 생성자의 액세스 레벨 (예: PUBLIC, PRIVATE 등).
- onConstructor: 생성자에 적용할 어노테이션 목록.
- fndAllFields(typeNode): 타입 노드에서 모든 필드를 찾습니다.
- false: 생성자에 @ConstructorProperties 어노테이션을 추가하지 않도록 지시합니다.
- staticName: 정적 생성자 이름 (비어 있으면 일반 생성자를 생성함).
- SkipIfConstructorExists.NO: 이미 존재하는 생성자를 무시할지 여부를 설정합니다. 여기서는 무시하지 않도록 설정합니다.
- annotationNode: 처리 중인 어노테이션의 노드.
AllArgsConstructor를 제외하고, 다른 No, Required 어노테이션 역시 구현되어 있으므로 확인해 보면 재미(?)가 있을 것 같다.
코드가 굉장히 어려워서 사실 잘 모르겠다 하하
정리
- 자바 컴파일시 'AbstractProcessor'를 상속 받고 있는 어노테이션 프로세서가 활동하며 어노테이션을 찾는다.
- 'LombokProcessor'가 'AbstractProcessor'상속받아 어노테이션 프로세서로 활동하며 롬복의 `JavacAnnotationHandler`를 상속받고 있는 클래스 및 어노테이션을 분석한다.
- 생성자를 생성해주는 핸들러는 `JavacAnnotationHandler` 상속받은 'HandleConstructor'클래스 입니다.
- 여기서 어노테이션 이름에 따라(@All, @Required, @No..) 메서드가 생성자를 생성하는 로직을 실행합니다.
'Server > Java' 카테고리의 다른 글
[Java] ResponseEntity<> 사용 이유? (1) | 2023.05.15 |
---|---|
[Java] Builder 패턴이란? @Builder (0) | 2023.05.03 |
[JDBC] 데이터베이스 연결 (0) | 2022.11.08 |
[JDBC] JDBC와 최신 데이터 접근 기술 (0) | 2022.11.02 |
[JDBC] JDBC의 이해 (0) | 2022.11.02 |