Contexts and Dependency Injection (CDI) 2.0

Especificações Jakarta EE & Microprofile.io (3 Part Series)

1 Jakarta EE e Microprofile.io
2 Escolhendo uma implementação Jakarta EE & Microprofile.io
3 Contexts and Dependency Injection (CDI) 2.0

CDI é a especificação mais básica do Jakarta EE e do Microprofile.io. Está na base de todas as outras e é utilizada por quase todas outras.

O meu projeto tem CDI?

Pra quem não conhece a especificação, pode ficar perdido. Era o que acontecia comigo. Você se depara com classes assim:

<span>@ApplicationScoped</span>
<span>public</span> <span>class</span> <span>UserService</span> <span>{</span>
<span>@Inject</span>
<span>private</span> <span>UserRepository</span> <span>userRepository</span><span>;</span>
<span>public</span> <span>Optional</span><span><</span><span>User</span><span>></span> <span>findById</span><span>(</span><span>long</span> <span>userId</span><span>)</span> <span>{</span>
<span>return</span> <span>Optional</span><span>.</span><span>ofNullable</span><span>(</span><span>userRepository</span><span>.</span><span>find</span><span>(</span><span>userId</span><span>));</span>
<span>}</span>
<span>}</span>
<span>@ApplicationScoped</span>
<span>public</span> <span>class</span> <span>UserService</span> <span>{</span>
    <span>@Inject</span>
    <span>private</span> <span>UserRepository</span> <span>userRepository</span><span>;</span>

    <span>public</span> <span>Optional</span><span><</span><span>User</span><span>></span> <span>findById</span><span>(</span><span>long</span> <span>userId</span><span>)</span> <span>{</span>
        <span>return</span> <span>Optional</span><span>.</span><span>ofNullable</span><span>(</span><span>userRepository</span><span>.</span><span>find</span><span>(</span><span>userId</span><span>));</span>
    <span>}</span>
<span>}</span>
@ApplicationScoped public class UserService { @Inject private UserRepository userRepository; public Optional<User> findById(long userId) { return Optional.ofNullable(userRepository.find(userId)); } }

Enter fullscreen mode Exit fullscreen mode

E você se pergunta:

  • Tá, quem instancia essa classe?
  • Como eu configuro userRepository?
  • Como eu uso UserService?

Se você não sabe responder essas perguntas, vamos lá….

O que é CDI?

A primeira versão de Contexts and Dependency Injection for Java EE (CDI) entrou no Java EE 6, e rapidamente se tornou uma das mais importantes e populares especificações da plataforma.

CDI define um conjunto de especificações que permite uma boa modularização do código do projeto:

  • Um ciclo de vida bem definidos para objetos e seu respectivo contextos. Também um conjunto extensível de contextos.
  • Um sofisticado e mecanismo de injeção com tipagem forte sem configuração
  • Suporte a construção de Componentes totalmente desacoplados
  • Integração com JSF
  • Decorators
  • Interceptors
  • Programação Event-Driven

Escopo

O Java CDI define uma API básica para que frameworks de dependency injection possam criar, gerenciar e remover TypeSafe beans automaticamente dentro de containers Jakarta EE.

Porque usar CDI

CDI permite desacoplar totalmente o ciclo de vida de um objeto e seu uso. O desenvolvedor que usa uma classe não precisa saber como instanciar e nem como remover estes objetos. Permitindo assim:

  • Velocidade de Desenvolvimento
  • Padronização do código
  • Baixo acoplamento e alta coesão
  • Foco na Lógica de Negócio

Definições

Para entender o que é o Java CDI é preciso compreender algumas definições antes.

Inversão de Controle

Inversão de controle, segundo Martin Fowler, é o que difere Frameworks de Bibliotecas. Ao usar uma biblioteca, deve ser explicitamente carregado no código as dependências de cada componente. Ao se usar um componene, as dependências de cada componente são carregadas pelo mesmo, sendo apenas responsabilidade do desenvolvedor usar o compomenente.

Programação por Aspectos

