Fiação na primavera: @Autowired, @Resource e @Inject

Fiação na primavera: @Autowired, @Resource e @Inject

1. Visão geral

Este artigo do Spring Framework irá demonstrar o uso de anotações relacionadas à injeção de dependência, ou seja, as anotações@Resource,@Injecte@Autowired. Essas anotações fornecem às classes uma maneira declarativa de resolver dependências. Por exemplo:

@Autowired
ArbitraryClass arbObject;

em vez de instancia-los diretamente (o caminho imperativo), por exemplo:

ArbitraryClass arbObject = new ArbitraryClass();

Duas das três anotações pertencem ao pacote de extensão Java:javax.annotation.Resourceejavax.inject.Inject. A anotação@Autowired pertence ao pacoteorg.springframework.beans.factory.annotation.

Cada uma dessas anotações pode resolver dependências por injeção de campo ou por injeção de setter. Um exemplo simplificado, mas prático, será usado para demonstrar a distinção entre as três anotações, com base nos caminhos de execução adotados por cada anotação.

Os exemplos focarão em como usar as três anotações de injeção durante o teste de integração. A dependência exigida pelo teste pode ser um arquivo arbitrário ou uma classe arbitrária.

2. A anotação@Resource

A anotação@Resource faz parte da coleção de anotaçõesJSR-250 e é fornecida com Java EE. Esta anotação possui os seguintes caminhos de execução, listados por precedência:

  1. Corresponder por Nome

  2. Corresponder por tipo

  3. Jogo por Qualificador

Esses caminhos de execução são aplicáveis ​​ao setter e à injeção de campo.

2.1. Injeção em Campo

Resolver dependências por injeção de campo é alcançado anotando uma variável de instância com a anotação@Resource.

2.1.1. Corresponder por Nome

O teste de integração usado para demonstrar a injeção de campo por nome é listado da seguinte maneira:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceNameType.class)
public class FieldResourceInjectionIntegrationTest {

    @Resource(name="namedFile")
    private File defaultFile;

    @Test
    public void givenResourceAnnotation_WhenOnField_ThenDependencyValid(){
        assertNotNull(defaultFile);
        assertEquals("namedFile.txt", defaultFile.getName());
    }
}

Vamos examinar o código. No teste de integraçãoFieldResourceInjectionTest, na linha 7, a resolução da dependência por nome é alcançada passando o nome do bean como um valor de atributo para a anotação@Resource:

@Resource(name="namedFile")
private File defaultFile;

Essa configuração resolverá dependências usando o caminho de execução de correspondência por nome. O beannamedFile deve ser definido no contexto do aplicativoApplicationContextTestResourceNameType.

Observe que o ID do bean e o valor do atributo de referência correspondente devem corresponder:

@Configuration
public class ApplicationContextTestResourceNameType {

    @Bean(name="namedFile")
    public File namedFile() {
        File namedFile = new File("namedFile.txt");
        return namedFile;
    }
}

A falha em definir o bean no contexto do aplicativo resultará emorg.springframework.beans.factory.NoSuchBeanDefinitionException sendo lançado. Isso pode ser demonstrado alterando o valor do atributo passado na anotação@Bean, no contexto do aplicativoApplicationContextTestResourceNameType; ou alterando o valor do atributo passado na anotação@Resource, no teste de integraçãoFieldResourceInjectionTest.

2.1.2. Corresponder por tipo

Para demonstrar o caminho de execução de correspondência por tipo, apenas remova o valor do atributo na linha 7 do teste de integraçãoFieldResourceInjectionTest para que tenha a seguinte aparência:

@Resource
private File defaultFile;

e execute o teste novamente.

O teste ainda passará porque se a anotação@Resource não receber um nome de bean como um valor de atributo, o Spring Framework irá prosseguir com o próximo nível de precedência, correspondência por tipo, a fim de tentar resolver a dependência .

2.1.3. Jogo por Qualificador

Para demonstrar o caminho de execução match-by-qualifier, o cenário de teste de integração será modificado para que haja dois beans definidos no contexto do aplicativoApplicationContextTestResourceQualifier:

