G3 Summit 2016 - Taking Advantage of Groovy Annotations

4,264 views

Published on

Groovy is a great language with extremely powerful capabilities about compile time meta-programming. Do you know that provides more than 40 AST transformations out-of-the box just to make your life as a developer easier?

In this talk you will learn the most important transformations provided by Groovy. I'll use a lot of code examples to explain all the concepts.

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,264
On SlideShare
0
From Embeds
0
Number of Embeds
3,803
Actions
Shares
0
Downloads
11
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

G3 Summit 2016 - Taking Advantage of Groovy Annotations

  1. 1. G3 Summit Taking advantage of Groovy Annotations Iván López - @ilopmar
  2. 2. ➢ Iván López - @ilopmar ➢ Groovy & Grails developer Member of Grails team at OCI ➢ @MadridGUG coordinator http://www.madridgug.com ➢ Greach organizer (@greachconf) http://greachconf.com ➢ Speaker: SpringOne 2GX, GR8Conf, Codemotion, GeeCon, Spring IO, Greach, JavaCro, RigaDevDay,... About me...
  3. 3. “ The best code is not code at all
  4. 4. 1. A little bit of theory
  5. 5. AST and compilation ▷ Abstract Syntax Tree ▷ AST modified during compilation ▷ Hook into the compiler phases
  6. 6. AST Transformations ▷ Global ▷ Local
  7. 7. 2. Out-of-the-box ASTs
  8. 8. AST transformations categories ▷ Code generation ▷ Class design ▷ Logging improvements ▷ Declarative concurrency ▷ Cloning and externalizing ▷ Safe scripting ▷ Compiler directives ▷ Dependencies handling
  9. 9. Code generation
  10. 10. @ToString ▷ Human readable toString ▷ Effective Java by Joshua Bloch (item 10)
  11. 11. class User { String name Integer age } def u = new User(name: 'Iván', age: 36) println u // User@1d2a54b2
  12. 12. @groovy.transform.ToString class User { String name Integer age } def u = new User(name: 'Iván', age: 36) assert u.toString() == 'User(Iván, 36)' class User { String name Integer age } def u = new User(name: 'Iván', age: 36) println u // User@1d2a54b2
  13. 13. @groovy.transform.ToString class User { String name Integer age } def u = new User(name: 'Iván', age: 36) assert u.toString() == 'User(Iván, 36)' String toString() { def _result = new StringBuilder() _result.append('User(') _result.append(this.name) _result.append(', ') _result.append(this.age) _result.append(')') return _result.toString() } class User { String name Integer age } def u = new User(name: 'Iván', age: 36) println u // User@1d2a54b2
  14. 14. @ToString ▷ includeNames, excludes, includes, includeSuper, includeSuperProperties, includeFields, ignoreNulls, includePackage, cache
  15. 15. @ToString ▷ includeNames, excludes, includes, includeSuper, includeSuperProperties, includeFields, ignoreNulls, includePackage, cache @groovy.transform.ToString(includeNames = true, excludes = ['name']) class User { String name Integer age } def u = new User(name: 'Iván', age: 36) assert u.toString() == 'User(age:36)'
  16. 16. @EqualsAndHashCode ▷ Generate equals and hashCode implementations ▷ Effective Java items 8 & 9
  17. 17. @EqualsAndHashCode ▷ Generate equals and hashCode implementations ▷ Effective Java items 8 & 9 @groovy.transform.EqualsAndHashCode class User { String name Integer age } def u1 = new User(name: 'Iván', age: 36) def u2 = new User(name: 'Iván', age: 36) assert u1 == u2 assert u1.hashCode() == u2.hashCode()
  18. 18. int hashCode() { def _result = HashCodeHelper.initHash() _result = HashCodeHelper.updateHash(_result, this.name) _result = HashCodeHelper.updateHash(_result, this.age) return _result } boolean canEqual(Object other) { return other instanceof User } boolean equals(Object other) { if (other == null) { return false } if (this.is(other)) { return true } if (!(other instanceof User)) { return false } User otherTyped = ((other) as User) if (!(otherTyped.canEqual(this))) { return false } if (!(this.getName().is(otherTyped.getName()))) { if (this.getName().is(this) && !(otherTyped.getName().is(otherTyped)) || !(this.getName().is(this)) && otherTyped.getName().is(otherTyped)) return false } else { if (!(this.getName().is(this) && otherTyped.getName().is(otherTyped))) { if (!(this.getName() == otherTyped.getName())) { return false } } } } if (!(this.getAge().is(otherTyped.getAge()))) { if (this.getAge().is(this) && !(otherTyped.getAge().is(otherTyped)) || !(this.getAge().is(this)) && otherTyped.getAge().is(otherTyped)) { return false } else { if (!(this.getAge().is(this) && otherTyped.getAge().is(otherTyped))) { if (!(this.getAge() == otherTyped.getAge())) { return false } } } } return true }
  19. 19. @EqualsAndHashCode ▷ excludes, includes, callSuper, includeFields, cache, useCanEqual
  20. 20. @EqualsAndHashCode ▷ excludes, includes, callSuper, includeFields, cache, useCanEqual @groovy.transform.EqualsAndHashCode(includes = 'name') class User { String name Integer age } def u1 = new User(name: 'Iván', age: 36) def u2 = new User(name: 'Iván', age: 42) assert u1 == u2 assert u1.hashCode() == u2.hashCode()
  21. 21. @TupleConstructor ▷ Generate constructors
  22. 22. @TupleConstructor ▷ Generate constructors @groovy.transform.TupleConstructor class User { String name Integer age }
  23. 23. @TupleConstructor ▷ Generate constructors @groovy.transform.TupleConstructor class User { String name Integer age } // Default map constructor def u1 = new User(name: 'Iván', age: 36)
  24. 24. @TupleConstructor ▷ Generate constructors @groovy.transform.TupleConstructor class User { String name Integer age } // Default map constructor def u1 = new User(name: 'Iván', age: 36) // Generated tuple constructor def u2 = new User('Iván', 36) def u3 = new User('Iván')
  25. 25. @TupleConstructor ▷ Generate constructors @groovy.transform.TupleConstructor class User { String name Integer age } // Default map constructor def u1 = new User(name: 'Iván', age: 35) // Generated tuple constructor def u2 = new User('Iván', 36) def u3 = new User('Iván') User(String name = null, Integer age = null) { this.name = name this.age = age }
  26. 26. @TupleConstructor ▷ excludes, includes, includeFields, includeProperties, includeSuperFields, includeSuperProperties, callSuper, force
  27. 27. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor
  28. 28. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor @groovy.transform.Canonical class User { String name Integer age }
  29. 29. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor def u1 = new User(name: 'Iván', age: 36) assert u1.toString() == 'User(Iván, 36)' // @ToString @groovy.transform.Canonical class User { String name Integer age }
  30. 30. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor def u2 = new User('Iván', 36) // @TupleConstructor assert u2.toString() == 'User(Iván, 36)' def u1 = new User(name: 'Iván', age: 36) assert u1.toString() == 'User(Iván, 36)' // @ToString @groovy.transform.Canonical class User { String name Integer age }
  31. 31. @Canonical ▷ @ToString + @EqualsAndHashCode + @TupleConstructor assert u1 == u2 // @EqualsAndHashCode assert u1.hashCode() == u2.hashCode() // @EqualsAndHashCode def u2 = new User('Iván', 36) // @TupleConstructor assert u2.toString() == 'User(Iván, 36)' def u1 = new User(name: 'Iván', age: 36) assert u1.toString() == 'User(Iván, 36)' // @ToString @groovy.transform.Canonical class User { String name Integer age }
  32. 32. @InheritConstructors ▷ Reduce boilerplate code when parent classes have multiple constructors ▷ Useful when overriding exception classes
  33. 33. @groovy.transform.InheritConstructors class MyException extends Exception { }
  34. 34. protected MyException(String param0, Throwable param1, boolean param2, boolean param3) { super(param0, param1, param2, param3) } public MyException(Throwable param0) { super(param0) } public MyException(String param0, Throwable param1) { super(param0, param1) } public MyException(String param0) { super(param0) } public MyException() { super() } @groovy.transform.InheritConstructors class MyException extends Exception { }
  35. 35. @Lazy ▷ Lazy initialization of fields ▷ Useful when creating expensive resources ▷ Effective Java item 71
  36. 36. class SomeBean { @Lazy LinkedList myField }
  37. 37. class SomeBean { @Lazy LinkedList myField } public LinkedList getMyField() { if ($myField != null) { $myField } else { $myField = new LinkedList() } }
  38. 38. @Sortable ▷ Comparable interface ▷ compareTo method natural order ▷ N methods returning comparators ▷ Effective Java item 12
  39. 39. @groovy.transform.Sortable class User { String name Integer age Integer born }
  40. 40. public int compareTo(User other) { if (this.is(other)) return 0 Integer value = 0 value = this.name <=> other.name if (value != 0) return value value = this.age <=> other.age if (value != 0) return value value = this.born <=> other.born if (value != 0) return value return 0 } private static class User$NameComparator extends AbstractComparator<User> { public int compare(User arg0, User arg1) { if (arg0 == arg1) return 0 if (arg0 != null && arg1 == null) return -1 if (arg0 == null && arg1 != null) return 1 return arg0.name <=> arg1.name } } private static class User$AgeComparator extends AbstractComparator<User> { ... } @groovy.transform.Sortable class User { String name Integer age Integer born }
  41. 41. @groovy.transform.Sortable class User { String name Integer age Integer born }
  42. 42. def users = [ new User(name: 'Mary', age: 15, born: 2000), new User(name: 'Peter', age: 44, born: 1970), new User(name: 'John', age: 35, born: 1979), ] @groovy.transform.Sortable class User { String name Integer age Integer born }
  43. 43. assert users.sort(false, User.comparatorByName())*.name == ['John', 'Mary', 'Peter'] assert users.sort(false, User.comparatorByAge())*.born == [2000, 1979, 1970] def users = [ new User(name: 'Mary', age: 15, born: 2000), new User(name: 'Peter', age: 44, born: 1970), new User(name: 'John', age: 35, born: 1979), ] @groovy.transform.Sortable class User { String name Integer age Integer born }
  44. 44. @Sortable ▷ includes, excludes @groovy.transform.Sortable(excludes = 'age') class User { String name Integer age Integer born } def users = [ new User(name: 'Mary', age: 15, born: 2000), new User(name: 'Peter', age: 44, born: 1970), new User(name: 'John', age: 35, born: 1979), ] assert users.sort(false, User.comparatorByName())*.name == ['John', 'Mary', 'Peter'] assert users.sort(false, User.comparatorByAge())*.born == [2000, 1979, 1970]
  45. 45. @Builder ▷ Create fluent API calls ▷ Multiple building strategies ▷ Multiple configuration options: builder name, prefix, excludes, includes,... ▷ Effective Java item 2
  46. 46. @groovy.transform.builder.Builder class User { String name Integer age Integer born }
  47. 47. @groovy.transform.builder.Builder class User { String name Integer age Integer born } def u = User.builder() .name('Iván') .age(36) .born(1979) .build() assert u.name == 'Iván' assert u.age == 36 assert u.born == 1979
  48. 48. public static class User$UserBuilder extends Object { private String name private Integer age private Integer born public User$UserBuilder() { } public User$UserBuilder name(String name) { this.name = name return this } public User$UserBuilder age(Integer age) { this.age = age return this } public User$UserBuilder born(Integer born) { this.born = born return this } public User build() { User _theUser = new User() _theUser.name = name _theUser.age = age _theUser.born = born return _theUser } } @groovy.transform.builder.Builder class User { String name Integer age Integer born } def u = User.builder() .name('Iván') .age(36) .born(1979) .build() assert u.name == 'Iván' assert u.age == 36 assert u.born == 1979
  49. 49. Class design
  50. 50. @Delegate ▷ Implements delegation design pattern ▷ Delegate calls on object to method on delegated properties ▷ All public methods are delegated
  51. 51. import java.time.LocalDate class Conference { @groovy.lang.Delegate LocalDate when String name }
  52. 52. import java.time.LocalDate class Conference { @groovy.lang.Delegate LocalDate when String name } def greach = new Conference(name: 'Greach', when: LocalDate.of(2017, 03, 31)) def gr8conf = new Conference(name: 'GR8Conf' when: LocalDate.of(2017, 05, 31))
  53. 53. def greach = new Conference(name: 'Greach', when: LocalDate.of(2017, 03, 31)) def gr8conf = new Conference(name: 'GR8Conf' when: LocalDate.of(2017, 05, 31)) assert greach.isBefore(gr8conf) import java.time.LocalDate class Conference { @groovy.lang.Delegate LocalDate when String name }
  54. 54. class Conference { ... public boolean isAfter(ChronoLocalDate param0) { when.isAfter(param0) } public boolean isBefore(ChronoLocalDate param0) { when.isBefore(param0) } ... } def greach = new Conference(name: 'Greach', when: LocalDate.of(2017, 03, 31)) def gr8conf = new Conference(name: 'GR8Conf' when: LocalDate.of(2017, 05, 31)) assert greach.isBefore(gr8conf) import java.time.LocalDate class Conference { @groovy.lang.Delegate LocalDate when String name }
  55. 55. @Immutable ▷ Create immutable classes ▷ Effective Java item 15 ▷ Rules for immutability
  56. 56. @groovy.transform.Immutable class User { String name Integer age } def u = new User(name: 'Iván', age: 36)
  57. 57. // This does not compile // You are not allowed to overwrite // the final class 'User'. class Admin extends User { } @groovy.transform.Immutable class User { String name Integer age } def u = new User(name: 'Iván', age: 36)
  58. 58. @groovy.transform.Immutable class User { String name Integer age } def u = new User(name: 'Iván', age: 36) try { u.name = 'John' } catch (ReadOnlyPropertyException e) { println e } // This does not compile // You are not allowed to overwrite // the final class 'User'. class Admin extends User { }
  59. 59. @Memoized ▷ Cache the result of a method
  60. 60. @Memoized ▷ Cache the result of a method @groovy.transform.Memoized Long fibonacci(Integer n) { if (n < 2) return 1 else return fibonacci(n-1) + fibonacci(n-2) } fibonacci(300)
  61. 61. @Memoized ▷ Cache the result of a method @groovy.transform.Memoized Long fibonacci(Integer n) { if (n < 2) return 1 else return fibonacci(n-1) + fibonacci(n-2) } fibonacci(300) @groovy.transform.Memoized User getUserInfo(Long userId) { // Expensive repetitive // network operation }
  62. 62. Logging improvements
  63. 63. @Log, @Log4j, @Log4j2, @Slf4j ▷ Static final field for the logger
  64. 64. @Log, @Log4j, @Log4j2, @Slf4j @groovy.util.logging.Log4j class MyClass { void method() { log.debug "My debug message" } } ▷ Static final field for the logger
  65. 65. @Log, @Log4j, @Log4j2, @Slf4j @groovy.util.logging.Log4j class MyClass { void method() { log.debug "My debug message" } } ▷ Static final field for the logger class MyClass { private static final Logger log = Logger.getLogger(Saludador.name) void method() { if (log.isLoggable(Level.DEBUG)) { log.debug "My debug message" } } }
  66. 66. Declarative concurrency
  67. 67. Declarative concurrency ▷ @Synchronized ▷ @WithReadLock ▷ @WithWriteLock
  68. 68. Cloning and externalizing
  69. 69. Cloning and externalizing ▷ @AutoClone ▷ @AutoExternalize
  70. 70. Safe scripting
  71. 71. Safe scripting ▷ @ThreadInterrupt ▷ @TimedInterrupt ▷ @ConditionalInterrupt
  72. 72. Compiler directives
  73. 73. Compiler directives ▷ @TypeChecked ▷ @CompileStatic ▷ @CompileDynamic
  74. 74. Dependencies handling
  75. 75. @Grab ▷ Grape dependency manager
  76. 76. @Grab ▷ Grape dependency manager @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate // or @Grab('org.springframework:spring-orm:3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate
  77. 77. @GrabResolver ▷ Grape dependency manager @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate // or @Grab('org.springframework:spring-orm:3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate @GrabResolver(name='restlet', root='http://maven.restlet.org/') @Grab(group='org.restlet', module='org.restlet', version='1.1.6')
  78. 78. @GrabExclude ▷ Grape dependency manager @Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate // or @Grab('org.springframework:spring-orm:3.2.5.RELEASE') import org.springframework.jdbc.core.JdbcTemplate @GrabResolver(name='restlet', root='http://maven.restlet.org/') @Grab(group='org.restlet', module='org.restlet', version='1.1.6') @Grab('net.sourceforge.htmlunit:htmlunit:2.8') @GrabExclude('xml-apis:xml-apis')
  79. 79. Bonus
  80. 80. @Pokemon class Foo { void doStuff() { try { // Some code that can throws // different exceptions } catch (e) { // Gotta catch’em all } } }
  81. 81. @Pokemon class Foo { void doStuff() { try { // Some code that can throws // different exceptions } catch (e) { // Gotta catch’em all } } } class Foo { @com.github.danveloper.ast.Pokemon void doStuff() { // code } }
  82. 82. 3. Demo
  83. 83. 4. Summary
  84. 84. “ The best code is not code at all
  85. 85. Thanks! Any questions? @ilopmar lopez.ivan@gmail.com https://github.com/ilopmar Iván López http://bit.ly/g3summit-groovy

×