Noções básicas sobre o uso do Spring @Autowired

309

Estou lendo a documentação de referência do Spring 3.0.x para entender a anotação do Spring Autowired:

3.9.2 @Autowired e @Inject

Não consigo entender os exemplos abaixo. Precisamos fazer algo no XML para que funcione?

EXEMPLO 1

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

EXEMPLO 2

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
                    CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

Como as duas classes podem ser conectadas automaticamente, implementando a mesma interface e usando a mesma classe?

Exemplo:

class Red implements Color
class Blue implements Color

class myMainClass{
    @Autowired 
    private Color color;

    draw(){
        color.design(); 
    } 
}

Qual método de design será chamado? Como garantir que o método de design da classe Red seja chamado e não azul?

NewQueries
fonte

Respostas:

542

TL; DR

A anotação @Autowired poupa você da necessidade de fazer a fiação sozinha no arquivo XML (ou de qualquer outra maneira) e apenas descobre o que precisa ser injetado onde e o faz.

Explicação completa

A @Autowiredanotação permite que você pule as configurações em outro lugar do que injetar e apenas faz isso por você. Supondo que seu pacote seja necessário, com.mycompany.moviesvocê deve colocar essa tag no seu XML (arquivo de contexto do aplicativo):

<context:component-scan base-package="com.mycompany.movies" />

Essa tag fará uma verificação automática. Supondo que cada classe que precisa se tornar um bean seja anotada com uma anotação correta, como @Component(para bean simples) ou @Controller(para um controle de servlet) ou @Repository(para DAOclasses) e essas classes estiverem em algum lugar no pacote com.mycompany.movies, o Spring encontrará tudo isso e criará um feijão para cada um. Isso é feito em duas varreduras das classes - na primeira vez, apenas procura por classes que precisam se tornar um bean e mapeia as injeções necessárias e, na segunda varredura, ele injeta os beans. Obviamente, você pode definir seus beans no arquivo XML mais tradicional ou com um @Configuration classe (ou qualquer combinação das três).

A @Autowiredanotação informa ao Spring onde uma injeção precisa ocorrer. Se você o colocar em um método, setMovieFinderele entenderá (pelo prefixo set+ a @Autowiredanotação) que um bean precisa ser injetado. Na segunda varredura, o Spring procura um bean do tipo MovieFindere, se o encontrar, o injeta nesse método. Se encontrar dois desses feijões, você receberá um Exception. Para evitar isso Exception, você pode usar a @Qualifieranotação e dizer qual dos dois beans injetar da seguinte maneira:

@Qualifier("redBean")
class Red implements Color {
   // Class code here
}

@Qualifier("blueBean")
class Blue implements Color {
   // Class code here
}

Ou se você preferir declarar os beans em seu XML, seria algo parecido com isto:

<bean id="redBean" class="com.mycompany.movies.Red"/>

<bean id="blueBean" class="com.mycompany.movies.Blue"/>

Na @Autowireddeclaração, você também precisa adicionar o @Qualifierpara informar qual dos dois beans coloridos injetar:

@Autowired
@Qualifier("redBean")
public void setColor(Color color) {
  this.color = color;
}

Se você não quiser usar duas anotações (the @Autowirede @Qualifier), poderá usá @Resource-las para combinar essas duas:

@Resource(name="redBean")
public void setColor(Color color) {
  this.color = color;
}

O @Resource(você pode ler alguns dados extras sobre isso no primeiro comentário sobre esta resposta) poupa o uso de duas anotações e, em vez disso, usa apenas uma.

Vou apenas adicionar mais dois comentários:

  1. A boa prática seria usar em @Injectvez de, @Autowiredporque não é específica da Primavera e faz parte do JSR-330padrão .
  2. Outra boa prática seria colocar o @Inject/ @Autowiredem um construtor em vez de em um método. Se você o colocar em um construtor, poderá validar que os beans injetados não são nulos e falham rapidamente ao tentar iniciar o aplicativo e evitar um NullPointerExceptionquando você realmente precisa usá-lo.

Atualização : Para completar a figura, criei uma nova pergunta sobre a @Configurationturma.