@Configuration
public class ApplicationContextTestResourceQualifier {

    @Bean(name="defaultFile")
    public File defaultFile() {
        File defaultFile = new File("defaultFile.txt");
        return defaultFile;
    }

    @Bean(name="namedFile")
    public File namedFile() {
        File namedFile = new File("namedFile.txt");
        return namedFile;
    }
}

O teste de integraçãoQualifierResourceInjectionTest será usado para demonstrar a resolução de dependência de correspondência por qualificador. Nesse cenário, uma dependência específica do bean precisa ser injetada em cada variável de referência:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceQualifier.class)
public class QualifierResourceInjectionIntegrationTest {

    @Resource
    private File dependency1;

    @Resource
    private File dependency2;

    @Test
    public void givenResourceAnnotation_WhenField_ThenDependency1Valid(){
        assertNotNull(dependency1);
        assertEquals("defaultFile.txt", dependency1.getName());
    }

    @Test
    public void givenResourceQualifier_WhenField_ThenDependency2Valid(){
        assertNotNull(dependency2);
        assertEquals("namedFile.txt", dependency2.getName());
    }
}

Execute o teste de integração e umorg.springframework.beans.factory.NoUniqueBeanDefinitionException é lançado. Essa exceção é lançada porque o contexto do aplicativo encontrou duas definições de bean do tipoFile, e está confuso quanto a qual bean deve resolver a dependência.

Para resolver esse problema, consulte as linhas 7 a 10 do teste de integraçãoQualifierResourceInjectionTest:

@Resource
private File dependency1;

@Resource
private File dependency2;

e adicione as seguintes linhas de código:

@Qualifier("defaultFile")

@Qualifier("namedFile")

para que o bloco de código tenha a seguinte aparência:

@Resource
@Qualifier("defaultFile")
private File dependency1;

@Resource
@Qualifier("namedFile")
private File dependency2;

Execute o teste de integração novamente, desta vez ele deve passar. O objetivo deste teste foi demonstrar que mesmo se houver vários beans definidos em um contexto de aplicativo, a anotação@Qualifier elimina qualquer confusão, permitindo que dependências específicas sejam injetadas em uma classe.

2.2. Injeção de Setter

Os caminhos de execução adotados ao injetar dependências em um campo são aplicáveis ​​à injeção baseada em setter.

2.2.1. Corresponder por Nome

A única diferença é que o teste de integraçãoMethodResourceInjectionTest tem um método setter:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceNameType.class)
public class MethodResourceInjectionIntegrationTest {

    private File defaultFile;

    @Resource(name="namedFile")
    protected void setDefaultFile(File defaultFile) {
        this.defaultFile = defaultFile;
    }

    @Test
    public void givenResourceAnnotation_WhenSetter_ThenDependencyValid(){
        assertNotNull(defaultFile);
        assertEquals("namedFile.txt", defaultFile.getName());
    }
}

Resolver dependências por injeção de setter é feito anotando o método de setter correspondente de uma variável de referência. Passe o nome da dependência do bean como um valor de atributo para a anotação@Resource:

private File defaultFile;

@Resource(name="namedFile")
protected void setDefaultFile(File defaultFile) {
    this.defaultFile = defaultFile;
}

A dependência do beannamedFile será reutilizada neste exemplo. O nome do bean e o valor do atributo correspondente devem corresponder.

Execute o teste de integração como está e ele passará.

Para ver se a dependência foi realmente resolvida pelo caminho de execução match-by-name, altere o valor do atributo passado para a anotação@Resource para um valor de sua escolha e execute o teste novamente. Desta vez, o teste falhará comNoSuchBeanDefinitionException.

2.2.2. Corresponder por tipo

Para demonstrar a execução baseada em setter, correspondência por tipo, usaremos o teste de integraçãoMethodByTypeResourceTest:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceNameType.class)
public class MethodByTypeResourceIntegrationTest {

    private File defaultFile;

