Шпаргалки Java программиста 10: Lombok

  • Tutorial
image

Lombok — проект по добавлению дополнительной функциональности в Java c помощью изменения исходного кода перед Java компиляцией.

По сути, проект Lombok позволяет избавиться от многословности Java в большинстве случаев и перестать писать огромные простыни кода из гетеров, сеттеров, equals, hashcode и toString (да их обычно генерит IDE, но читать и менять все равно приходится программисту), в результате Java становиться почти такой же краткой как Kotlin, Scala или C#.

Что особенно радует, Lombok очень прост и легок в добавлении к вашему проекту. Если вам, как и мне, нравится принцип KISS, то советую посмотреть на Lombok.

Так же рекомендую, посмотреть на другие статьи цикла, например последную шпаргалку по Java SE8

Добавление в проекты очень простое, достаточно добавить обычные зависимости:

Подробнее...
Maven:

       <dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.16.18</version>
	</dependency>

Так же можно (но не обязательно добавить плагин для работы статических компиляторов, если нужно анализировать код после компиляции Lombok), см. тут

Gradle

plugins {
    id 'net.ltgt.apt' version '0.10'
}

dependencies {
	compileOnly 'org.projectlombok:lombok:1.16.18'
	
	apt "org.projectlombok:lombok:1.16.18"
}

Так же есть плагины для Idea, Eclipse и т.п. Если вы собираете Gradle или Maven, то собираться будет и без этих плагинов, но будут Idea/Eclipse возможно будут показывать ошибки при анализе кода.

Команды и аннотации:
Таблица
Название Описание Пример Lombok
Пример обычной Java
@NonNull обработка переменных,
которые не должны получать null
public Example(@NonNull P p) {
 super("Hello");
 this.name = p.getName();
}

public Example(@NonNull P p) {
  super("Hello");
  if (p == null) {
    throw new NullPointerException("p");
  }
  this.name = p.getName();
}

@Getter /
@Setter
легкое создание getter’ов и
setter’ов
@Getter 
@Setter 
private int age = 10;

private int age = 10;

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

@ToString определение аннотации перед классом,
для реализации стандартного  toString метода
@ToString(exclude="f")
public class Example

public class Example {
  @Override 
  public String toString() {
    return ...;
  }

@EqualsAndHashCode легкое создание методов Equals и HashCode
@EqualsAndHashCode(
   exclude={"id1", "id2"})
public class Example {

public class Example {
  ...
  @Override 
  public boolean equals(Object o) {
     ...
  }
  
  @Override 
  public int hashCode() {
     ...
  }

@NoArgsConstructor,
@RequiredArgsConstructor,
@AllArgsConstructor
создания пустого конструктора,
конструктора включающего все final поля,
либо конструктора включающего все возможные поля
@RequiredArgsConstructor(
     staticName = "of"
)
@AllArgsConstructor(
    access = AccessLevel.PROTECTED
)
public class E<T> {

public class E<T> {
  
 private E(T description) {
     ...
 }
  
 public static <T>E<T> of(
     T description
) {
   return new E<T>(description);
 } 

@Data генерация всех служебных методов,
заменяет сразу команды @ToString, @EqualsAndHashCode,
Getter, Setter, @RequiredArgsConstructor
@Data
public class Example { 
  private final String name;
  private int age;
}

Много кода
public class Example {
  private final String name;
  private int age;
 
  public Example(
     String name
  ) {
    this.name = name;
  }
  
  public String getName() {
    return this.name;
  }
  
  void setAge(int age) {
    this.age = age;
  }
  
  public int getAge() {
    return this.age;
  }
  
  @Override
 public String toString() {
    return ...;
  }
  
  @Override 
  public boolean equals(
    Object o
  ) {
    ....
  }
  
  @Override 
  public int hashCode() {
    ...
  }


@Value создание неизменяемых классов,
аналог Data, но для неизменяемых классов
@Value
public class Example { 
  private final String name;
  private int age;
}

Много кода
public class Example {
  private final String name;
  private final int age;
 
  public Example(
    String name, int age
) {
    this.name = name;
    this.age = age;
  }
  
  public String getName() {
    return this.name;
  }
  
