본문 바로가기

Script/Groovy

15. MOP - Compile Time

앞서 MOP 는 런타임과 컴파일 타임으로 구분되며 컴파일 타임 MOP 는 Groovy 어노테이션인 
 
AST Transform 을 가리킨다고 하였다.
 
이번 장에서는 AST Transform 을 만드는 것보다
 
기본적으로 제공되는 여러 어노테이션들을 살펴보도록 하자.
 
각 어노테이션의 상세 사용법은 아래 페이지를 참조하여 검색하자.
 
 
 
 
@ToString 
 
기본형으로 @ToString 어노테이션을 사용해도 되는데
 
이 경우는 아래와 같이 클래스명(입력데이터들) 형식으로 나온다.
 
import groovy.transform.ToString
 
@ToString
class Customer {
  String first, last
  int age
  Date since = new Date()
  Collection favItems
  private answer = 42
}
 
println new Customer(first:'Tom', last:'Jones', age:21, favItems:['Books', 'Games'])
 
Customer(Tom, Jones, 21, Fri Oct 18 09:03:09 KST 2019, [Books, Games])
 
 
그래서 속성으로 아래와 같이 컨트롤 할 수 있는데 관련된 속성들이 아주 많다.
 
기본적으로 아래와 같이 사용할 수 있다는 것만 숙지하고 필요할 때 찾아보도록 하자.
 
@ToString(includeNames = true, excludes = ["first","last"])
class Customer {
String first, last
int age
Date since = new Date()
Collection favItems
private answer = 42
}
 
println new Customer(first:'Tom', last:'Jones', age:21, favItems:['Books', 'Games'])
 
Customer(age:21, since:Fri Oct 18 09:06:07 KST 2019, favItems:[Books, Games])
 
실제 자바 클래스가 어떻게 나오는지 확인해 보면
 
아래와 같이 toString 메서드가 Generate 되어 있는 것을 확인 할 수 있다.
 
@ToString(
includeNames = true,
excludes = {"first", "last"}
)
public class Customer implements GroovyObject {
private String first;
private String last;
private int age;
private Date since;
private Collection favItems;
private Object answer;
 
...
@Generated
public String toString() {
  CallSite[] var1 = $getCallSiteArray();
  Object _result = var1[1].callConstructor(StringBuilder.class);
  Object $toStringFirst = Boolean.TRUE;
  var1[2].call(_result, "Customer(");
 
  if (DefaultTypeTransformation.booleanUnbox($toStringFirst)) {
    Boolean var4 = Boolean.FALSE;
    $toStringFirst = var4;
  } else {
    var1[3].call(_result, ", ");
  }
 
  var1[4].call(_result, "age:");
  var1[5].call(_result, var1[6].callStatic(InvokerHelper.class, var1[7].callCurrent(this)));
  if (DefaultTypeTransformation.booleanUnbox($toStringFirst)) {
    Boolean var5 = Boolean.FALSE;
    $toStringFirst = var5;
  } else {
    var1[8].call(_result, ", ");
  }
 
  var1[9].call(_result, "since:");
  var1[10].call(_result, var1[11].callStatic(InvokerHelper.class, var1[12].callCurrent(this)));
  if (DefaultTypeTransformation.booleanUnbox($toStringFirst)) {
    Boolean var6 = Boolean.FALSE;
  } else {
    var1[13].call(_result, ", ");
  }
 
  var1[14].call(_result, "favItems:");
  var1[15].call(_result, var1[16].callStatic(InvokerHelper.class, var1[17].callCurrent(this)));
  var1[18].call(_result, ")");
  return (String)ShortTypeHandling.castToString(var1[19].call(_result));
}
 
...
}
Customer(age:21, since:Fri Oct 18 09:06:07 KST 2019, favItems:[Books, Games])
 
 
 
@TupleConstructor
 
앞서  Groovy 의 특징으로 Constructor 를 정의하지 않고 key:value 형식으로 값을 넣으면 
 
기본생성자 + Groovy 의 getProperty 함수가 해당 key 를 찾아 value 를 할당해 준다고 하였다.
 
이는 편리하지만 기본적으로 멤버명에 해당하는 key 를 넣어주어야 하므로 사용상 불편하다.
 
그래서 Groovy 에서는 위 어노테이션을 제공하여 이런 불편한 점을 해소해준다.
 
import groovy.transform.TupleConstructor
 
@TupleConstructor
class Customer {
  String first, last
  int age
  Date since
  Collection favItems
}
def c1 = new Customer(first:'Tom', last:'Jones', age:21, since:new Date(), favItems:['Books', 'Games'])
def c2 = new Customer('Tom', 'Jones', 21, new Date(), ['Books', 'Games'])
def c3 = new Customer('Tom', 'Jones')
 
 
@TupleConstructor 도 마찬가지로 여러 속성으로 제어 가능하므로 필요시 api 문서를 참조하자.
 
 
 
@Canonical
 
Canonical 은 @EqualsAndHashCode, @ToString@TupleConstructor 를 조합한 어노테이션이다.
 
 
 
@Singleton
 
싱글톤 객체를 만들어주는 어노테이션이다.
 