    @Resource
    protected void setDefaultFile(File defaultFile) {
        this.defaultFile = defaultFile;
    }

    @Test
    public void givenResourceAnnotation_WhenSetter_ThenValidDependency(){
        assertNotNull(defaultFile);
        assertEquals("namedFile.txt", defaultFile.getName());
    }
}

Execute este teste como está e ele passará.

Para verificar se a dependênciaFile foi realmente resolvida pelo caminho de execução de correspondência por tipo, altere o tipo de classe da variáveldefaultFile para outro tipo de classe comoString. Execute o teste de integraçãoMethodByTypeResourceTest novamente e, desta vez, umNoSuchBeanDefinitionException será lançado.

A exceção verifica se a correspondência por tipo foi realmente usada para resolver a dependência deFile. ONoSuchBeanDefinitionException confirma que o nome da variável de referência não precisa corresponder ao nome do bean. Em vez disso, a resolução de dependência depende do tipo de classe do bean correspondendo ao tipo de classe da variável de referência.

2.2.3. Jogo por Qualificador

O teste de integraçãoMethodByQualifierResourceTest será usado para demonstrar o caminho de execução match-by-qualifier:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestResourceQualifier.class)
public class MethodByQualifierResourceIntegrationTest {

    private File arbDependency;
    private File anotherArbDependency;

    @Test
    public void givenResourceQualifier_WhenSetter_ThenValidDependencies(){
      assertNotNull(arbDependency);
        assertEquals("namedFile.txt", arbDependency.getName());
        assertNotNull(anotherArbDependency);
        assertEquals("defaultFile.txt", anotherArbDependency.getName());
    }

    @Resource
    @Qualifier("namedFile")
    public void setArbDependency(File arbDependency) {
        this.arbDependency = arbDependency;
    }

    @Resource
    @Qualifier("defaultFile")
    public void setAnotherArbDependency(File anotherArbDependency) {
        this.anotherArbDependency = anotherArbDependency;
    }
}

O objetivo deste teste é demonstrar que mesmo se várias implementações de bean de um tipo específico forem definidas em um contexto de aplicativo, uma anotação@Qualifier pode ser usada junto com a anotação@Resource para resolver uma dependência.

Semelhante à injeção de dependência baseada em campo, se houver vários beans definidos em um contexto de aplicativo, umNoUniqueBeanDefinitionException é lançado se nenhuma anotação@Qualifier for usada para especificar qual bean deve ser usado para resolver dependências.

3. A anotação@Inject

A anotação@Inject pertence à coleção de anotaçõesJSR-330. Esta anotação possui os seguintes caminhos de execução, listados por precedência:

  1. Corresponder por tipo

  2. Jogo por Qualificador

  3. Corresponder por Nome

Esses caminhos de execução são aplicáveis ​​ao setter e à injeção de campo. Para acessar a anotação@Inject, a bibliotecajavax.inject deve ser declarada como uma dependência Gradle ou Maven.

Para Gradle:

testCompile group: 'javax.inject', name: 'javax.inject', version: '1'

Para Maven:


    javax.inject
    javax.inject
    1

3.1. Injeção em Campo

3.1.1. Corresponder por tipo

O exemplo de teste de integração será modificado para usar outro tipo de dependência, ou seja, a classeArbitraryDependency. A dependência de classeArbitraryDependency serve meramente como uma dependência simples e não tem mais significado. Está listado da seguinte forma:

@Component
public class ArbitraryDependency {

    private final String label = "Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

O teste de integraçãoFieldInjectTest em questão está listado da seguinte forma:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestInjectType.class)
public class FieldInjectIntegrationTest {

    @Inject
    private ArbitraryDependency fieldInjectDependency;

    @Test
    public void givenInjectAnnotation_WhenOnField_ThenValidDependency(){
        assertNotNull(fieldInjectDependency);
        assertEquals("Arbitrary Dependency",
          fieldInjectDependency.toString());
    }
}