  public int getAge() {
    return this.age;
  }
  
  @Override
 public String toString() {
    return ...;
  }
  
  @Override 
  public boolean equals(
     Object o
  ) {
    ....
  }
  
  @Override 
  public int hashCode() {
    ...
  }


@Builder реализация паттерна bulder,
Singular – используется для объектов в
единственном экземпляре (добавления элемента
в коллекции и т.п.)  
@Builder
public class Example {
  private String name;
  private int age;
  @Singular 
  private Set<String> occupations;
}

много кода
public class Example {
  private String name;
  private int age;
  private Set<String> occupations;
  
  Example(
    String name, 
    int age, 
   Set<String> occupations
 ) {
    this.name = name;
    this.age = age;
    this.occupations = occupations;
  }
  
  public static ExampleBuilder builder() {
    return new ExampleBuilder();
  }
  
  public static class ExampleBuilder {
    private String name;
    private int age;
    private ArrayList<> occupations;
    
    ExampleBuilder() {
    }
    
    public ExampleBuilder name(
       String name
    ) {
      this.name = name;
      return this;
    }
    
    public ExampleBuilder age(
      int age
    ) {
      this.age = age;
      return this;
    }
    
    public ExampleBuilder occupation(
      String occupation
   ) {
      if (this.occupations == null) {
        this.occupations = 
          new ArrayList<String>();
      }
      
      this.occupations.add(occupation);
      return this;
    }
    
   ...
    public Example build() {
      Set<String> occupations = ...;
      return new Example(name, age, occupations);
    }
    
    @java.lang.Override
    public String toString() {
      ...
    }
  }
}


@SneakyThrows обертка проверяемых исключений
@SneakyThrows(
UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
return new String(bytes, «UTF-8»);
}
public String utf8ToString(byte[] bytes) {
    try {
      return new String(bytes, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw Lombok.sneakyThrow(e);
    }
 }

@Synchronized простое создание synchronized блоков
private final Object readLock = new Object();
  
@Synchronized
public static void hello() {
    ...;
 }
  
 @Synchronized
 public int answerToLife() {
   ...
 }
  
 @Synchronized("readLock")
 public void foo() {
    ...
 }

Много кода
private static final Object $LOCK = new Object[0];
private final Object $lock = new Object[0];
private final Object readLock = new Object();
  
public static void hello() {
  synchronized($LOCK) {
    ...
   }
}
  
public int answerToLife() {
  synchronized($lock) {
    ...
   }
}
  
public void foo() {
   synchronized(readLock) {
      ...
   }
}


@Log добавление инницилизации логирования,
так же позволяет выбрать вид логгера: @CommonsLog,
@JBossLog, Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j
@Slf4j
public class Example {
  public static void main(String... args) {
    log.error("error");
  }

public class Example {
  private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExampleOther.class);
  
  public static void main(String... args) {
    log.error("error");
  }

Val простое создание финальной
переменной с выводом типа,
то есть то самый val о котором
спорили
val map = new HashMap<Integer, String>();
for (val entry : map.entrySet()) {
      ...
}

final HashMap<Integer, String> map = new HashMap<Integer, String>();
...
for (final Map.Entry<Integer, String> entry : map.entrySet()) {
      ...
}

@Cleanup простое определение ресурсов,
так чтобы они автоматически закрывались
после окончания работы кода.
(не так актуально при использовании
try with resources )
@Cleanup InputStream in = new FileInputStream(args[0]);
@Cleanup OutputStream out = new FileOutputStream(args[1]);
...

InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        ...
      } finally {
        if (out != null) {
          out.close();
        }
      }
    } finally {
      if (in != null) {
        in.close();
      }
    }





Если таблица кажется плохо читаемой, просто по очереди
Название: @NonNull
Описание: обработка переменных,
которые не должны получать null
Код Lombok:
public Example(@NonNull P p) {
 super("Hello");
 this.name = p.getName();
}

Код обычной Java:
public Example(@NonNull P p) {
  super("Hello");
  if (p == null) {
    throw new NullPointerException("p");
  }
  this.name = p.getName();
}



Название: Getter /
Setter
Описание: легкое создание getter’ов и
setter’ов
Код Lombok:
@Getter 
@Setter 
private int age = 10;

Код обычной Java:
private int age = 10;

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



Название: @ToString
Описание: определение аннотации перед классом,
для реализации стандартного  toString метода
Код Lombok:
@ToString(exclude="f")
public class Example

Код обычной Java:
public class Example {
  @Override 
  public String toString() {
    return ...;
  }



Название: @EqualsAndHashCode
Описание: легкое создание методов Equals и HashCode
Код Lombok:
@EqualsAndHashCode(
   exclude={"id1", "id2"})
public class Example {

Код обычной Java:
public class Example {
  ...
  @Override 
  public boolean equals(Object o) {
     ...
  }
  
