Introdução ao Projeto Lombok
Lombok é uma das ferramentas que eu literalmente sempre coloco em meus projetos de builds primeiro. Eu não conseguia me imaginar programando Java sem ele hoje em dia. Eu realmente espero que você encontre seu poder lendo este artigo!
1. Evite código repetitivo
Java é uma ótima linguagem, mas às vezes fica muito detalhada para as coisas que você precisa fazer no seu código para tarefas comuns ou conformidade com algumas práticas de estrutura. Muitas vezes, elas não trazem valor real para o lado comercial dos seus programas - e é aqui que a Lombok está aqui para tornar sua vida mais feliz e mais produtiva.
A maneira como funciona é conectando-se ao seu processo de construção e gerando automaticamente o bytecode Java em seus arquivos.class de acordo com uma série de anotações de projeto que você introduz em seu código.
Leitura adicional:
Construtor Lombok com valor padrão
Aprenda a criar valores de propriedade padrão do construtor usando Lombok
Configurando o Lombok com Eclipse e Intellij
Aprenda a configurar o Lombok com IDEs populares
A inclusão em suas compilações, independentemente do sistema que você estiver usando, é muito direta. Seuproject page tem instruções detalhadas sobre os detalhes. A maioria dos meus projetos são baseados em maven, então eu normalmente deixo sua dependência no escopoprovided e estou pronto para prosseguir:
...
org.projectlombok
lombok
1.18.4
provided
...
Verifique a versão mais recente disponívelhere.
Observe que depender do Lombok não fará com que os usuários do seu.jars dependam dele também, pois é uma dependência de construção pura, não de tempo de execução.
2. Getters/Setters, Constructors – So Repetitive
Encapsular propriedades de objetos por meio de métodos públicos getter e setter é uma prática comum no mundo Java, e muitas estruturas contam com esse padrão de "Java Bean" extensivamente: uma classe com um construtor vazio e métodos get / set para "propriedades".
Isso é tão comum que a maioria dos IDEs suporta código de geração automática para esses padrões (e mais). Esse código, no entanto, precisa estar em suas fontes e também deve ser mantido quando, digamos, uma nova propriedade é adicionada ou um campo renomeado.
Vamos considerar esta classe que queremos usar como uma entidade JPA como exemplo:
@Entity
public class User implements Serializable {
private @Id Long id; // will be set when persisting
private String firstName;
private String lastName;
private int age;
public User() {
}
public User(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// getters and setters: ~30 extra lines of code
}
Esta é uma classe bastante simples, mas ainda considere se adicionarmos o código extra para getters e setters, acabaríamos com uma definição onde teríamos mais código de valor zero clichê do que as informações de negócios relevantes: "um usuário tem primeiro e sobrenomes e idade. ”
Vamos agoraLombok-ize esta classe:
@Entity
@Getter @Setter @NoArgsConstructor // <--- THIS is it
public class User implements Serializable {
private @Id Long id; // will be set when persisting
private String firstName;
private String lastName;
private int age;
public User(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
}
Adicionando as anotações@Getter e@Setter, dissemos ao Lombok para, bem, gerá-las para todos os campos da classe. @NoArgsConstructor levará a uma geração de construtor vazio.
Observe que este é o código de classewhole, não estou omitindo nada em oposição à versão acima com o comentário// getters and setters. Para uma classe de três atributos relevantes, é uma economia significativa no código!
Se você adicionar atributos (propriedades) à sua classeUser, o mesmo acontecerá: você aplicou as anotações ao próprio tipo para que eles cuidem de todos os campos por padrão.
E se você quisesse refinar a visibilidade de algumas propriedades? Por exemplo, gosto de manter os modificadores de campoid das minhas entidadespackage ouprotected visíveis porque se espera que sejam lidos, mas não explicitamente definidos pelo código do aplicativo. Basta usar um@Setter mais granulado para este campo específico:
private @Id @Setter(AccessLevel.PROTECTED) Long id;
3. Lazy Getter
Muitas vezes, os aplicativos precisam executar algumas operações caras e salvar os resultados para uso posterior.
Por exemplo, digamos que precisamos ler dados estáticos de um arquivo ou banco de dados. Geralmente, é uma boa prática recuperar esses dados uma vez e, em seguida, armazená-los em cache para permitir leituras na memória dentro do aplicativo. Isso evita que o aplicativo repita a operação cara.
Outro padrão comum éretrieve this data only when it’s first needed. Em outras palavras,only get the data when the corresponding getter is called the first time. This is called lazy-loading.
Suponha que esses dados sejam armazenados em cache como um campo dentro de uma classe. A classe agora deve garantir que qualquer acesso a esse campo retorne os dados em cache. Uma maneira possível de implementar essa classe é fazer com que o método getter recupere os dados apenas se o campo fornull. Por esse motivo,we call this a lazy getter.
O Lombok torna isso possível comlazy parameter in the @Getter annotation que vimos acima.
Por exemplo, considere esta classe simples:
public class GetterLazy {
@Getter(lazy = true)
private final Map transactions = readTxnsFromFile();
private Map readTxnsFromFile() {
final Map cache = new HashMap<>();
List txnRows = readTxnListFromFile();
txnRows.forEach(s -> {
String[] txnIdValueTuple = s.split(DELIMETER);
cache.put(txnIdValueTuple[0], Long.parseLong(txnIdValueTuple[1]));
});
return cache;
}
}
Isso lê algumas transações de um arquivo emMap. Como os dados no arquivo não mudam, vamos armazená-los em cache uma vez e permitir o acesso por meio de um getter.
Se agora olharmos para o código compilado desta classe, veremos umgetter method which updates the cache if it was null and then returns the cached data:
public class GetterLazy {
private final AtomicReference
É interessante salientar queLombok wrapped the data field in an *https://www.example.com/java-atomic-variables[AtomicReference].* Isso garante atualizações atômicas para o campotransaction . O métodogetTransactions() também garante a leitura do arquivo setransactions isnull.
O uso do campoAtomicReference transactions diretamente de dentro da classe é desencorajado. It’s recommended to use the getTransactions() method for accessing the field.
Por esse motivo, se usarmos outra anotação do Lombok comoToString na mesma classe,, ela usarágetTransactions() em vez de acessar diretamente o campo.
4. Classes de valor / DTO’s
Existem muitas situações nas quais queremos definir um tipo de dados com o único objetivo de representar "valores" complexos ou como "Objetos de Transferência de Dados", na maioria das vezes na forma de estruturas de dados imutáveis que construímos uma vez e nunca queremos mudar .
Nós projetamos uma classe para representar uma operação de login bem-sucedida. Queremos que todos os campos sejam não nulos e que os objetos sejam imutáveis, para que possamos acessar com segurança suas propriedades:
public class LoginResult {
private final Instant loginTs;
private final String authToken;
private final Duration tokenValidity;
private final URL tokenRefreshUrl;
// constructor taking every field and checking nulls
// read-only accessor, not necessarily as get*() form
}
Novamente, a quantidade de código que teríamos que escrever para as seções comentadas seria de um volume muito maior do que as informações que queremos encapsular e que tem valor real para nós. Podemos usar o Lombok novamente para melhorar isso:
@RequiredArgsConstructor
@Accessors(fluent = true) @Getter
public class LoginResult {
private final @NonNull Instant loginTs;
private final @NonNull String authToken;
private final @NonNull Duration tokenValidity;
private final @NonNull URL tokenRefreshUrl;
}
Basta adicionar a anotação@RequiredArgsConstructor e você obterá um construtor para todos os campos finais da classe, assim como você os declarou. Adicionar@NonNull aos atributos faz nosso construtor verificar se há nulidade e lançarNullPointerExceptions de acordo. Isso também aconteceria se os campos não fossem finais e adicionássemos@Setter para eles.
Você não quer o antigo formulárioget*() chato para suas propriedades? Como adicionamos@Accessors(fluent=true) neste exemplo, “getters” teria o mesmo nome de método das propriedades:getAuthToken() simplesmente se tornaauthToken().
Este formulário "fluente" se aplica a campos não finais para configuradores de atributos e também permite chamadas em cadeia:
// Imagine fields were no longer final now
return new LoginResult()
.loginTs(Instant.now())
.authToken("asdasd")
. // and so on
5. Core Java Boilerplate
Outra situação em que acabamos escrevendo o código que precisamos manter é ao gerar os métodostoString(),equals()ehashCode(). Os IDEs tentam ajudar com os modelos para gerá-los automaticamente em termos de nossos atributos de classe.
Podemos automatizar isso por meio de outras anotações em nível de classe do Lombok:
-
@ToString: irá gerar um métodotoString() incluindo todos os atributos de classe. Não é necessário escrever um e mantê-lo, pois enriquecemos nosso modelo de dados.
-
@EqualsAndHashCode: irá gerar os métodosequals() ehashCode() por padrão, considerando todos os campos relevantes e de acordo comvery well though semantics.
Esses geradores fornecem opções de configuração muito úteis. Por exemplo, se suas classes anotadas fazem parte de uma hierarquia, você pode apenas usar o parâmetrocallSuper=true e os resultados do pai serão considerados ao gerar o código do método.
Mais sobre isso: digamos que nosso exemplo de entidadeUser JPA incluísse uma referência a eventos associados a este usuário:
@OneToMany(mappedBy = "user")
private List events;
Não gostaríamos de ter toda a lista de eventos despejada sempre que chamarmos o métodotoString() do nosso usuário, apenas porque usamos a anotação@ToString. Sem problema: basta parametrizá-lo assim:@ToString(exclude = \{“events”}), e isso não acontecerá. Isso também é útil para evitar referências circulares se, por exemplo,UserEvents tiver uma referência aUser.
Para o exemploLoginResult, podemos definir a igualdade e o cálculo do código hash apenas em termos do token em si e não dos outros atributos finais em nossa classe. Em seguida, basta escrever algo como@EqualsAndHashCode(of = \{“authToken”}).
6. O Padrão Builder
O seguinte poderia criar uma classe de configuração de amostra para um cliente da API REST:
public class ApiClientConfiguration {
private String host;
private int port;
private boolean useHttps;
private long connectTimeout;
private long readTimeout;
private String username;
private String password;
// Whatever other options you may thing.
// Empty constructor? All combinations?
// getters... and setters?
}
Poderíamos ter uma abordagem inicial baseada no uso do construtor vazio padrão da classe e no fornecimento de métodos setter para cada campo. No entanto, o ideal é que as configurações não sejam re -set uma vez que tenham sido construídas (instanciadas), tornando-as efetivamente imutáveis. Portanto, queremos evitar setters, mas escrever um construtor de args potencialmente longo é um anti-padrão.
Em vez disso, podemos dizer à ferramenta para gerar um padrãobuilder, evitando que escrevamos uma classeBuilder extra e métodos semelhantes a setter associados, simplesmente adicionando a anotação @Builder ao nossoApiClientConfiguration .
@Builder
public class ApiClientConfiguration {
// ... everything else remains the same
}
Deixando a definição de classe acima como tal (sem declarar construtores nem setters +@Builder), podemos acabar usando-a como:
ApiClientConfiguration config =
ApiClientConfiguration.builder()
.host("api.server.com")
.port(443)
.useHttps(true)
.connectTimeout(15_000L)
.readTimeout(5_000L)
.username("myusername")
.password("secret")
.build();
7. Carga de exceções verificadas
Muitas APIs Java são projetadas para que possam lançar uma série de exceções verificadas, o código do cliente é forçado acatch ou declarado athrows. Quantas vezes você já transformou essas exceções que sabe que não aconteceriam em algo assim?
public String resourceAsString() {
try (InputStream is = this.getClass().getResourceAsStream("sure_in_my_jar.txt")) {
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException | UnsupportedCharsetException ex) {
// If this ever happens, then its a bug.
throw new RuntimeException(ex); <--- encapsulate into a Runtime ex.
}
}
Se você quiser evitar esses padrões de código porque o compilador não ficará feliz de outra forma (e, afinal, vocêknow os erros verificados não podem acontecer), use o apropriadamente chamado@SneakyThrows:
@SneakyThrows
public String resourceAsString() {
try (InputStream is = this.getClass().getResourceAsStream("sure_in_my_jar.txt")) {
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
return br.lines().collect(Collectors.joining("\n"));
}
}
8. Certifique-se de que seus recursos sejam liberados
Java 7 introduziu o bloco try-with-resources para garantir que seus recursos mantidos por instâncias de qualquer coisa que implementejava.lang.AutoCloseable sejam liberados ao sair.
O Lombok fornece uma maneira alternativa de conseguir isso e de forma mais flexível por meio de@Cleanup. Use-o para qualquer variável local cujos recursos você deseja garantir que sejam liberados. Não há necessidade de implementar qualquer interface em particular, você apenas obterá seu métodoclose() chamado.
@Cleanup InputStream is = this.getClass().getResourceAsStream("res.txt");
Seu método de liberação tem um nome diferente? Não tem problema, basta personalizar a anotação:
@Cleanup("dispose") JFrame mainFrame = new JFrame("Main Window");
9. Faça anotações em sua aula para obter um registrador
Muitos de nós adicionamos instruções de registro em nosso código com moderação, criando uma instância de aLogger de nossa estrutura de escolha. Digamos, SLF4J:
public class ApiClientConfiguration {
private static Logger LOG = LoggerFactory.getLogger(ApiClientConfiguration.class);
// LOG.debug(), LOG.info(), ...
}
Esse é um padrão tão comum que os desenvolvedores do Lombok se preocuparam em simplificá-lo para nós:
@Slf4j // or: @Log @CommonsLog @Log4j @Log4j2 @XSlf4j
public class ApiClientConfiguration {
// log.debug(), log.info(), ...
}
Muitoslogging frameworks são suportados e, claro, você pode personalizar o nome da instância, tópico, etc.
10. Escreva Métodos Thread-Safer
Em Java, você pode usar a palavra-chavesynchronized para implementar seções críticas. No entanto, essa não é uma abordagem 100{}egura: outro código de cliente também pode ser sincronizado em sua instância, levando a conflitos inesperados.
É aqui que entra@Synchronized: anote seus métodos (instância e estáticos) com ele e você obterá um campo privado não exposto gerado automaticamente que sua implementação usará para bloqueio:
@Synchronized
public /* better than: synchronized */ void putValueInCache(String key, Object value) {
// whatever here will be thread-safe code
}
11. Automatizar composição de objetos
Java não possui construções no nível da linguagem para suavizar uma abordagem de "herança da composição de favor". Outras linguagens têm conceitos integrados, comoTraits ouMixins para conseguir isso.
O@Delegate do Lombok é muito útil quando você deseja usar esse padrão de programação. Vamos considerar um exemplo:
-
Queremos queUsers eCustomers compartilhem alguns atributos comuns para nomenclatura e número de telefone
-
Definimos uma interface e uma classe de adaptador para esses campos
-
Teremos nossos modelos implementando a interface e@Delegate em seu adaptador, efetivamentecomposing eles com nossas informações de contato
Primeiro, vamos definir uma interface:
public interface HasContactInformation {
String getFirstName();
void setFirstName(String firstName);
String getFullName();
String getLastName();
void setLastName(String lastName);
String getPhoneNr();
void setPhoneNr(String phoneNr);
}
E agora um adaptador como uma classesupport:
@Data
public class ContactInformationSupport implements HasContactInformation {
private String firstName;
private String lastName;
private String phoneNr;
@Override
public String getFullName() {
return getFirstName() + " " + getLastName();
}
}
A parte interessante vem agora, veja como é fácil compor informações de contato nas duas classes de modelo:
public class User implements HasContactInformation {
// Whichever other User-specific attributes
@Delegate(types = {HasContactInformation.class})
private final ContactInformationSupport contactInformation =
new ContactInformationSupport();
// User itself will implement all contact information by delegation
}
O caso paraCustomer seria tão semelhante que omitiríamos a amostra por brevidade.
12. Revirando o Lombok?
Resposta curta: Não mesmo.
Você pode estar preocupado com a possibilidade de usar o Lombok em um de seus projetos, mas depois deseja reverter essa decisão. Você então teria um grande número de classes anotadas para ele ... o que você poderia fazer?
Eu realmente nunca me arrependi disso, mas quem sabe por você, sua equipe ou sua organização. Para esses casos, você está coberto graças à ferramentadelombok do mesmo projeto.
Pordelombok-ing seu código, você obteria o código-fonte Java gerado automaticamente com exatamente os mesmos recursos do bytecode criado pelo Lombok. Então, você pode simplesmente substituir seu código anotado original por esses novos arquivosdelomboked e não depender mais deles.
Isso é algo que você podeintegrate in your builde eu fiz isso no passado apenas para estudar o código gerado ou para integrar o Lombok com alguma outra ferramenta baseada em código-fonte Java.
13. Conclusão
Existem alguns outros recursos que não apresentamos neste artigo, recomendo que você se aprofunde emfeature overview para obter mais detalhes e casos de uso.
Além disso, a maioria das funções que mostramos tem uma série de opções de personalização que você pode achar úteis para fazer a ferramenta gerar coisas mais compatíveis com as práticas de sua equipe para nomenclatura, etc. Oconfiguration system integrado disponível também pode ajudá-lo com isso.
Espero que você tenha encontrado a motivação para dar ao Lombok a chance de entrar no seu conjunto de ferramentas de desenvolvimento Java. Experimente e aumente sua produtividade!
O código de exemplo pode ser encontrado emGitHub project.