Avi
fonte
6
Apenas para completar sua incrível resposta: '@Resource' faz parte do padrão JSR-250 e possui semântica extra além da injeção simples (como você disse '@Autowired' é da Spring; e '@Inject' faz parte do JSR-330) :)
Ignacio Rubio
Se MovieFinderé uma interface e temos um bean para MovieFinderImpl(bean id = movieFinder), o Spring injeta automaticamente por tipo ou por nome?
Jaskey
@jaskey - depende se você usa @Qualifier. Se você fizer - por nome, se não - por tipo. Por tipo funcionaria apenas se você tiver apenas um bean do tipo MovieFinderem seu contexto. Mais de 1 levaria a uma exceção.
Avi
@ Avi, resposta impressionante. Mas eu não entendo como a @Autowiredanotação funciona no preparemétodo do Exemplo 2 . Está inicializando o MovieRecommender, mas, tecnicamente, NÃO é um levantador.
precisa
@KaranChadha - O @Autowiredtambém funciona para construtores. Ele encontra as dependências necessárias e as injeta no construtor.
Avi
21

Nada no exemplo diz que as "classes implementando a mesma interface". MovieCatalogé um tipo eCustomerPreferenceDao é outro tipo. A primavera pode diferenciá-los facilmente.

Na primavera 2.x, a fiação dos beans acontecia principalmente por meio de IDs ou nomes de beans. Isso ainda é suportado pelo Spring 3.x, mas muitas vezes você terá uma instância de um bean com um determinado tipo - a maioria dos serviços são singletons. Criar nomes para eles é entediante. Então, o Spring começou a suportar "autowire por tipo".

O que os exemplos mostram são várias maneiras que você pode usar para injetar beans em campos, métodos e construtores.

O XML já contém todas as informações que o Spring precisa, pois é necessário especificar o nome completo da classe em cada bean. Você precisa ter um pouco de cuidado com as interfaces:

Essa fiação automática falhará:

 @Autowired
 public void prepare( Interface1 bean1, Interface1 bean2 ) { ... }

Como o Java não mantém os nomes dos parâmetros no código de bytes, o Spring não pode mais distinguir entre os dois beans. A correção é usar @Qualifier:

 @Autowired
 public void prepare( @Qualifier("bean1") Interface1 bean1,
     @Qualifier("bean2")  Interface1 bean2 ) { ... }
Aaron Digulla
fonte
@AaronDigulla Isso foi legal. No entanto, eu quero saber como você chama a função prepare, quais parâmetros serão usados ​​para chamar essa função?
Nguyen Quang Anh
@NguyenQuangAnh Não estou chamando o método, o Spring fará isso quando o bean for criado. Isso acontece exatamente quando os @Autowiredcampos são injetados. O Spring verá que os parâmetros são necessários e usará as mesmas regras usadas para injeção de campo para encontrar os parâmetros.
Aaron Digulla
5

Sim, você pode configurar o arquivo xml de contexto do servlet Spring para definir seus beans (ou seja, classes), para que ele possa fazer a injeção automática para você. No entanto, observe que você precisa fazer outras configurações para ter o Spring instalado e funcionando e a melhor maneira de fazer isso é seguir um tutorial completo.

Depois que o Spring estiver configurado, provavelmente, você poderá fazer o seguinte no arquivo xml de contexto do servlet do Spring para o Exemplo 1 acima ( substitua o nome do pacote de com.movies pelo nome verdadeiro do pacote e se este é um terceiro classe, verifique se o arquivo jar apropriado está no caminho de classe):

<beans:bean id="movieFinder" class="com.movies.MovieFinder" />

ou se a classe MovieFinder tiver um construtor com um valor primitivo, você poderá algo assim,

<beans:bean id="movieFinder" class="com.movies.MovieFinder" >
    <beans:constructor-arg value="100" />
</beans:bean>

ou se a classe MovieFinder tiver um construtor esperando outra classe, você poderá fazer algo assim,

<beans:bean id="movieFinder" class="com.movies.MovieFinder" >
    <beans:constructor-arg ref="otherBeanRef" />
</beans:bean>

... onde ' otherBeanRef ' é outro bean que tem uma referência à classe esperada.

Cem Sultan
fonte
4
Definindo toda a fiação no XML apenas falta toda a ideia de@Autowired
Avi