JavaRush /Blogue Java /Random-PT /Uma breve excursão sobre injeção de dependência ou "O que...
Viacheslav
Nível 3

Uma breve excursão sobre injeção de dependência ou "O que mais é CDI?"

Publicado no grupo Random-PT
A base sobre a qual as estruturas mais populares são construídas atualmente é a injeção de dependência. Sugiro observar o que a especificação CDI diz sobre isso, quais recursos básicos temos e como podemos usá-los.
Uma breve excursão sobre injeção de dependência ou

Introdução

Eu gostaria de dedicar esta breve revisão a algo como o CDI. O que é isso? CDI significa Contextos e Injeção de Dependência. Esta é uma especificação Java EE que descreve injeção de dependência e contextos. Para obter informações, você pode consultar o site http://cdi-spec.org . Como o CDI é uma especificação (uma descrição de como deve funcionar, um conjunto de interfaces), também precisaremos de uma implementação para utilizá-lo. Uma dessas implementações é o Weld - http://weld.cdi-spec.org/ Para gerenciar dependências e criar um projeto, usaremos o Maven - https://maven.apache.org Então, temos o Maven instalado, agora vamos vai entender na prática, para não entender o abstrato. Para fazer isso, criaremos um projeto usando Maven. Vamos abrir a linha de comando (no Windows, você pode usar Win+R para abrir a janela “Executar” e executar cmd) e pedir ao Maven para fazer tudo por nós. Para isso, o Maven possui um conceito chamado de arquétipo: Maven Archetype .
Uma breve excursão sobre injeção de dependência ou
Depois disso, nas perguntas “ Escolha um número ou aplique filtro ” e “ Escolha org.apache.maven.archetypes:maven-archetype-quickstart version ” basta pressionar Enter. Em seguida, insira os identificadores do projeto, os chamados GAV (ver Guia de Convenção de Nomenclatura ).
Uma breve excursão sobre injeção de dependência ou
Após a criação bem-sucedida do projeto, veremos a inscrição “BUILD SUCCESS”. Agora podemos abrir nosso projeto em nosso IDE favorito.

Adicionando CDI a um projeto

Na introdução, vimos que o CDI possui um site interessante - http://www.cdi-spec.org/ . Existe uma seção de download, que contém uma tabela que contém os dados que precisamos:
Uma breve excursão sobre injeção de dependência ou
Aqui podemos ver como o Maven descreve o fato de usarmos a API CDI no projeto. API é uma interface de programação de aplicativo, ou seja, alguma interface de programação. Trabalhamos com a interface sem nos preocupar com o que e como funciona por trás dessa interface. A API é um arquivo jar que começaremos a utilizar em nosso projeto, ou seja, nosso projeto passa a depender desse jar. Portanto, a API CDI do nosso projeto é uma dependência. No Maven, um projeto é descrito em arquivos POM.xml ( POM - Project Object Model ). As dependências são descritas no bloco de dependências, ao qual precisamos adicionar uma nova entrada:
<dependency>
	<groupId>javax.enterprise</groupId>
	<artifactId>cdi-api</artifactId>
	<version>2.0</version>
</dependency>
Como você deve ter notado, não especificamos o escopo com o valor fornecido. Por que existe essa diferença? Este escopo significa que alguém nos fornecerá a dependência. Quando um aplicativo é executado em um servidor Java EE, significa que o servidor fornecerá ao aplicativo todas as tecnologias JEE necessárias. Para simplificar esta revisão, trabalharemos em um ambiente Java SE, portanto ninguém nos fornecerá essa dependência. Você pode ler mais sobre o Escopo de Dependência aqui: " Escopo de Dependência ". Ok, agora temos a capacidade de trabalhar com interfaces. Mas também precisamos de implementação. Como lembramos, usaremos o Weld. É interessante que diferentes dependências sejam fornecidas em todos os lugares. Mas seguiremos a documentação. Portanto, vamos ler " 18.4.5. Configurando o Classpath " e fazer como diz:
<dependency>
	<groupId>org.jboss.weld.se</groupId>
	<artifactId>weld-se-core</artifactId>
	<version>3.0.5.Final</version>
</dependency>
É importante que as versões de terceira linha do Weld suportem CDI 2.0. Portanto, podemos contar com a API desta versão. Agora estamos prontos para escrever código.
Uma breve excursão sobre injeção de dependência ou