Ao contrário da anotação@Resource, que resolve dependências por nome primeiro; o comportamento padrão da anotação@Inject resolve dependências por tipo.

Isso significa que, mesmo que o nome da variável de referência de classe seja diferente do nome do bean, a dependência ainda será resolvida, desde que o bean seja definido no contexto do aplicativo. Observe como o nome da variável de referência no seguinte teste:

@Inject
private ArbitraryDependency fieldInjectDependency;

difere do nome do bean configurado no contexto do aplicativo:

@Bean
public ArbitraryDependency injectDependency() {
    ArbitraryDependency injectDependency = new ArbitraryDependency();
    return injectDependency;
}

e quando o teste é executado, ele pode resolver a dependência.

3.1.2. Jogo por Qualificador

Mas e se houver várias implementações de um tipo de classe específico e uma certa classe exigir um bean específico? Vamos modificar o exemplo de teste de integração para que outra dependência seja necessária.

Neste exemplo, criamos uma subclasse da classeArbitraryDependency, usada no exemplo de correspondência por tipo, para criar a classeAnotherArbitraryDependency:

public class AnotherArbitraryDependency extends ArbitraryDependency {

    private final String label = "Another Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

O objetivo de cada caso de teste é garantir que cada dependência seja injetada corretamente em cada variável de referência:

@Inject
private ArbitraryDependency defaultDependency;

@Inject
private ArbitraryDependency namedDependency;

O teste de integraçãoFieldQualifierInjectTest usado para demonstrar a correspondência por qualificador é listado como segue:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestInjectQualifier.class)
public class FieldQualifierInjectIntegrationTest {

    @Inject
    private ArbitraryDependency defaultDependency;

    @Inject
    private ArbitraryDependency namedDependency;

    @Test
    public void givenInjectQualifier_WhenOnField_ThenDefaultFileValid(){
        assertNotNull(defaultDependency);
        assertEquals("Arbitrary Dependency",
          defaultDependency.toString());
    }

    @Test
    public void givenInjectQualifier_WhenOnField_ThenNamedFileValid(){
        assertNotNull(defaultDependency);
        assertEquals("Another Arbitrary Dependency",
          namedDependency.toString());
    }
}

Se houver várias implementações de uma classe específica em um contexto de aplicativo e o teste de integraçãoFieldQualifierInjectTest tenta injetar as dependências da maneira listada abaixo:

@Inject
private ArbitraryDependency defaultDependency;

@Inject
private ArbitraryDependency namedDependency;

aNoUniqueBeanDefinitionException será lançado.

Lançar essa exceção é a maneira do Spring Framework de apontar que existem várias implementações de uma determinada classe e não se sabe qual delas usar. Para elucidar a confusão, vá para as linhas 7 e 10 do teste de integraçãoFieldQualifierInjectTest:

@Inject
private ArbitraryDependency defaultDependency;

@Inject
private ArbitraryDependency namedDependency;

passe o nome do bean necessário para a anotação@Qualifier, que é usada junto com a anotação@Inject. O bloco de código agora terá a seguinte aparência:

@Inject
@Qualifier("defaultFile")
private ArbitraryDependency defaultDependency;

@Inject
@Qualifier("namedFile")
private ArbitraryDependency namedDependency;

A anotação@Qualifier espera uma correspondência estrita ao receber um nome de bean. Certifique-se de que o nome do bean seja passado paraQualifier corretamente, caso contrário, umNoUniqueBeanDefinitionException será lançado. Execute o teste novamente e, desta vez, deve passar.

3.1.3. Corresponder por Nome

O teste de integraçãoFieldByNameInjectTest usado para demonstrar correspondência por nome é semelhante ao caminho de execução de correspondência por tipo. A única diferença agora é que um bean específico é necessário, em oposição a um tipo específico. Neste exemplo, subclassificamos a classeArbitraryDependency novamente para produzir a classeYetAnotherArbitraryDependency:

public class YetAnotherArbitraryDependency extends ArbitraryDependency {

