똑같은 삽질은 2번 하지 말자

Lombok(롬복)은 어떻게 동작하는 걸까? 본문

Spring

Lombok(롬복)은 어떻게 동작하는 걸까?

곽빵 2020. 9. 19. 19:08

Lombok(롬복)을 사용하는 이유 ? 

밑의 사진을 보면 코드가 상당히 짧고 읽기 쉽지만,

 

롬복을 사용 안하고 직접 코드로 구현하려고 한다 하면?

이렇게 애노테이션들을 코드로 다 구현한 것도 아닌데, 이미 코드가 상당히 길어지고 있다.

여기서 이 클래스에 비지니스 로직이라도 들어있으면 점점 코드는 길어져서 가독성이 떨어진다.

 

그러므로 우리는 롬복이라는 친구를 사용하고 있다.

 

그럼, Lombok은 어떻게 동작하는 걸까?

  Lombok은 컴파일 시점에 애노테이션 프로세서를 사용하여 소스코드의 AST(abstract syntax tree)를 조작한다.

 

애노테이션 프로세서 문서

https://docs.oracle.com/javase/8/docs/api/javax/annotation/processing/Processor.html

 

Processor (Java Platform SE 8 )

The interface for an annotation processor. Annotation processing happens in a sequence of rounds. On each round, a processor may be asked to process a subset of the annotations found on the source and class files produced by a prior round. The inputs to th

docs.oracle.com

애노테이션 프로세서? 이게 뭐하는건가?

솔직히 글만 보고 팍 이해가 안되서 역시 개발자들은 코드로 구현해 보면서 이해하는게 좋을꺼 같당.

 

우선 내가 만들 애노테이션 정의

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Magic {
}

그리고 이 애노테이션이 붙는 곳에 애노테이션 프로세서를 이용해서 코드 조작(?)

@AutoService(Processor.class) // 서비스 프로바이더 레지스트리 생성기(Maven 빌드시 자동 생성)
public class MagicMojaProcessor extends AbstractProcessor {
	
	@Override
	public Set<String> getSupportedAnnotationTypes() { // 어떤 애노테이션을을 처리할껀가?
		Set<String> set = new HashSet<String>();
		set.add(Magic.class.getName()); // 나는 Magic이라는 애노테이션들을 처리하겠다.
		return set;
		
		// Java 9 이상
		// return Set.of(Magic.class.getName());
	}
	
	@Override
	public SourceVersion getSupportedSourceVersion() {
		// Java 8 Support
		return getSupportedSourceVersion().RELEASE_8;
	}
	
	@Override
	public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
		Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Magic.class);
		for(Element element : elements) {
			if(element.getKind() != ElementKind.INTERFACE) { // 타입이 interface 인 친구에게만 @Magic 애노테이션을 붙일 수 있게 
				processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Magic annotation can't be used on " + element.getSimpleName());
			} else {
				processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Processing " + element.getSimpleName());
			}
			
			// JavaPoet 라이브러리 (소스코드 생성을 위한 라이브러리)
			// getter setter처럼 @Magic 애노테이션을 붙이면 아래 코드가 
			// Source 레벨에서 생성된다(RetentionPolicy.SOURCE) 
            	TypeElement typeElement = (TypeElement)element;
            	ClassName className = ClassName.get(typeElement); // 다양한 클래스 정보 참조가능
            
	        MethodSpec pullOut = MethodSpec.methodBuilder("pullOut") // pullOut 이라는  메소드 생성
	                .addModifiers(Modifier.PUBLIC) // public 타입
	                .returns(String.class)
	                .addStatement("return $S", "Rabbit!")
	                .build();
                    
	        TypeSpec magicMoja = TypeSpec.classBuilder("MagicMoja") // MajicMoja 라는 클래스 생성
	                .addModifiers(Modifier.PUBLIC)
	                .addSuperinterface(className)
	                .addMethod(pullOut)
	                .build();
                    
            // 이때까지 메모리상에서 내가 사용할 클래스 메소드 파일을 정의     
			// 이제 실제로 Source File을 쓰겠다.
            // 여기서 부터 Javapoet 사용
	        Filer filer = processingEnv.getFiler();
	        try {
	            JavaFile.builder(className.packageName(), magicMoja)
	                    .build()
	                    .writeTo(filer);
	        } catch (IOException e) {
	            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "FATAL ERROR: " + e);
	        }
		}
		
		return true; // 다른 프로세서들한테 더 이상 이 애노테이션을 처리하지 말라고 true을 반환
	}
}

사용한 라이브러리

 

AutoService

컴파일 시점에 애노테이션 프로세서를 사용하여

META-INF/services/javax.annotation.processor.Processor 파일 자동으로 생성해 줌.

<dependency>
	<groupId>com.google.auto.service</groupId>
	<artifactId>auto-service</artifactId>
	<version>1.0-rc6</version>
</dependency>

 

Javapoet(소스코드 생성기 간단히 가능)

<!-- https://mvnrepository.com/artifact/com.squareup/javapoet -->
<dependency>
    <groupId>com.squareup</groupId>
    <artifactId>javapoet</artifactId>
    <version>1.13.0</version>
</dependency>

 

애노테이션 프로세서 사용 예

  • 롬복

  • AutoService: java.util.ServiceLoader용 파일 생성 유틸리티

  • @Override

  • Dagger 2: 컴파일 타임 DI 제공

  • 안드로이드 라이브러리

    • ButterKinfe: @BindView (뷰 아이디와 애노테이션 붙인 필드 바인딩)

    • DeepLinkDispatch: 특정 URI 링크를 Activity로 연결할 때 사용

 

애노테이션 프로세서 장점

  • 런타임 비용이 제로

애노테이션 프로세서 단점

  • 기존 클래스 코드를 변경할 때는 약간의 hack이 필요하다.

 

'Spring' 카테고리의 다른 글

Spring 로깅(Logging)  (0) 2020.11.14
Java(Enum) → DB(Int)  (0) 2020.06.16
Spring+JPA REST API 성능 최적화  (0) 2020.06.13
@PathVariable 사용해보자(전달인자 처리)  (0) 2020.06.07
Redirect parameter ? How are you going to spend it?  (0) 2020.04.23
Comments