Inicializando um contêiner CDI

CDI é um mecanismo. Alguém deve controlar este mecanismo. Como já lemos acima, tal gerenciador é um contêiner. Portanto, precisamos criá-lo; ele próprio não aparecerá no ambiente SE. Vamos adicionar o seguinte ao nosso método principal:
public static void main(String[] args) {
	SeContainerInitializer initializer = SeContainerInitializer.newInstance();
	initializer.addPackages(App.class.getPackage());
	SeContainer container = initializer.initialize();
}
Criamos o contêiner CDI manualmente porque... Trabalhamos em um ambiente SE. Em projetos típicos de combate, o código é executado em um servidor, que fornece diversas tecnologias ao código. Assim, se o servidor fornecer CDI, isso significa que o servidor já possui um contêiner CDI e não precisaremos adicionar nada. Mas para os propósitos deste tutorial, usaremos o ambiente SE. Além disso, o contêiner está aqui, de forma clara e compreensível. Por que precisamos de um contêiner? O contêiner dentro contém beans (feijões CDI).
Uma breve excursão sobre injeção de dependência ou

Feijão CDI

Então, feijão. O que é uma caixa de CDI? Esta é uma classe Java que segue algumas regras. Estas regras estão descritas na especificação, no capítulo " 2.2. Que tipos de classes são beans? ". Vamos adicionar um bean CDI ao mesmo pacote da classe App:
public class Logger {
    public void print(String message) {
        System.out.println(message);
    }
}
Agora podemos chamar esse bean do nosso mainmétodo:
Logger logger = container.select(Logger.class).get();
logger.print("Hello, World!");
Como você pode ver, não criamos o bean usando a palavra-chave new. Perguntamos ao contêiner CDI: "Contêiner CDI. Eu realmente preciso de uma instância da classe Logger, dê-me, por favor." Este método é denominado " Pesquisa de dependências ", ou seja, busca por dependências. Agora vamos criar uma nova classe:
public class DateSource {
    public String getDate() {
        return new Date().toString();
    }
}
Uma classe primitiva que retorna uma representação de texto de uma data. Vamos agora adicionar a saída da data à mensagem:
public class Logger {
    @Inject
    private DateSource dateSource;

    public void print(String message) {
        System.out.println(dateSource.getDate() + " : " + message);
    }
}
Uma anotação @Inject interessante apareceu. Conforme indicado no capítulo " 4.1. Pontos de injeção " da documentação do cdi weld, utilizando esta anotação definimos o Ponto de Injeção. Em russo, isso pode ser lido como “pontos de implementação”. Eles são usados ​​pelo contêiner CDI para injetar dependências ao instanciar beans. Como você pode ver, não estamos atribuindo nenhum valor ao campo dateSource. A razão para isso é o fato de que o contêiner CDI permite dentro dos beans CDI (apenas aqueles beans que ele mesmo instancia, ou seja, que ele gerencia) usar “ Injeção de Dependência ”. Esta é outra forma de Inversão de Controle , uma abordagem em que a dependência é controlada por outra pessoa, em vez de criarmos explicitamente os objetos. A injeção de dependência pode ser feita por meio de um método, construtor ou campo. Para mais detalhes, consulte o capítulo de especificação do CDI " 5.5. Injeção de dependência ". O procedimento para determinar o que precisa ser implementado é chamado de resolução typesafe, sobre o qual precisamos falar.
Uma breve excursão sobre injeção de dependência ou

Resolução de nomes ou resolução Typesafe

Normalmente, uma interface é usada como o tipo de objeto a ser implementado, e o próprio contêiner CDI determina qual implementação escolher. Isso é útil por vários motivos, que discutiremos. Portanto, temos uma interface de logger:
public interface Logger {
    void print(String message);
}
Ele diz que se tivermos algum logger, podemos enviar uma mensagem para ele e ele completará sua tarefa - log. Como e onde não será de interesse neste caso. Vamos agora criar uma implementação para o logger:
public class SystemOutLogger implements Logger {
    @Inject
    private DateSource dateSource;