    private final String label = "Yet Another Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

Para demonstrar o caminho de execução da correspondência por nome, usaremos o seguinte teste de integração:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestInjectName.class)
public class FieldByNameInjectIntegrationTest {

    @Inject
    @Named("yetAnotherFieldInjectDependency")
    private ArbitraryDependency yetAnotherFieldInjectDependency;

    @Test
    public void givenInjectQualifier_WhenSetOnField_ThenDependencyValid(){
        assertNotNull(yetAnotherFieldInjectDependency);
        assertEquals("Yet Another Arbitrary Dependency",
          yetAnotherFieldInjectDependency.toString());
    }
}

O contexto do aplicativo é listado da seguinte maneira:

@Configuration
public class ApplicationContextTestInjectName {

    @Bean
    public ArbitraryDependency yetAnotherFieldInjectDependency() {
        ArbitraryDependency yetAnotherFieldInjectDependency =
          new YetAnotherArbitraryDependency();
        return yetAnotherFieldInjectDependency;
    }
}

Execute o teste de integração como está e ele passará.

Para verificar se a dependência foi realmente injetada pelo caminho de execução match-by-name, altere o valoryetAnotherFieldInjectDependency, que foi passado para a anotação@Named para outro nome de sua escolha. Execute o teste novamente - desta vez, umNoSuchBeanDefinitionException é lançado.

3.2. Injeção de Setter

A injeção baseada em setter para a anotação@Inject é semelhante à abordagem usada para injeção baseada em setter@Resource. Em vez de anotar a variável de referência, o método setter correspondente é anotado. Os caminhos de execução seguidos pela injeção de dependência baseada em campo também se aplicam à injeção baseada em setter.

4. A anotação@Autowired

O comportamento da anotação@Autowired é semelhante ao da anotação@Inject. A única diferença é que a anotação@Autowired faz parte do framework Spring. Esta anotação tem os mesmos caminhos de execução que a anotação@Inject, listados em ordem de precedência:

  1. Corresponder por tipo

  2. Jogo por Qualificador

  3. Corresponder por Nome

Esses caminhos de execução são aplicáveis ​​ao setter e à injeção de campo.

4.1. Injeção em Campo

4.1.1. Corresponder por tipo

O exemplo de teste de integração usado para demonstrar o caminho de execução de correspondência por tipo de@Autowired será semelhante ao teste usado para demonstrar o caminho de execução de correspondência por tipo de@Inject. O teste de integraçãoFieldAutowiredTest usado para demonstrar correspondência por tipo usando a anotação@Autowired é listado como segue:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestAutowiredType.class)
public class FieldAutowiredIntegrationTest {

    @Autowired
    private ArbitraryDependency fieldDependency;

    @Test
    public void givenAutowired_WhenSetOnField_ThenDependencyResolved() {
        assertNotNull(fieldDependency);
        assertEquals("Arbitrary Dependency", fieldDependency.toString());
    }
}

O contexto do aplicativo para este teste de integração está listado da seguinte maneira:

@Configuration
public class ApplicationContextTestAutowiredType {

    @Bean
    public ArbitraryDependency autowiredFieldDependency() {
        ArbitraryDependency autowiredFieldDependency =
          new ArbitraryDependency();
        return autowiredFieldDependency;
    }
}

O objetivo do teste de integração é demonstrar que a correspondência por tipo tem precedência sobre os outros caminhos de execução. Observe na linha 8 do teste de integraçãoFieldAutowiredTest como o nome da variável de referência:

@Autowired
private ArbitraryDependency fieldDependency;

é diferente do nome do bean no contexto do aplicativo:

@Bean
public ArbitraryDependency autowiredFieldDependency() {
    ArbitraryDependency autowiredFieldDependency =
      new ArbitraryDependency();
    return autowiredFieldDependency;
}

Quando o teste é executado, ele passa.

Para confirmar que a dependência foi realmente resolvida usando o caminho de execução match-by-type, altere o tipo da variável de referênciafieldDependency e execute o teste de integração novamente. Desta vez, o teste de integraçãoFieldAutowiredTest deve falhar, com umNoSuchBeanDefinitionException sendo lançado. Isso verifica se a correspondência por tipo foi usada para resolver a dependência.

