Introdução à Inversão do Controle e Injeção de Dependência com Mola
1. Visão geral
Neste artigo, apresentaremos os conceitos de IoC (Inversão de Controle) e DI (Injeção de Dependência), e então daremos uma olhada em como eles são implementados no framework Spring.
Leitura adicional:
Fiação na primavera: @Autowired, @Resource e @Inject
Este artigo irá comparar e contrastar o uso de anotações relacionadas à injeção de dependência, a saber, as anotações @Resource, @Inject e @Autowired.
@Component vs @Repository e @Service na Primavera
Aprenda sobre as diferenças entre as anotações @Component, @Repository e @Service e quando usá-las.
2. O que é inversão de controle?
Inversão de controle é um princípio na engenharia de software pelo qual o controle de objetos ou partes de um programa é transferido para um contêiner ou estrutura. É mais frequentemente usado no contexto de programação orientada a objetos.
Em contraste com a programação tradicional, na qual nosso código personalizado faz chamadas para uma biblioteca, a IoC permite que uma estrutura controle o fluxo de um programa e faça chamadas para nosso código personalizado. Para habilitar isso, as estruturas usam abstrações com comportamento adicional incorporado. If we want to add our own behavior, we need to extend the classes of the framework or plugin our own classes.
As vantagens dessa arquitetura são:
-
dissociando a execução de uma tarefa de sua implementação
-
facilitando a alternância entre diferentes implementações
-
maior modularidade de um programa
-
maior facilidade em testar um programa isolando um componente ou zombando de suas dependências e permitindo que os componentes se comuniquem através de contratos
A inversão do controle pode ser alcançada através de vários mecanismos, como: padrão de design da estratégia, padrão do Localizador de serviço, padrão de fábrica e injeção de dependência (DI).
Nós vamos olhar para DI a seguir.
3. O que é injeção de dependência?
A injeção de dependência é um padrão através do qual implementar IoC, onde o controle sendo invertido é a configuração das dependências do objeto.
O ato de conectar objetos com outros objetos, ou "injetar" objetos em outros objetos, é realizado por um montador e não pelos próprios objetos.
Veja como você criaria uma dependência de objeto na programação tradicional:
public class Store {
private Item item;
public Store() {
item = new ItemImpl1();
}
}
No exemplo acima, precisamos instanciar uma implementação da interfaceItem dentro da própria classeStore.
Usando DI, podemos reescrever o exemplo sem especificar a implementação deItem que queremos:
public class Store {
private Item item;
public Store(Item item) {
this.item = item;
}
}
Nas próximas seções, veremos como podemos fornecer a implementação deItem por meio de metadados.
Tanto IoC quanto DI são conceitos simples, mas têm profundas implicações na maneira como estruturamos nossos sistemas, por isso vale a pena entendê-los bem.
4. O contêiner Spring IoC
Um contêiner de IoC é uma característica comum das estruturas que implementam a IoC.
Na estrutura do Spring, o contêiner IoC é representado pela interfaceApplicationContext. O container Spring é responsável por instanciar, configurar e montar objetos conhecidos comobeans, assim como gerenciar seu ciclo de vida.
A estrutura Spring fornece várias implementações da interfaceApplicationContext -ClassPathXmlApplicationContexteFileSystemXmlApplicationContext para aplicativos independentes eWebApplicationContext para aplicativos da web.
Para montar beans, o contêiner usa metadados de configuração, que podem estar na forma de anotações ou configuração XML.
Esta é uma maneira de instanciar manualmente um contêiner:
ApplicationContext context
= new ClassPathXmlApplicationContext("applicationContext.xml");
Para definir o atributoitem no exemplo acima, podemos usar metadados. Em seguida, o contêiner lerá esses metadados e os usará para montar beans em tempo de execução.
A injeção de dependência no Spring pode ser feita por meio de construtores, configuradores ou campos.
5. Injeção de dependência baseada em construtor
No caso deconstructor-based dependency injection, o contêiner invocará um construtor com argumentos, cada um representando uma dependência que queremos definir.
O Spring resolve cada argumento principalmente por tipo, seguido pelo nome do atributo e pelo índice de desambiguação. Vejamos a configuração de um bean e suas dependências usando anotações:
@Configuration
public class AppConfig {
@Bean
public Item item1() {
return new ItemImpl1();
}
@Bean
public Store store() {
return new Store(item1());
}
}
A anotação@Configuration indica que a classe é uma fonte de definições de bean. Além disso, podemos adicioná-lo a várias classes de configuração.
A anotação@Bean é usada em um método para definir um bean. Se não especificarmos um nome personalizado, o nome do bean será o padrão para o nome do método.
Para um bean com o escopo padrãosingleton, o Spring primeiro verifica se uma instância em cache do bean já existe e apenas cria uma nova se não existir. Se estivermos usando o escopoprototype, o contêiner retornará uma nova instância de bean para cada chamada de método.
Outra maneira de criar a configuração dos beans é através da configuração XML:
6. Injeção de dependência baseada em setter
Para DI baseada em setter, o contêiner chamará métodos setter de nossa classe, depois de chamar um construtor sem argumento ou método estático de fábrica sem argumento para instanciar o bean. Vamos criar esta configuração usando anotações:
@Bean
public Store store() {
Store store = new Store();
store.setItem(item1());
return store;
}
Também podemos usar XML para a mesma configuração de beans:
Os tipos de injeção com base no construtor e no setter podem ser combinados para o mesmo bean. A documentação do Spring recomenda o uso de injeção baseada em construtor para dependências obrigatórias e injeção baseada em setter para dependências opcionais.
7. Baseado em campo Injeção de dependência
No caso da DI baseada em campo, podemos injetar as dependências marcando-as com uma anotação@Autowired:
public class Store {
@Autowired
private Item item;
}
Ao construir o objetoStore, se não houver nenhum método construtor ou setter para injetar o beanItem, o contêiner usará reflexão para injetarItem emStore.
Também podemos conseguir isso usandoXML configuration.
Essa abordagem pode parecer mais simples e mais limpa, mas não é recomendada, porque possui algumas desvantagens, como:
-
Esse método usa a reflexão para injetar as dependências, que são mais caras que a injeção baseada em construtor ou em setter
-
É realmente fácil adicionar várias dependências usando essa abordagem. Se você estivesse usando injeção de construtor com vários argumentos, nos faria pensar que a classe faz mais de uma coisa que pode violar o Princípio de Responsabilidade Única.
Mais informações sobre a anotação@Autowired podem ser encontradas no artigoWiring In Spring.
8. Dependências Autowiring
Wiring permite que o contêiner Spring resolva automaticamente as dependências entre os beans de colaboração inspecionando os beans que foram definidos.
Existem quatro modos de conectar automaticamente um bean usando uma configuração XML:
-
no: o valor padrão - isso significa que nenhum autowiring é usado para o bean e temos que nomear explicitamente as dependências
-
byName: autowiring é feito com base no nome da propriedade, portanto, Spring irá procurar por um bean com o mesmo nome da propriedade que precisa ser definida
-
byType: semelhante à fiação automática debyName, apenas com base no tipo da propriedade. Isso significa que o Spring procurará um bean com o mesmo tipo de propriedade a ser definido. Se houver mais de um bean desse tipo, a estrutura lança uma exceção.
-
constructor: autowiring é feito com base nos argumentos do construtor, o que significa que o Spring irá procurar por beans com o mesmo tipo dos argumentos do construtor
Por exemplo, vamos autowire o beanitem1 definido acima por tipo no beanstore:
@Bean(autowire = Autowire.BY_TYPE)
public class Store {
private Item item;
public setItem(Item item){
this.item = item;
}
}
Também podemos injetar beans usando a anotação@Autowired para autowiring por tipo:
public class Store {
@Autowired
private Item item;
}
Se houver mais de um bean do mesmo tipo, podemos usar a anotação@Qualifier para fazer referência a um bean pelo nome:
public class Store {
@Autowired
@Qualifier("item1")
private Item item;
}
Agora, vamos autowire beans por tipo por meio da configuração XML:
A seguir, vamos injetar um bean denominadoitem na propriedadeitem do beanstore por nome por meio de XML:
Também podemos substituir a fiação automática definindo dependências explicitamente por meio de argumentos ou setters de construtores.
9. Feijão Inicializado Preguiçoso
Por padrão, o contêiner cria e configura todos os beans singleton durante a inicialização. Para evitar isso, você pode usar o atributolazy-init com o valortrue na configuração do bean:
Como consequência, o beanitem1 será inicializado apenas quando for solicitado pela primeira vez, e não na inicialização. A vantagem disso é o tempo de inicialização mais rápido, mas a desvantagem é que os erros de configuração podem ser descobertos somente após a solicitação do bean, o que pode levar várias horas ou até dias após o aplicativo já estar em execução.
10. Conclusão
Neste artigo, apresentamos os conceitos de inversão de controle e injeção de dependência e os exemplificamos no framework Spring.
Você pode ler mais sobre esses conceitos nos artigos de Martin Fowler:
E você pode aprender mais sobre as implementações Spring de IoC e DI noSpring Framework Reference Documentation.