Generate 된 코드를 보면 SingleTon 에 객체를 생성하고
 
사용하기 위한 getInstance 메서드와 private 생성자를 볼 수 있다.
 
@Singleton
class Customer {
  static final String first='Daeung'
}
def c1 = Customer.instance
 
public class Customer implements GroovyObject {
  private static final String first = "Daeung";
  public static final Customer instance;
 
  @Generated
  private Customer() {
  ...
  }
 
  @Generated
  public static Customer getInstance() {
    CallSite[] var0 = $getCallSiteArray();
    return instance;
  }
  ...
}
 
 
 
@Sortable
 
해당 어노테이션을 사용하면 Comparable 인터페이스를 상속받아
 
compareTo 메서드를 자동으로 구현해 주어 객체를 Sort 할 수 있게 해준다.
 
기본적으로 아래와 같이 사용되는데 따로 어노테이션 프로퍼티를 지정하지 않으면
 
클래스 프로퍼티 선언 순서 기준으로 sort 메서드를 작동시킨다.
 
includes 메서드를 사용하면 Sort 기준을 바꾸고 Sort 에 사용할 인자만 지정할 수 있다.
 
import groovy.transform.Canonical
import groovy.transform.Sortable
 
@Canonical
@Sortable(includes = ["last","first"])
class Person {
  String first
  String last
}
 
def p1 = new Person("Andy","Kim")
def p2 = new Person("Joe", "Lee")
def p3 = new Person("Dan","Vega")
 
 
def pList = [p1,p2,p3]
 
println pList.toSorted()
 
[Person(Andy, Kim), Person(Joe, Lee), Person(Dan, Vega)]
 
 
 
@Immutable
 
클래스 멤버를 초기화 된 이후 상수화 시켜 변경을 막는다. 
 
import groovy.transform.Canonical
import groovy.transform.Immutable
 
@Canonical
@Immutable
class Person {
String first
String last
}
 
def p1 = new Person("Andy","Kim")
p1.first = "Daeung"
 
Caught: groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: first for class: Person
groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: first for class: Person
    at Person.setProperty(Test.groovy)
    at Test.run(Test.groovy:13)
 
 
 
@TypeChecked
 
스크립팅 언어의 가장 불편한 점 중 하나는 컴파일 시점에서
 
해당 코드가 실행이 될 지 여부를 판단하기 힘들다는 점이다.
 
예를 들어 아래와 같은 상황이 있을 수 있다.
 
사용자가 멤버에 포함되어 있지 않은 변수를 포함한 함수를 만들어도
 
getFullName 이 호출되기 전에는 에러가 나지 않는다.
 
class Person {
  String first
  String last
 
  String getFullName() {
    "$first $Last"
  }
}
 
 
이를 해소하기 위해 @TypeChecked 어노테이션을 사용할 수 있다.
 
import groovy.transform.TypeChecked
 
@TypeChecked
class Person {
  String first
  String last
 
  String getFullName() {
    "$first $Last"
  }
}
 
Last 가 없다고 에러남
 
 
 
@CompileStatic
 
TypeChecked 와 비슷하지만 TypeChecking 이 필요하지 않은 메소드, 속성 등을 제외시킬수 있다.
 
import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
 
@CompileStatic
class Person {
  String first
  String last
 
  String getFullName() {
    "$first $last"
  }
 
  @CompileStatic(TypeCheckingMode.SKIP)
  def skipMethod(){
    "$first $Last"
  }
}
에러가 나지 않음
 
 
 
@Builder
 
이 어노테이션은 빌더 패턴을 내부적으로 만들어 준다.
 
Builder 패턴이란 입력할 정보가 많거나 순서가 있을 때 아래와 같이
 
필요한 순서대로 객체를 만드는 패턴으로 UI 화면을 구성할 때 종종 사용된다.
 
 
public class AddStudentMainLayoutFactory {
       
       private class AddStudentMainLayout extends VerticalLayout {
              ..
              public AddStudentMainLayout init() {
                     ..
                     return this;
              }
              
              public AddStudentMainLayout bind() {
                     ..
                     return this;
              }      
              
              public Component layout() {
                     ..
                     return this;
              }
       }
       
       public Component createComponent() {
              return new AddStudentMainLayout().init().bind().layout();
       }
}
 
Groovy 에서는 단순히 위 어노테이션을 선언하는 것으로 이와 비슷한 동작을 하게 한다.
 
import groovy.transform.builder.Builder
 
@Builder
class Person {
  String firstName
  String lastName
  int age
}
 
def person = Person
                 .builder()
                 .firstName("Robert")
                 .lastName("Lewandowski")
                 .age(21)
                 .build()
 
assert person.firstName == "Robert"
assert person.lastName == "Lewandowski"
assert person.age == 21
 
 
 
 
 
 
 
 
 

'Script > Groovy' 카테고리의 다른 글

17. Rest  (0) 2020.01.21
16. Builder  (0) 2020.01.21
14. MOP - Runtime  (0) 2020.01.21
13. OOP  (0) 2020.01.21
12. Exception  (0) 2020.01.21