4.1.2. Jogo por Qualificador

E se confrontado com uma situação em que várias implementações de bean foram definidas no contexto do aplicativo, como a listada abaixo:

@Configuration
public class ApplicationContextTestAutowiredQualifier {

    @Bean
    public ArbitraryDependency autowiredFieldDependency() {
        ArbitraryDependency autowiredFieldDependency =
          new ArbitraryDependency();
        return autowiredFieldDependency;
    }

    @Bean
    public ArbitraryDependency anotherAutowiredFieldDependency() {
        ArbitraryDependency anotherAutowiredFieldDependency =
          new AnotherArbitraryDependency();
        return anotherAutowiredFieldDependency;
    }
}

Se o teste de integraçãoFieldQualifierAutowiredTest, listado abaixo, for executado:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestAutowiredQualifier.class)
public class FieldQualifierAutowiredIntegrationTest {

    @Autowired
    private ArbitraryDependency fieldDependency1;

    @Autowired
    private ArbitraryDependency fieldDependency2;

    @Test
    public void givenAutowiredQualifier_WhenOnField_ThenDep1Valid(){
        assertNotNull(fieldDependency1);
        assertEquals("Arbitrary Dependency", fieldDependency1.toString());
    }

    @Test
    public void givenAutowiredQualifier_WhenOnField_ThenDep2Valid(){
        assertNotNull(fieldDependency2);
        assertEquals("Another Arbitrary Dependency",
          fieldDependency2.toString());
    }
}

aNoUniqueBeanDefinitionException será lançado.

A exceção é devido à ambiguidade causada pelos dois beans definidos no contexto do aplicativo. O Spring Framework não sabe qual dependência de bean deve ser conectada automaticamente a qual variável de referência. Resolva esse problema adicionando a anotação@Qualifier às linhas 7 e 10 do teste de integraçãoFieldQualifierAutowiredTest:

@Autowired
private FieldDependency fieldDependency1;

@Autowired
private FieldDependency fieldDependency2;

para que o bloco de código tenha a seguinte aparência:

@Autowired
@Qualifier("autowiredFieldDependency")
private FieldDependency fieldDependency1;

@Autowired
@Qualifier("anotherAutowiredFieldDependency")
private FieldDependency fieldDependency2;

Execute o teste novamente e desta vez ele será aprovado.

4.1.3. Corresponder por Nome

O mesmo cenário de teste de integração será usado para demonstrar o caminho de execução de correspondência por nome ao usar a anotação@Autowired para injetar uma dependência de campo. Ao autowiring dependências por nome, a anotação@ComponentScan deve ser usada com o contexto do aplicativo,ApplicationContextTestAutowiredName:

@Configuration
@ComponentScan(basePackages={"com.example.dependency"})
    public class ApplicationContextTestAutowiredName {
}

A anotação@ComponentScan pesquisará pacotes de classes Java que foram anotadas com a anotação@Component. Por exemplo, no contexto do aplicativo, o pacotecom.example.dependency será verificado em busca de classes que foram anotadas com a anotação@Component. Nesse cenário, o Spring Framework deve detectar a classeArbitraryDependency, que tem a anotação@Component:

@Component(value="autowiredFieldDependency")
public class ArbitraryDependency {

    private final String label = "Arbitrary Dependency";

    public String toString() {
        return label;
    }
}

O valor do atributo,autowiredFieldDependency, passado para a anotação@Component, informa ao Spring Framework que a classeArbitraryDependency é um componente denominadoautowiredFieldDependency. Para que a anotação@Autowired resolva dependências por nome, o nome do componente deve corresponder ao nome do campo definido no teste de integraçãoFieldAutowiredNameTest; consulte a linha 8:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  loader=AnnotationConfigContextLoader.class,
  classes=ApplicationContextTestAutowiredName.class)
public class FieldAutowiredNameIntegrationTest {