Quando se fala de AOP, há inúmeros conceitos, mas podemos ser bem simples em definir. Ao se escrever um código, o desenvolvedor pode separar vários níveis de tipos de códigos. Aqueles que definem o que está sendo feito, requisitos funcionais e requisitos básicos.

Vamos imaginar que você está desenvolvendo um endpoint para tratar de reservar um item em um carrinho de compra. Existe o código de reservar o item (1), existe o código que implementará o protocolo HTTP (2) e existe outras funcionalidades que darão segurança ao serviço (3). 1 é o código principal, 2 e 3 são apenas aspectos desse código e podem ser definidos ortogonalmente ao código. Como fazer isso? Vamos ver a seguir: Decorators ou Interceptors!

Criando um Projeto CDI

Para criar um projeto CDI, basta adicionar o arquivo src/main/resources/META-INF/beans.xml a um projeto Maven.

<span><beans</span> <span>xmlns=</span><span>"http://xmlns.jcp.org/xml/ns/javaee"</span>
<span>xmlns:xsi=</span><span>"http://www.w3.org/2001/XMLSchema-instance"</span>
<span>xsi:schemaLocation=</span><span>"http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"</span>
<span>bean-discovery-mode=</span><span>"all"</span><span>></span>
<span></beans></span>
<span><beans</span> <span>xmlns=</span><span>"http://xmlns.jcp.org/xml/ns/javaee"</span> 
    <span>xmlns:xsi=</span><span>"http://www.w3.org/2001/XMLSchema-instance"</span> 
    <span>xsi:schemaLocation=</span><span>"http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"</span> 
    <span>bean-discovery-mode=</span><span>"all"</span><span>></span>
<span></beans></span>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="all"> </beans>

Enter fullscreen mode Exit fullscreen mode

Isso fará com que automaticamente TODOS os objetos definidos nesse projeto sejam considerados um bean e carregados aotumaticamente.

Instanciando Objetos CDI

Objetos CDI são automaticamente carregados. Para isso é preciso que sigam os seguintes requisitos:

  • Não seja uma Inner Class
  • Não seja uma Classe abstrata e não possui a Annotation @Decorator
  • Não implementa a interface javax.enterprise.inject.spi.Extension
  • Não possui a Annotation @Vetoed e nem está em um pacote com @Vetoed
  • Tem um construtor apropriado
    • Um construtor sem parâmetros
    • Um construtor com parâmetros com @Inject
      • Está na especificação, mas não funciona! 🤔 🤦‍️

Escopo e Contexto

Cada objeto deve definir qual o seu escopo. Isso impactará diretamente quando cada objeto será criado e removido. Ou se existirá apenas um ou várias ao mesmo tempo. Os escopos pre definidos pelo Java CDI são:

Scope Annotation Duration
Request @RequestScoped A user’s interaction with a web application in a single HTTP request.
Session @SessionScoped A user’s interaction with a web application across multiple HTTP requests.
Application @ApplicationScoped Shared state across all users’ interactions with a web application.
Dependent @Dependent The default scope if none is specified; it means that an object exists to serve exactly one client (bean) and has the same lifecycle as that client (bean).
Conversation @ConversationScoped A user’s interaction with a JavaServer Faces application, within explicit developer-controlled boundaries that extend the scope across multiple invocations of the JavaServer Faces lifecycle. All long-running conversations are scoped to a particular HTTP servlet session and may not cross session boundaries.

Nesse tutorial, para facilitar os exemplos, vamos tratar apenas dos escopos Request e Application.

O SessionScoped é usado em projetos JSF onde há uma sessão de usuário ativa.

Diretamente

Qualquer objeto que cumpra todos os requisitos acima. Como você pode ver em UserRepository.java, o campo collection é inserido e inicializado apenas usando a annotation @Inject.

<span>@Dependent</span>
<span>public</span> <span>class</span> <span>UserRepository</span> <span>{</span>
<span>[...]</span>
<span>@Inject</span>
<span>private</span> <span>MongoCollection</span><span><</span><span>User</span><span>></span> <span>collection</span><span>;</span>
<span>[...]</span>
<span>}</span>
<span>@Dependent</span>
<span>public</span> <span>class</span> <span>UserRepository</span> <span>{</span>
    <span>[...]</span>

    <span>@Inject</span>
    <span>private</span> <span>MongoCollection</span><span><</span><span>User</span><span>></span> <span>collection</span><span>;</span>

    <span>[...]</span>
<span>}</span>
@Dependent public class UserRepository { [...] @Inject private MongoCollection<User> collection; [...] }