  @Override 
  public int hashCode() {
     ...
  }



Название:
@NoArgsConstructor,
@RequiredArgsConstructor,
@AllArgsConstructor
Описание: создания пустого конструктора,
конструктора включающего все final поля,
либо конструктора включающего все возможные поля
Код Lombok:
@RequiredArgsConstructor(
     staticName = "of"
)
@AllArgsConstructor(
    access = AccessLevel.PROTECTED
)
public class E<T> {

Код обычной Java:
public class E<T> {
  
 private E(T description) {
     ...
 }
  
 public static <T>E<T> of(
     T description
) {
   return new E<T>(description);
 } 



Название: Data
Описание: генерация всех служебных методов,
заменяет сразу команды @ToString, @EqualsAndHashCode,
Getter, Setter, @RequiredArgsConstructor
Код Lombok:
@Data
public class Example { 
  private final String name;
  private int age;
}



public class Example {
  private final String name;
  private int age;
 
  public Example(
     String name
  ) {
    this.name = name;
  }
  
  public String getName() {
    return this.name;
  }
  
  void setAge(int age) {
    this.age = age;
  }
  
  public int getAge() {
    return this.age;
  }
  
  @Override
 public String toString() {
    return ...;
  }
  
  @Override 
  public boolean equals(
    Object o
  ) {
    ....
  }
  
  @Override 
  public int hashCode() {
    ...
  }




Название: Value
Описание: создание неизменяемых классов,
аналог Data, но для неизменяемых классов
Код Lombok:
@Value
public class Example { 
  private final String name;
  private int age;
}


Код обычной Java:
public class Example {
  private final String name;
  private final int age;
 
  public Example(
    String name, int age
) {
    this.name = name;
    this.age = age;
  }
  
  public String getName() {
    return this.name;
  }
  
  public int getAge() {
    return this.age;
  }
  
  @Override
 public String toString() {
    return ...;
  }
  
  @Override 
  public boolean equals(
     Object o
  ) {
    ....
  }
  
  @Override 
  public int hashCode() {
    ...
  }




Название: Builder
Описание: реализация паттерна bulder,
Singular – используется для объектов в
единственном экземпляре (добавления элемента
в коллекции и т.п.)  
Код Lombok:
@Builder
public class Example {
  private String name;
  private int age;
  @Singular 
  private Set<String> occupations;
}


Код обычной Java:
public class Example {
  private String name;
  private int age;
  private Set<String> occupations;
  
  Example(
    String name, 
    int age, 
   Set<String> occupations
 ) {
    this.name = name;
    this.age = age;
    this.occupations = occupations;
  }
  
  public static ExampleBuilder builder() {
    return new ExampleBuilder();
  }
  
  public static class ExampleBuilder {
    private String name;
    private int age;
    private ArrayList<> occupations;
    
    ExampleBuilder() {
    }
    
    public ExampleBuilder name(
       String name
    ) {
      this.name = name;
      return this;
    }
    
    public ExampleBuilder age(
      int age
    ) {
      this.age = age;
      return this;
    }
    
    public ExampleBuilder occupation(
      String occupation
   ) {
      if (this.occupations == null) {
        this.occupations = 
          new ArrayList<String>();
      }
      
      this.occupations.add(occupation);
      return this;
    }
    
   ...
    public Example build() {
      Set<String> occupations = ...;
      return new Example(name, age, occupations);
    }
    