    public void print(String message) {
        System.out.println(message);
    }
}
Como você pode ver, este é um logger que grava em System.out. Maravilhoso. Agora, nosso método principal funcionará como antes. Logger logger = container.select(Logger.class).get(); Esta linha ainda será recebida pelo logger. E o bom é que só precisamos conhecer a interface, e o container CDI já pensa na implementação para nós. Digamos que temos uma segunda implementação que deve enviar o log para algum lugar, para um armazenamento remoto:
public class NetworkLogger implements Logger {
    @Override
    public void print(String message) {
        System.out.println("Send log message to remote log system");
    }
}
Se agora executarmos nosso código sem alterações, obteremos um erro, porque O contêiner CDI vê duas implementações da interface e não pode escolher entre elas: org.jboss.weld.exceptions.AmbiguousResolutionException: WELD-001335: Ambiguous dependencies for type Logger O que fazer? Existem diversas variações disponíveis. A mais simples é a anotação @Vetoed para um bean CDI, para que o contêiner CDI não perceba essa classe como um bean CDI. Mas há uma abordagem muito mais interessante. Um bean CDI pode ser marcado como uma "alternativa" usando a anotação @Alternativedescrita no capítulo " 4.7. Alternativas " da documentação do Weld CDI. O que isso significa? Isso significa que, a menos que digamos explicitamente para usá-lo, ele não será selecionado. Esta é uma versão alternativa do feijão. Vamos marcar o bean NetworkLogger como @Alternative e podemos ver que o código é executado novamente e usado pelo SystemOutLogger. Para habilitar a alternativa, devemos ter um arquivo beans.xml . Pode surgir a pergunta: " beans.xml, onde coloco você? " Portanto, vamos colocar o arquivo corretamente:
Uma breve excursão sobre injeção de dependência ou
Assim que tivermos este arquivo, o artefato com nosso código será denominado “ Arquivo de bean explícito ”. Agora temos 2 configurações separadas: software e xml. O problema é que eles carregarão os mesmos dados. Por exemplo, a definição do bean DataSource será carregada 2 vezes e nosso programa irá travar quando executado, porque O contêiner CDI irá considerá-los como dois beans separados (embora na verdade sejam da mesma classe, que o contêiner CDI aprendeu duas vezes). Para evitar isso existem 2 opções:
  • remova a linha initializer.addPackages(App.class.getPackage())e adicione uma indicação da alternativa ao arquivo xml:
<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">
    <alternatives>
        <class>ru.javarush.NetworkLogger</class>
    </alternatives>
</beans>
  • adicione um atributo bean-discovery-modecom o valor " none " ao elemento raiz do beans e especifique uma alternativa programaticamente:
initializer.addPackages(App.class.getPackage());
initializer.selectAlternatives(NetworkLogger.class);
Assim, utilizando a alternativa CDI, o contêiner pode determinar qual bean selecionar. Curiosamente, se o contêiner CDI conhece diversas alternativas para a mesma interface, então podemos saber indicando a prioridade usando uma anotação @Priority(Desde CDI 1.1).
Uma breve excursão sobre injeção de dependência ou

Eliminatórias

Separadamente, vale a pena discutir eliminatórias. O qualificador é indicado por uma anotação acima do bean e refina a busca pelo bean. E agora mais detalhes. Curiosamente, qualquer bean CDI, em qualquer caso, possui pelo menos um qualificador - @Any. Se não especificarmos QUALQUER qualificador acima do bean, o próprio contêiner CDI adicionará @Anyoutro qualificador ao qualificador - @Default. Se especificarmos algo (por exemplo, especificar @Any explicitamente), o qualificador @Default não será adicionado automaticamente. Mas a beleza das eliminatórias é que você pode criar suas próprias eliminatórias. O qualificador quase não difere das anotações, porque em essência, esta é apenas uma anotação escrita de uma maneira especial. Por exemplo, você pode inserir Enum para o tipo de protocolo:
public enum ProtocolType {
    HTTP, HTTPS
}
A seguir podemos fazer um qualificador que levará este tipo em consideração:
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Protocol {
    ProtocolType value();
    @Nonbinding String comment() default "";
}
Vale ressaltar que os campos marcados como @Nonbindingnão afetam a determinação do qualificador. Agora você precisa especificar o qualificador. É indicado acima do tipo de bean (para que o CDI saiba defini-lo) e acima do Ponto de Injeção (com a anotação @Inject, para que você entenda qual bean procurar para injeção neste local). Por exemplo, podemos adicionar alguma classe com um qualificador. Para simplificar, neste artigo faremos isso dentro do NetworkLogger:
public interface Sender {
	void send(byte[] data);
}