    @Autowired
    private ArbitraryDependency autowiredFieldDependency;

    @Test
    public void givenAutowiredAnnotation_WhenOnField_ThenDepValid(){
        assertNotNull(autowiredFieldDependency);
        assertEquals("Arbitrary Dependency",
          autowiredFieldDependency.toString());
    }
}

Quando o teste de integraçãoFieldAutowiredNameTest for executado como está, ele será aprovado.

Mas como sabemos que a anotação@Autowired realmente invocou o caminho de execução match-by-name? Altere o nome da variável de referênciaautowiredFieldDependency para outro nome de sua escolha e execute o teste novamente.

Desta vez, o teste falhará e umNoUniqueBeanDefinitionException é lançado. Uma verificação semelhante seria alterar o valor do atributo@Component,autowiredFieldDependency, para outro valor de sua escolha e executar o teste novamente. UmNoUniqueBeanDefinitionException também será lançado.

Essa exceção prova que, se um nome de bean incorreto for usado, nenhum bean válido será encontrado. Portanto, o caminho de execução da correspondência por nome foi chamado.

4.2. Injeção de Setter

A injeção baseada em setter para a anotação@Autowired é semelhante à abordagem demonstrada para a injeção baseada em setter@Resource. Em vez de anotar a variável de referência com a anotação@Inject, o setter correspondente é anotado. Os caminhos de execução seguidos pela injeção de dependência baseada em campo também se aplicam à injeção baseada em setter.

5. Aplicando Estas Anotações

Isso levanta a questão: qual anotação deve ser usada e em que circunstâncias? A resposta para essas perguntas depende do cenário de design enfrentado pelo aplicativo em questão e de como o desenvolvedor deseja alavancar o polimorfismo com base nos caminhos de execução padrão de cada anotação.

5.1. Uso amplo de aplicativos de singletons por meio de polimorfismo

Se o design for tal que os comportamentos do aplicativo sejam baseados em implementações de uma interface ou classe abstrata, e esses comportamentos forem usados ​​em todo o aplicativo, use a anotação@Inject ou@Autowired.

O benefício dessa abordagem é que, quando o aplicativo é atualizado, ou um patch precisa ser aplicado para corrigir um erro; as classes podem ser trocadas com um impacto negativo mínimo no comportamento geral do aplicativo. Nesse cenário, o caminho de execução padrão primário é correspondido por tipo.

5.2. Configuração refinada do comportamento do aplicativo por meio do polimorfismo

Se o design for tal que o aplicativo tenha um comportamento complexo, cada comportamento será baseado em diferentes interfaces / classes abstratas, e o uso de cada uma dessas implementações varia no aplicativo, então use a anotação@Resource. Nesse cenário, o caminho de execução padrão primário é a correspondência por nome.

5.3. A injeção de dependência deve ser tratada exclusivamente pela plataforma Java EE

Se houver um mandato de design para todas as dependências serem injetadas pela plataforma Java EE e não Spring, então a escolha é entre a anotação@Resource e a anotação@Inject. Você deve restringir a decisão final entre as duas anotações, com base no caminho de execução padrão necessário.

5.4. A injeção de dependência deve ser tratada exclusivamente pelo Spring Framework

Se o mandato for para que todas as dependências sejam tratadas pelo Spring Framework, a única escolha é a anotação@Autowired.

5.5. Resumo da discussão

A tabela abaixo resume a discussão.

Cenário

@Recurso

@Injetar

@Autowired

Uso de singletons em todo o aplicativo por meio de polimorfismo

Configuração refinada do comportamento do aplicativo por meio de polimorfismo

A injeção de dependência deve ser tratada exclusivamente pela plataforma Java EE

A injeção de dependência deve ser tratada exclusivamente pelo Spring Framework

6. Conclusão

O artigo teve como objetivo fornecer uma visão mais profunda do comportamento de cada anotação. A compreensão de como cada anotação se comporta contribuirá para um melhor design e manutenção geral do aplicativo.

O código usado durante a discussão pode ser encontrado emGitHub.