    @java.lang.Override
    public String toString() {
      ...
    }
  }
}




Название: @SneakyThrows
Описание: обертка проверяемых исключений
Код Lombok:

@SneakyThrows(
UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
return new String(bytes, «UTF-8»);
}

Код Lombok:
public String utf8ToString(byte[] bytes) {
    try {
      return new String(bytes, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw Lombok.sneakyThrow(e);
    }
 }



Название: @Synchronized
Описание: простое создание synchronized блоков
Код Lombok:
private final Object readLock = new Object();
  
@Synchronized
public static void hello() {
    ...;
 }
  
 @Synchronized
 public int answerToLife() {
   ...
 }
  
 @Synchronized("readLock")
 public void foo() {
    ...
 }


Код обычной Java:
private static final Object $LOCK = new Object[0];
private final Object $lock = new Object[0];
private final Object readLock = new Object();
  
public static void hello() {
  synchronized($LOCK) {
    ...
   }
}
  
public int answerToLife() {
  synchronized($lock) {
    ...
   }
}
  
public void foo() {
   synchronized(readLock) {
      ...
   }
}




Название: Log
Описание: добавление инницилизации логирования,
так же позволяет выбрать вид логгера: @CommonsLog,
@JBossLog, Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j
Код Lombok:
@Slf4j
public class Example {
  public static void main(String... args) {
    log.error("error");
  }

Код обычной Java:
public class Example {
  private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExampleOther.class);
  
  public static void main(String... args) {
    log.error("error");
  }



Val простое создание финальной
переменной с выводом типа,
то есть то самый val о котором
спорили
val map = new HashMap<Integer, String>();
for (val entry : map.entrySet()) {
      ...
}

final HashMap<Integer, String> map = new HashMap<Integer, String>();
...
for (final Map.Entry<Integer, String> entry : map.entrySet()) {
      ...
}



Название: @Cleanup
Описание: простое определение ресурсов,
так чтобы они автоматически закрывались
после окончания работы кода.
(не так актуально при использовании
try with resources )
Код Lombok:
@Cleanup InputStream in = new FileInputStream(args[0]);
@Cleanup OutputStream out = new FileOutputStream(args[1]);
...

Код обычной Java:
InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        ...
      } finally {
        if (out != null) {
          out.close();
        }
      }
    } finally {
      if (in != null) {
        in.close();
      }
    }

Support the author
Share post

Similar posts

Comments 37

    +2
    Планируется ли обзор DI решений (Guice, Dagger, HK2)?
      +1
      Да, уже есть наполовину написанная статья по Guice, Dagger, Spring, с HK2 не сталкивался, посмотрю.
      +2
      Беда начинается, когда требуется в одном проекте использовать и Lombok и AspectJ, они не очень дружат.
        0
        А можете рассказать, что происходит? Я не сталкивался с такими проблемами, вроде AspectJ по идее работает уже с сгенеренными классами (или я ошибаюсь?), а сгенеренные классы Lombok не отличаются от обычных.
      +1
      Никогда не понимал людей кто юзает эту либу.
      Для нового продукта можно выбрать лаконичные scala или koltin.
      А в бородатый энтерпрайз вряд ли кто то станет это внедрять.
      Нужно принимать язык таким как каким он есть.
        –1
        Для нового продукта можно выбрать лаконичные scala или koltin.

        Можно, но если вся команда привыкла писать на Java почему бы и нет. У scala многих пугает сложность, понятно, что можно писать как чуть лучшая Java, но если работать с чужим кодом можно ничего не понять. koltin пока не так распространен. Данная библиотека это компромис между новым языком и старым.

        А в бородатый энтерпрайз вряд ли кто то станет это внедрять.

        Кроме энтерпрайза есть просто старые проекты, которые по каким-то причинам использовали Java (скажем, микросервисы). С одной стороны переходит на новый язык сложно, с другой добавление библиотеки позволяет рефакторить старый код. Вообще, в Java энтерпрайз все больше переходит на Spring Boot и микросервисы, а это могут быть совсем не огромные проекты.

        Нужно принимать язык таким как каким он есть.