@Protocol(ProtocolType.HTTP)
public static class HTTPSender implements Sender{
	public void send(byte[] data) {
		System.out.println("sended via HTTP");
	}
}

@Protocol(ProtocolType.HTTPS)
public static class HTTPSSender implements Sender{
	public void send(byte[] data) {
		System.out.println("sended via HTTPS");
	}
}
E então quando realizarmos o Inject, especificaremos um qualificador que influenciará qual classe será utilizada:
@Inject
@Protocol(ProtocolType.HTTPS)
private Sender sender;
Ótimo, não é?) Parece lindo, mas não está claro por quê. Agora imagine o seguinte:
Protocol protocol = new Protocol() {
	@Override
	public Class<? extends Annotation> annotationType() {
		return Protocol.class;
	}
	@Override
	public ProtocolType value() {
		String value = "HTTP";
		return ProtocolType.valueOf(value);
	}
};
container.select(NetworkLogger.Sender.class, protocol).get().send(null);
Dessa forma, podemos substituir a obtenção de valor para que possa ser calculado dinamicamente. Por exemplo, pode ser obtido em algumas configurações. Então podemos alterar a implementação mesmo em tempo real, sem recompilar ou reiniciar o programa/servidor. Fica muito mais interessante, não é? )
Uma breve excursão sobre injeção de dependência ou

Produtores

Outro recurso útil do CDI são os produtores. Esses são métodos especiais (marcados com uma anotação especial) que são chamados quando algum bean solicita injeção de dependência. Mais detalhes estão descritos na documentação, na seção " 2.2.3. Métodos produtores ". O exemplo mais simples:
@Produces
public Integer getRandomNumber() {
	return new Random().nextInt(100);
}
Agora, ao injetar em campos do tipo Inteiro, este método será chamado e dele será obtido um valor. Aqui devemos entender imediatamente que quando vemos a palavra-chave new, devemos entender imediatamente que este NÃO é um bean CDI. Ou seja, uma instância da classe Random não se tornará um bean CDI apenas porque é derivada de algo que controla o contêiner CDI (neste caso, o produtor).
Uma breve excursão sobre injeção de dependência ou

Interceptadores