Enter fullscreen mode Exit fullscreen mode

Ciclo de Vida

Juntamente com a instanciação, o Java CDI permite controlar o ciclo de vida de um objeto. Se for preciso limpar o objeto ao mesmo ser removido ou instanciar objetos não CDI, como faz?

Para isso podemos usar as annotations @PostConstruct e @PreDestroy.

Veja o exemplo de MongoClientFactory. Observe que ao ser inicializado, é chamado automaticamente o metodo buildMongoClient e ao ser removido o metodo cleanup.

Factory

E se a classe não for definida no meu código, como faço?

Para isso, podemos inicializar ela através de uma Factory.

Em CDI ela é definida pela annotation @Produces.

Veja o caso de MongoClientFactory, a classe é usada pelo CDI para instanciar qualquer instancia de MongoCollection.

Se a classe MongoCollection fosse Closeable, eu precisaria também implementaro um metodo pra isso, logo:

<span>public</span> <span><</span><span>T</span><span>></span> <span>void</span> <span>disposeMongoCollection</span><span>(</span><span>@Dispose</span> <span>MongoCollection</span><span><</span><span>T</span><span>></span> <span>collection</span><span>)</span> <span>{</span>
<span>// close if possible</span>
<span>}</span>
<span>public</span> <span><</span><span>T</span><span>></span> <span>void</span> <span>disposeMongoCollection</span><span>(</span><span>@Dispose</span> <span>MongoCollection</span><span><</span><span>T</span><span>></span> <span>collection</span><span>)</span> <span>{</span>
    <span>// close if possible</span>
<span>}</span>
public <T> void disposeMongoCollection(@Dispose MongoCollection<T> collection) { // close if possible }

Enter fullscreen mode Exit fullscreen mode

Usando Qualifiers

E se agora eu precisar escolher entre implementações distintas? Como faço?

O CDI também tem a solução para isso com baixo acoplamento. Primeiro você vai ter que definir uma interface ou uma class abstrata. Em nosso exemplos vamos criar o HelloService com a unica intenção de dizer Oi.

<span>public</span> <span>interface</span> <span>HelloService</span> <span>{</span>
<span>public</span> <span>String</span> <span>sayHello</span><span>(</span><span>String</span> <span>username</span><span>);</span>
<span>}</span>
<span>public</span> <span>interface</span> <span>HelloService</span> <span>{</span>

    <span>public</span> <span>String</span> <span>sayHello</span><span>(</span><span>String</span> <span>username</span><span>);</span>
<span>}</span>
public interface HelloService { public String sayHello(String username); }

Enter fullscreen mode Exit fullscreen mode

Nessa interface não é necessário praticamente nada.

Depois é necessário definir quais serão os qualifiers usados:

<span>@Qualifier</span>
<span>@Retention</span><span>(</span><span>RUNTIME</span><span>)</span>
<span>@Target</span><span>({</span> <span>TYPE</span><span>,</span> <span>ElementType</span><span>.</span><span>FIELD</span> <span>})</span>
<span>public</span> <span>@interface</span> <span>PtBr</span> <span>{</span>
<span>}</span>
<span>@Qualifier</span>
<span>@Retention</span><span>(</span><span>RUNTIME</span><span>)</span>
<span>@Target</span><span>({</span> <span>TYPE</span><span>,</span> <span>ElementType</span><span>.</span><span>FIELD</span> <span>})</span>
<span>public</span> <span>@interface</span> <span>PtBr</span> <span>{</span>

<span>}</span>
@Qualifier @Retention(RUNTIME) @Target({ TYPE, ElementType.FIELD }) public @interface PtBr { }

Enter fullscreen mode Exit fullscreen mode

O proximo passo é definir as implementações, segue abaixo as duas que fiz:

<span>@ApplicationScoped</span>
<span>@PtBr</span>
<span>public</span> <span>class</span> <span>HelloServicePtBr</span> <span>implements</span> <span>HelloService</span> <span>{</span>
<span>@Override</span>
<span>public</span> <span>String</span> <span>sayHello</span><span>(</span><span>String</span> <span>username</span><span>)</span> <span>{</span>
<span>return</span> <span>String</span><span>.</span><span>format</span><span>(</span><span>"Olá! %s"</span><span>,</span> <span>username</span><span>);</span>
<span>}</span>
<span>}</span>
<span>@ApplicationScoped</span>
<span>@PtBr</span>
<span>public</span> <span>class</span> <span>HelloServicePtBr</span> <span>implements</span> <span>HelloService</span> <span>{</span>

    <span>@Override</span>
    <span>public</span> <span>String</span> <span>sayHello</span><span>(</span><span>String</span> <span>username</span><span>)</span> <span>{</span>
        <span>return</span> <span>String</span><span>.</span><span>format</span><span>(</span><span>"Olá! %s"</span><span>,</span> <span>username</span><span>);</span>
    <span>}</span>

<span>}</span>
@ApplicationScoped @PtBr public class HelloServicePtBr implements HelloService { @Override public String sayHello(String username) { return String.format("Olá! %s", username); } }

Enter fullscreen mode Exit fullscreen mode

E

<span>@ApplicationScoped</span>
<span>public</span> <span>class</span> <span>HelloServiceUs</span> <span>implements</span> <span>HelloService</span> <span>{</span>
<span>@Override</span>
<span>public</span> <span>String</span> <span>sayHello</span><span>(</span><span>String</span> <span>username</span><span>)</span> <span>{</span>
<span>return</span> <span>String</span><span>.</span><span>format</span><span>(</span><span>"Hello! %s"</span><span>,</span> <span>username</span><span>);</span>
<span>}</span>
<span>}</span>
<span>@ApplicationScoped</span>
<span>public</span> <span>class</span> <span>HelloServiceUs</span> <span>implements</span> <span>HelloService</span> <span>{</span>

    <span>@Override</span>
    <span>public</span> <span>String</span> <span>sayHello</span><span>(</span><span>String</span> <span>username</span><span>)</span> <span>{</span>
        <span>return</span> <span>String</span><span>.</span><span>format</span><span>(</span><span>"Hello! %s"</span><span>,</span> <span>username</span><span>);</span>
    <span>}</span>

<span>}</span>
@ApplicationScoped public class HelloServiceUs implements HelloService { @Override public String sayHello(String username) { return String.format("Hello! %s", username); } }

Enter fullscreen mode Exit fullscreen mode

Nos dois casos acima, você pode observar que HelloServiceUs é a implementação padrão, enquanto HelloServicePtBr seria uma alternativa. Assim podemos usar:

<span>public</span> <span>class</span> <span>SayHelloEndpoing</span> <span>{</span>
<span>@Inject</span>
<span>private</span> <span>HelloService</span> <span>helloService</span><span>;</span>
<span>// something</span>
<span>}</span>
<span>public</span> <span>class</span> <span>SayHelloEndpoing</span> <span>{</span>

    <span>@Inject</span>
    <span>private</span> <span>HelloService</span> <span>helloService</span><span>;</span>

    <span>// something</span>

<span>}</span>
public class SayHelloEndpoing { @Inject private HelloService helloService; // something }

Enter fullscreen mode Exit fullscreen mode

Para usar a implementação padrão, e apenas adicionando @PtBr podemos alterar a implementação.

<span>public</span> <span>class</span> <span>SayHelloEndpoing</span> <span>{</span>
<span>@Inject</span>
<span>@PtBr</span>
<span>private</span> <span>HelloService</span> <span>helloService</span><span>;</span>
<span>// something</span>
<span>}</span>
<span>public</span> <span>class</span> <span>SayHelloEndpoing</span> <span>{</span>

    <span>@Inject</span>
    <span>@PtBr</span>
    <span>private</span> <span>HelloService</span> <span>helloService</span><span>;</span>

    <span>// something</span>

<span>}</span>
public class SayHelloEndpoing { @Inject @PtBr private HelloService helloService; // something }

Enter fullscreen mode Exit fullscreen mode

Assim é possível trocar a implementação de um componente com minimas alterações nas classes.

Interceptando

Uma última feature do CDI é a possibilidade de se interceptar. Essa é a implementação do que podemos chamar de AOP.

Vamos deixar claro, qual a intenção de se usar um interceptador? Remover do código funcionalidades que não fazem parte da intenção direta do código. Por exemplo: Uma criação de usuário, exitem as regras de negócio e transações, transações são elegíveis para um interceptador, enquantoas regras de negócio DEVEM estar expressas no código.

Como fazer uma interceptação?

  1. Sua classe a ser interceptada deve ser provida pelo CDI
  2. Uma Annotation, usando @Inherited e @InterceptorBinding, para marcar o metodo/classe
  3. Sua classe a ser interceptada deve ter algum qualifier associado (a ela ou ao método)
  4. Deve ser declarado um interceptador tanto na classe como no beans.xml.

Vamos supor que em algumas classes eu desejo gerar estatisticas, então observe a implementação abaixo:

<span>@Measured</span>
<span>@Interceptor</span>
<span>public</span> <span>class</span> <span>StatisticInterceptor</span> <span>{</span>
<span>private</span> <span>static</span> <span>final</span> <span>Logger</span> <span>logger</span> <span>=</span> <span>LoggerFactory</span><span>.</span><span>getLogger</span><span>(</span><span>StatisticInterceptor</span><span>.</span><span>class</span><span>);</span>
<span>@AroundInvoke</span>
<span>public</span> <span>Object</span> <span>calculateExecutionTime</span><span>(</span><span>InvocationContext</span> <span>invocationContext</span><span>)</span> <span>throws</span> <span>Exception</span> <span>{</span>
<span>long</span> <span>startTime</span> <span>=</span> <span>System</span><span>.</span><span>currentTimeMillis</span><span>();</span>
<span>Object</span> <span>returnedValue</span> <span>=</span> <span>invocationContext</span><span>.</span><span>proceed</span><span>();</span>
<span>long</span> <span>endTime</span> <span>=</span> <span>System</span><span>.</span><span>currentTimeMillis</span><span>();</span>
<span>logger</span><span>.</span><span>info</span><span>(</span><span>"The execution of {}.{} took {}ms"</span><span>,</span> <span>invocationContext</span><span>.</span><span>getMethod</span><span>().</span><span>getName</span><span>(),</span>
<span>invocationContext</span><span>.</span><span>getMethod</span><span>().</span><span>getDeclaringClass</span><span>().</span><span>getName</span><span>(),</span> <span>endTime</span> <span>-</span> <span>startTime</span><span>);</span>
<span>return</span> <span>returnedValue</span><span>;</span>
<span>}</span>
<span>}</span>
<span>@Measured</span>
<span>@Interceptor</span>
<span>public</span> <span>class</span> <span>StatisticInterceptor</span> <span>{</span>

    <span>private</span> <span>static</span> <span>final</span> <span>Logger</span> <span>logger</span> <span>=</span> <span>LoggerFactory</span><span>.</span><span>getLogger</span><span>(</span><span>StatisticInterceptor</span><span>.</span><span>class</span><span>);</span>

    <span>@AroundInvoke</span>
    <span>public</span> <span>Object</span> <span>calculateExecutionTime</span><span>(</span><span>InvocationContext</span> <span>invocationContext</span><span>)</span> <span>throws</span> <span>Exception</span> <span>{</span>
        <span>long</span> <span>startTime</span> <span>=</span> <span>System</span><span>.</span><span>currentTimeMillis</span><span>();</span>
        <span>Object</span> <span>returnedValue</span> <span>=</span> <span>invocationContext</span><span>.</span><span>proceed</span><span>();</span>
        <span>long</span> <span>endTime</span> <span>=</span> <span>System</span><span>.</span><span>currentTimeMillis</span><span>();</span>
        <span>logger</span><span>.</span><span>info</span><span>(</span><span>"The execution of {}.{} took {}ms"</span><span>,</span> <span>invocationContext</span><span>.</span><span>getMethod</span><span>().</span><span>getName</span><span>(),</span>
        <span>invocationContext</span><span>.</span><span>getMethod</span><span>().</span><span>getDeclaringClass</span><span>().</span><span>getName</span><span>(),</span> <span>endTime</span> <span>-</span> <span>startTime</span><span>);</span>
        <span>return</span> <span>returnedValue</span><span>;</span>
    <span>}</span>
<span>}</span>
@Measured @Interceptor public class StatisticInterceptor { private static final Logger logger = LoggerFactory.getLogger(StatisticInterceptor.class); @AroundInvoke public Object calculateExecutionTime(InvocationContext invocationContext) throws Exception { long startTime = System.currentTimeMillis(); Object returnedValue = invocationContext.proceed(); long endTime = System.currentTimeMillis(); logger.info("The execution of {}.{} took {}ms", invocationContext.getMethod().getName(), invocationContext.getMethod().getDeclaringClass().getName(), endTime - startTime); return returnedValue; } }

Enter fullscreen mode Exit fullscreen mode

Observe a execução! Sucesso!

2020-05-27 13:37:16,263 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of findByUsernameAndPassword.io.vepo.access.user.UserRepository took 6ms
2020-05-27 13:37:16,333 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of login.io.vepo.access.user.UserEndpoint took 113ms
2020-05-27 13:37:35,087 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of findByUsernameAndPassword.io.vepo.access.user.UserRepository took 2ms
2020-05-27 13:37:35,088 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of login.io.vepo.access.user.UserEndpoint took 4ms
2020-05-27 13:37:40,430 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of findByUsernameAndPassword.io.vepo.access.user.UserRepository took 2ms
2020-05-27 13:37:40,431 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of login.io.vepo.access.user.UserEndpoint took 3ms
2020-05-27 13:37:16,263 INFO  [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of findByUsernameAndPassword.io.vepo.access.user.UserRepository took 6ms
2020-05-27 13:37:16,333 INFO  [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of login.io.vepo.access.user.UserEndpoint took 113ms
2020-05-27 13:37:35,087 INFO  [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of findByUsernameAndPassword.io.vepo.access.user.UserRepository took 2ms
2020-05-27 13:37:35,088 INFO  [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of login.io.vepo.access.user.UserEndpoint took 4ms
2020-05-27 13:37:40,430 INFO  [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of findByUsernameAndPassword.io.vepo.access.user.UserRepository took 2ms
2020-05-27 13:37:40,431 INFO  [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of login.io.vepo.access.user.UserEndpoint took 3ms
2020-05-27 13:37:16,263 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of findByUsernameAndPassword.io.vepo.access.user.UserRepository took 6ms 2020-05-27 13:37:16,333 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of login.io.vepo.access.user.UserEndpoint took 113ms 2020-05-27 13:37:35,087 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of findByUsernameAndPassword.io.vepo.access.user.UserRepository took 2ms 2020-05-27 13:37:35,088 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of login.io.vepo.access.user.UserEndpoint took 4ms 2020-05-27 13:37:40,430 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of findByUsernameAndPassword.io.vepo.access.user.UserRepository took 2ms 2020-05-27 13:37:40,431 INFO [i.v.a.i.s.StatisticInterceptor] (default task-1) The execution of login.io.vepo.access.user.UserEndpoint took 3ms

Enter fullscreen mode Exit fullscreen mode

Events e Observers

Uma última feature para usar do CDI são os eventos. O CDI habilita a comunicação asincrona e desacoplada dentro de uma mesma JVM.

Um evento pode ser qualquer objeto Java, de preferência um que representa o evento em si. No meu exemplo usei o UserCreated e UserRemoved para representar essas ações.

Como enviar? Basta instanciar um producer usando o @Inject e pronto:

<span>@Inject</span>
<span>private</span> <span>Event</span><span><</span><span>UserCreated</span><span>></span> <span>createdEvent</span><span>;</span>
<span>@POST</span>
<span>@Secured</span>
<span>@Produces</span><span>(</span><span>MediaType</span><span>.</span><span>APPLICATION_JSON</span><span>)</span>
<span>@Consumes</span><span>(</span><span>MediaType</span><span>.</span><span>APPLICATION_JSON</span><span>)</span>
<span>public</span> <span>User</span> <span>createUser</span><span>(</span><span>User</span> <span>user</span><span>)</span> <span>{</span>
<span>logger</span><span>.</span><span>info</span><span>(</span><span>"Credentials: {}"</span><span>,</span> <span>credentials</span><span>);</span>
<span>user</span><span>.</span><span>setPassword</span><span>(</span><span>this</span><span>.</span><span>passwordEncrypter</span><span>.</span><span>encrypt</span><span>(</span><span>user</span><span>.</span><span>getPassword</span><span>()));</span>
<span>this</span><span>.</span><span>userRepository</span><span>.</span><span>create</span><span>(</span><span>user</span><span>);</span>
<span>this</span><span>.</span><span>createdEvent</span><span>.</span><span>fire</span><span>(</span><span>new</span> <span>UserCreated</span><span>(</span><span>user</span><span>.</span><span>getUsername</span><span>()));</span>
<span>return</span> <span>user</span><span>;</span>
<span>}</span>
<span>@Inject</span>
<span>private</span> <span>Event</span><span><</span><span>UserCreated</span><span>></span> <span>createdEvent</span><span>;</span>


<span>@POST</span>
<span>@Secured</span>
<span>@Produces</span><span>(</span><span>MediaType</span><span>.</span><span>APPLICATION_JSON</span><span>)</span>
<span>@Consumes</span><span>(</span><span>MediaType</span><span>.</span><span>APPLICATION_JSON</span><span>)</span>
<span>public</span> <span>User</span> <span>createUser</span><span>(</span><span>User</span> <span>user</span><span>)</span> <span>{</span>
    <span>logger</span><span>.</span><span>info</span><span>(</span><span>"Credentials: {}"</span><span>,</span> <span>credentials</span><span>);</span>
    <span>user</span><span>.</span><span>setPassword</span><span>(</span><span>this</span><span>.</span><span>passwordEncrypter</span><span>.</span><span>encrypt</span><span>(</span><span>user</span><span>.</span><span>getPassword</span><span>()));</span>
    <span>this</span><span>.</span><span>userRepository</span><span>.</span><span>create</span><span>(</span><span>user</span><span>);</span>
    <span>this</span><span>.</span><span>createdEvent</span><span>.</span><span>fire</span><span>(</span><span>new</span> <span>UserCreated</span><span>(</span><span>user</span><span>.</span><span>getUsername</span><span>()));</span>
    <span>return</span> <span>user</span><span>;</span>
<span>}</span>
@Inject private Event<UserCreated> createdEvent; @POST @Secured @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public User createUser(User user) { logger.info("Credentials: {}", credentials); user.setPassword(this.passwordEncrypter.encrypt(user.getPassword())); this.userRepository.create(user); this.createdEvent.fire(new UserCreated(user.getUsername())); return user; }

Enter fullscreen mode Exit fullscreen mode

Para consumir esse evento, basta criar um método com @Observes UserCreated userCreatedEvent.

Vejamos a implementação:

<span>@ApplicationScoped</span>
<span>public</span> <span>class</span> <span>SendEmailHandler</span> <span>{</span>
<span>private</span> <span>static</span> <span>final</span> <span>Logger</span> <span>logger</span> <span>=</span> <span>LoggerFactory</span><span>.</span><span>getLogger</span><span>(</span><span>SendEmailHandler</span><span>.</span><span>class</span><span>);</span>
<span>public</span> <span>void</span> <span>sendUserCreatedEmail</span><span>(</span><span>@Observes</span> <span>UserCreated</span> <span>userCreatedEvent</span><span>)</span> <span>{</span>
<span>logger</span><span>.</span><span>info</span><span>(</span><span>"Sending User Created email! {}"</span><span>,</span> <span>userCreatedEvent</span><span>);</span>
<span>}</span>
<span>public</span> <span>void</span> <span>sendUserRemovedEmail</span><span>(</span><span>@Observes</span> <span>UserRemoved</span> <span>userRemovedEvent</span><span>)</span> <span>{</span>
<span>logger</span><span>.</span><span>info</span><span>(</span><span>"Sending User Removed email! {}"</span><span>,</span> <span>userRemovedEvent</span><span>);</span>
<span>}</span>
<span>}</span>
<span>@ApplicationScoped</span>
<span>public</span> <span>class</span> <span>SendEmailHandler</span> <span>{</span>

    <span>private</span> <span>static</span> <span>final</span> <span>Logger</span> <span>logger</span> <span>=</span> <span>LoggerFactory</span><span>.</span><span>getLogger</span><span>(</span><span>SendEmailHandler</span><span>.</span><span>class</span><span>);</span>

    <span>public</span> <span>void</span> <span>sendUserCreatedEmail</span><span>(</span><span>@Observes</span> <span>UserCreated</span> <span>userCreatedEvent</span><span>)</span> <span>{</span>
        <span>logger</span><span>.</span><span>info</span><span>(</span><span>"Sending User Created email! {}"</span><span>,</span> <span>userCreatedEvent</span><span>);</span>
    <span>}</span>

    <span>public</span> <span>void</span> <span>sendUserRemovedEmail</span><span>(</span><span>@Observes</span> <span>UserRemoved</span> <span>userRemovedEvent</span><span>)</span> <span>{</span>
        <span>logger</span><span>.</span><span>info</span><span>(</span><span>"Sending User Removed email! {}"</span><span>,</span> <span>userRemovedEvent</span><span>);</span>
    <span>}</span>
<span>}</span>
@ApplicationScoped public class SendEmailHandler { private static final Logger logger = LoggerFactory.getLogger(SendEmailHandler.class); public void sendUserCreatedEmail(@Observes UserCreated userCreatedEvent) { logger.info("Sending User Created email! {}", userCreatedEvent); } public void sendUserRemovedEmail(@Observes UserRemoved userRemovedEvent) { logger.info("Sending User Removed email! {}", userRemovedEvent); } }

Enter fullscreen mode Exit fullscreen mode

Conclusão

CDI ajudar a controlar o ciclo de vida de um objeto e com isso diminui o acomplamento do seu código. Para quem não o conhece parece mágica, por isso é importante que seu equipe tenha total conhecimento da especificação antes de usa-lo.

Com os Intercepts do CDI é possível remover códigos que são ortogonais a lógica de negócio para que estes não atrapalhem o entendimento. Deixando a base de código clara e compartimentada.

Com os Eventos CDI é possível fazer com que seu código seja mais desacoplado e que novas funcionalidades não precisem alterar códigos antigos.

Bom uso!

Está faltando mais alguma coisa? Está achando confuso? Pergunte que eu posso melhorar!

Todos os exemplos desse post estão em:

vepo / cdi-tutorial

Java CDI tutorial using Jakarta EE (Thorntail)

Tutorial CDI

Usado para palestras

O conteúdo principal da palestra está em CDI.

Antes de executar, é necessário haver uma base MongoDB rodando na mesma máquina.

docker run --rm --name mongo-db --env MONGO_INITDB_ROOT_USERNAME=root --env MONGO_INITDB_ROOT_PASSWORD=root -p 27017:27017 mongo:3.4

Enter fullscreen mode Exit fullscreen mode

Para executar:

mvn clean thorntail:run
mvn clean thorntail:run
mvn clean thorntail:run

View on GitHub

Especificações Jakarta EE & Microprofile.io (3 Part Series)

1 Jakarta EE e Microprofile.io
2 Escolhendo uma implementação Jakarta EE & Microprofile.io
3 Contexts and Dependency Injection (CDI) 2.0

原文链接:Contexts and Dependency Injection (CDI) 2.0

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
Life is never easier, we just get stronger.
生活从未变得容易,只是我们变得更加坚强
评论 抢沙发

请登录后发表评论

    暂无评论内容