        Не согласен, так можно было вообще писать на Java 5 или Java 3. Язык вполне гибкая штука, в Java огромное количество открытых библиотек, которые тоже расширяют возможности. По такой логики их тоже не стоит использовать.
          +3
          Не согласен, так можно было вообще писать на Java 5 или Java 3. Язык вполне гибкая штука, в Java огромное количество открытых библиотек, которые тоже расширяют возможности. По такой логики их тоже не стоит использовать.

          Вы передергиваете :) Речь ведь о языке в целом, а не конкретно какой-то её версии.
          Генерация кода для DI вынужденная мера, тут я согласен.
          В остальном, не западло прописать getter/setter потому что это Java, тут так принято.

          Хотите нормальную аргументацию?
          Для нормальной работы этого чудо модуля нужно ставить расширение для IntelliJ IDEA
          Все эти генерации могут усложнить/замедлить скорость сборки проекта
          Все эта магия вгонит в ступор первого же человека кто начнет разбираться в таком коде.

          Ненужно пытаться нагнать всевдо-читаемость такими плагинами.
          Если что, есть много других языков где ненужно писать getter/setter :)
            0
            Если что, есть много других языков где ненужно писать getter/setter :)

            Эм… Много? Что-то как-то в голову даже 5 не приходит.

              +1
              Ruby, Python, Swift, Scala, Kotlin
                0

                Ай, блин, _не_нужно. Прочитал не верно

              +1
              Все эта магия вгонит в ступор первого же человека кто начнет разбираться в таком коде.

              Спринговый @Compontent тоже вгонит, так что ж теперь, Спрингом не пользоваться?
                0
                Аннотация @Compontent в отличии от аннотаций Lombok не дописывает код вместо вас, вот в чем разница!
                  +1
                  Для человека, который этого не знает и та и другая аннотация выглядят магически и требуют времени для постижения того, что это такое и как работает, так что с этой точки зрения разница не велика. При этом, замечу, квикдок по аннотации Data первым делом сообщает: "Generates getters for all fields", плюс дает ссылку на сайт ломбока, что сильно помогает делу.
                    0
                    Я не знаю как по другому еще донести до вас суть. Вы меня не хотите слышать, эта ветка зашла в тупик.
                      +1
                      Суть вами сказанного, собственно, ясна. Просто есть иной взгляд на вещи, вот и все.
                        0
                        Для меня лично не приемлем тот факт что без специального плагина для IDE с этим самым Lombok невозможно нормально работать. Это главная причина того, почему я не буду использовать это в своих проектах.
                          +1
                          Со Спрингом, кстати, тоже невозможно нормально работать в комьюнити эдишн версии, но многие работают, и при этом говорят, что им норм :)
                            0

                            Почему для вас проблема поставить плагин?

                        0
                        парсер, что ты делаешь, прекрати
                0
                Ну мне лично не нравится синтаксис котлина и скали, а джава с си подобным синтаксисом очень даже ничего. В принципе сгенерировать через alt insert в идее эти геттеры сеттеры тоже не сложно, но ломбок вообще шикарен.
                  +1
                  Есть объект класса, нужно вернуть его часть в виде json — я создаю отдельную обертку, которая в конструктор принимает этот объект, и берет из него все нужное, после чего объект обертки прогоняется через jackson, и на выходе получаем json. Jackson, как известно, пользуется для конвертации либо публичными полями, либо геттерами, поэтому одна единственная аннотация Data над классом-оберткой быстро и не заметно экономит мне время. Еще одна аннотация @Slf4j позволяет мне работать с логгером даже не думая о нем. В общем не очень понимаю ваше не понимание, о каком «внедрении в бородатый энтерпрайз» вообще идет речь? Это же библиотека, а не фреймворк — если удобно, то используем, если не удобно, то не используем.
                    +1
                    Немного необычный подход с оберткой над объектом для генерации json.
                    Обычно делаются статические методы в хелпер-классе, например EntityHelper.toJson(myEntity)
                    В вашем случае идет совершенно ненужное создание +1 объекта обертки к каждой операции конвертации объекта в Json

                    Не буду спорить что ваш подход выглядит короче, круче :)
                      0
                      Ваш подход не подойдет в случае если нет однозначного соответствия Класс<->json. То есть в одном случае нужен json состоящий из некоторых полей класса А, в другом — из всех полей, а в третьем случае вообще должен быть композицией из A + B + C. Плюс мне нравится иметь возможность на вопрос «почему фронту приходит ЭТО вместо ЭТОГО?» пойти и подправить одну конкретную обертку гарантированно не затронув ничего по соседству. Я такие обертки вообще частенько создаю, как inner классы прямо по месту преобразования результата.
                        0
                        Нужно конечно видеть код, что бы подсказать альтернативный путь для решения этой задачи без дополнительных объектов-оберток.
                        Возможно что ваш подход хорошо подходит именно в вашем случае. Тут я спорить не буду.
                          0
                          А в чем проблема дополнительных оберток, кстати говоря? Формально понятно, лишняя сущность, но практически она маленькая, локальная и вопросов не вызывает. Стоит ли с ней бороться?
                            0
                            В вашем случае, на сколько я понял, это обертка возвращается вашим контроллером, по сути ничего, кроме эстетического момента меня не смущает.

                            Если же вы проектируете api какой-либо библиотеки, которую будут использовать сторонние разработчики, то тут я бы воздержался от таких конструкций. Разработчик может вызывать такой код в различных циклах миллионы раз, зачем провоцировать GC на лишнюю работу за зря?
                              0
                              Не совсем так. У меня выделена отдельная сущность «экшен», который с одной стороны может вызываться на выполнение фронтэндом, путем отправления по вебсокету специального сообщения, а с другой стороны в него интегрирован доступ к ядерным сервисам, которые обеспечивают работу самого приложения, и знать не знают ни о какой фронтовой части. То есть реализуя экшен (java код) на получение, к примеру, данных о работнике, которые на ядерном уровне распределены по нескольким компонентам, мы делам примерно так:
                              UserProfile userProfile = userService.getProfile(id);
                              EmployeeProfile employeeProfile = employeeService.getProfile(id);

                              после чего компонуем эти два профиля в одну entity, состоящую, скажем, из ФИО (взятого из userProfile) и количества рабочих часов в месяц (из employeeProfile), которую экшен и возвращает для дальнейшего превращения в json, и возврата фронтэнду. При таком подходе результирующий entity даже в виде inner класса в классе экшена смотрится довольно уместно.

                              А насчет библиотеки это да, тут согласен.
                  +2
                  Пользую ломбок давненько, в основном Getter/Setter/ToString/Equal…
                  По моему удобно и Entity сокращаются в размерах в разы.

                  Только следить за циклическими зависимостями в ManyToMany и прочих подобных штуках надо путем использования exclude ну и с Equal… осторожно, в остальном просто супер.
                    +1
                    Табличный формат совершенно не удобен, вы разве не видите?
                      0
                      Добавил альтернативный формат, спасибо
                      +1
                      Выглядит круто, только вот один вопрос очень интересует: если физически в коде например геттеров/сеттеров нет (добавятся аннотацией), а ты их будешь юзать, не будет ли IDE их подчеркивать все время как ошибку? Она то не знает что они добавятся во время компиляции… И вообще как с ними автокомплит будет работать?
                        +1
                        Для всех IDE есть специальные плагины, при которых все работает нормально.
                          +1
                          Idea при установленном lombok-plugin все показывает корректно
                          image
                        • UFO just landed and posted this here
                            +2
                            Для этой зависимости можно установить scope как provided, т.к. область действия функциональности ограничивается временем компиляции, что бы не тащить в билд эту либу.
                              0
                              Года 2-3 использую ломбок только для Dto и Entity (для остального скорей не нужно). Крутая вещь. Одной аннотацией избавляешься от некоторой кучи проблем, например, достаточно просто изменить название поля или тип и ничего не надо больше делать (перегенерить сеттеры, хэшкод и пр.). Проблем ни разу не было, Dto'шки довольно краткие и красивые, на перфоманс не влияет.
                              Единственный минус, как выше писали, с AspectJ не работает, думаю, до сих пор не подружили. Сейчас попался такой проект и прям сильно скучаю по ломбоку, приходится руками лишние движения производить.

                              Only users with full accounts can post comments. Log in, please.