Interceptadores são interceptadores que “interferem” no trabalho. No CDI isso é feito de forma bastante clara. Vamos ver como podemos fazer log usando intérpretes (ou interceptadores). Primeiro, precisamos descrever a ligação ao interceptor. Como muitas coisas, isso é feito usando anotações:
@Inherited
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface ConsoleLog {
}
O principal aqui é que se trata de uma ligação para o interceptor ( @InterceptorBinding), que será herdado por extends ( @InterceptorBinding). Agora vamos escrever o próprio interceptor:
@Interceptor
@ConsoleLog
public class LogInterceptor {
    @AroundInvoke
    public Object log(InvocationContext ic) throws Exception {
        System.out.println("Invocation method: " + ic.getMethod().getName());
        return ic.proceed();
    }
}
Você pode ler mais sobre como os interceptores são escritos no exemplo da especificação: " 1.3.6. Exemplo de interceptador ". Bem, tudo o que precisamos fazer é ligar o inerceptor. Para fazer isso, especifique a anotação de ligação acima do método que está sendo executado:
@ConsoleLog
public void print(String message) {
E agora outro detalhe muito importante. Os interceptores estão desabilitados por padrão e devem ser habilitados da mesma forma que as alternativas. Por exemplo, no arquivo beans.xml :
<interceptors>
	<class>ru.javarush.LogInterceptor</class>
</interceptors>
Como você pode ver, é bem simples.
Uma breve excursão sobre injeção de dependência ou

Evento e Observadores

O CDI também fornece um modelo de eventos e observadores. Aqui nem tudo é tão óbvio como com os interceptadores. Portanto, o Evento neste caso pode ser absolutamente qualquer classe; nada de especial é necessário para a descrição. Por exemplo:
public class LogEvent {
    Date date = new Date();
    public String getDate() {
        return date.toString();
    }
}
Agora alguém deve esperar pelo evento:
public class LogEventListener {
    public void logEvent(@Observes LogEvent event){
        System.out.println("Message Date: " + event.getDate());
    }
}
O principal aqui é especificar a anotação @Observes, que indica que este não é apenas um método, mas um método que deve ser chamado como resultado da observação de eventos do tipo LogEvent. Bem, agora precisamos de alguém que assista:
public class LogObserver {
    @Inject
    private Event<LogEvent> event;
    public void observe(LogEvent logEvent) {
        event.fire(logEvent);
    }
}
Temos um único método que informará ao contêiner que ocorreu um evento Event para o tipo de evento LogEvent. Agora só falta usar o observador. Por exemplo, no NetworkLogger podemos adicionar uma injeção do nosso observador:
@Inject
private LogObserver observer;
E no método print podemos notificar o observador que temos um novo evento:
public void print(String message) {
	observer.observe(new LogEvent());
É importante saber que os eventos podem ser processados ​​em um thread ou em vários. Para processamento assíncrono, use um método .fireAsync(em vez de .fire) e uma anotação @ObservesAsync(em vez de @Observes). Por exemplo, se todos os eventos forem executados em threads diferentes, se um thread lançar uma exceção, os outros poderão fazer seu trabalho para outros eventos. Você pode ler mais sobre eventos em CDI, como sempre, na especificação, no capítulo “ 10. Eventos ”.
Uma breve excursão sobre injeção de dependência ou

Decoradores

Como vimos acima, vários padrões de design são coletados sob a ala do CDI. E aqui está outro - um decorador. Isso é uma coisa muito interessante. Vamos dar uma olhada nesta aula:
@Decorator
public abstract class LoggerDecorator implements Logger {
    public final static String ANSI_GREEN = "\u001B[32m";
    public static final String ANSI_RESET = "\u001B[0m";

    @Inject
    @Delegate
    private Logger delegate;

    @Override
    public void print(String message) {
        delegate.print(ANSI_GREEN + message + ANSI_RESET);
    }
}
Ao declará-lo um decorador, dizemos que quando for utilizada qualquer implementação do Logger, será utilizado este “add-on”, que conhece a implementação real, que fica armazenada no campo delegado (já que está marcado com a anotação @Delegate). Os decoradores só podem ser associados a um bean CDI, que por si só não é um interceptador nem um decorador. Um exemplo também pode ser visto na especificação: " 1.3.7. Exemplo de decorador ". O decorador, assim como o interceptador, deve estar ativado. Por exemplo, em beans.xml :
<decorators>
	<class>ru.javarush.LoggerDecorator</class>
</decorators>
Para mais detalhes, veja referência de solda: " Capítulo 10. Decoradores ".

Vida útil

O feijão tem seu próprio ciclo de vida. Parece algo assim:
Uma breve excursão sobre injeção de dependência ou
Como você pode ver na imagem, temos os chamados retornos de chamada do ciclo de vida. Estas são anotações que dirão ao contêiner CDI para chamar determinados métodos em um determinado estágio do ciclo de vida do bean. Por exemplo:
@PostConstruct
public void init() {
	System.out.println("Inited");
}
Este método será chamado quando um bean CDI for instanciado por um contêiner. O mesmo acontecerá com @PreDestroy quando o bean for destruído quando não for mais necessário. Não é à toa que a sigla CDI contém a letra C – Contexto. Os beans no CDI são contextuais, o que significa que seu ciclo de vida depende do contexto em que existem dentro do contêiner do CDI. Para entender isso melhor, você deve ler a seção de especificações “ 7. Ciclo de vida de instâncias contextuais ”. Também vale a pena saber que o próprio contêiner possui um ciclo de vida, sobre o qual você pode ler em “ Eventos de ciclo de vida do contêiner ”.
Uma breve excursão sobre injeção de dependência ou

Total

Acima, olhamos para a ponta do iceberg chamado CDI. CDI faz parte da especificação JEE e é usado no ambiente JavaEE. Quem usa Spring não usa CDI e sim DI, ou seja, são especificações um pouco diferentes. Mas conhecendo e compreendendo o que foi dito acima, você pode facilmente mudar de ideia. Considerando que o Spring suporta anotações do mundo CDI (o mesmo Inject). Materiais adicionais: #Viacheslav
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION