Comprender el uso Spring @Autowired

309

Estoy leyendo la documentación de referencia de Spring 3.0.x para comprender la anotación Spring Autowired:

3.9.2 @Autowired y @Inject

No puedo entender los siguientes ejemplos. ¿Necesitamos hacer algo en el XML para que funcione?

EJEMPLO 1

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

EJEMPLO 2

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

¿Cómo se pueden conectar automáticamente las dos clases implementando la misma interfaz y usando la misma clase?

Ejemplo:

class Red implements Color
class Blue implements Color

class myMainClass{
    @Autowired 
    private Color color;

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

¿Qué método de diseño se llamará? ¿Cómo me aseguro de que se llamará al método de diseño de la clase Roja y no al Azul?

NewQueries
fuente

Respuestas:

542

TL; DR

La anotación @Autowired le ahorra la necesidad de realizar el cableado usted mismo en el archivo XML (o de cualquier otra manera) y simplemente encuentra lo que necesita inyectarse en dónde y lo hace por usted.

Explicación completa

La @Autowiredanotación le permite omitir configuraciones en otros lugares de qué inyectar y simplemente lo hace por usted. Suponiendo que su paquete com.mycompany.moviestiene que poner esta etiqueta en su XML (archivo de contexto de la aplicación):

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

Esta etiqueta hará un escaneo automático. Suponiendo que cada clase que tiene que convertirse en un bean se anota con una anotación correcta como @Component(para bean simple) o @Controller(para un control de servlet) o @Repository(para DAOclases) y estas clases están en algún lugar debajo del paquete com.mycompany.movies, Spring encontrará todo esto y creará un frijol para cada uno Esto se realiza en 2 escaneos de las clases: la primera vez que solo busca clases que necesitan convertirse en un bean y mapea las inyecciones que necesita estar haciendo, y en el segundo escaneo inyecta los beans. Por supuesto, puede definir sus beans en el archivo XML más tradicional o con una clase @Configuration (o cualquier combinación de los tres).

La @Autowiredanotación le dice a Spring dónde debe ocurrir una inyección. Si lo pones en un método setMovieFinder, entiende (por el prefijo set+ la @Autowiredanotación) que se necesita inyectar un bean. En la segunda exploración, Spring busca un bean de tipo MovieFindery, si lo encuentra, lo inyecta a este método. Si encuentra dos de esos frijoles, obtendrá un Exception. Para evitarlo Exception, puede usar la @Qualifieranotación y decirle cuál de los dos frijoles inyectar de la siguiente manera:

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

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

O si prefiere declarar los beans en su XML, se vería así:

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

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

En la @Autowireddeclaración, también debe agregar el @Qualifierpara decir cuál de los dos granos de color para inyectar:

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

Si no desea usar dos anotaciones (la @Autowiredy @Qualifier) puede @Resourcecombinar estas dos:

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

El @Resource(puede leer algunos datos adicionales al respecto en el primer comentario de esta respuesta) le ahorra el uso de dos anotaciones y en su lugar solo usa una.

Solo agregaré dos comentarios más:

  1. Una buena práctica sería usarlo en @Injectlugar de hacerlo @Autowiredporque no es específico de Spring y es parte del JSR-330estándar .
  2. Otra buena práctica sería colocar @Inject/ @Autowireden un constructor en lugar de un método. Si lo pones en un constructor, puedes validar que los beans inyectados no son nulos y fallan rápidamente cuando intentas iniciar la aplicación y evitar NullPointerExceptioncuando necesitas usar el bean.

Actualización : para completar la imagen, creé una nueva pregunta sobre la @Configurationclase.

Avi
fuente
66
Solo para completar su increíble respuesta: '@Resource' es parte del estándar JSR-250 y tiene una semántica adicional además de la simple inyección (como ha dicho '@Autowired' es de Spring; y '@Inject' es parte del JSR-330) :)
Ignacio Rubio
Si MovieFinderes una interfaz, y tenemos un bean para MovieFinderImpl(bean id = movieFinder), ¿Spring lo inyectará automáticamente por tipo o por nombre?
Jaskey
@jaskey: depende de si lo usas @Qualifier. Si lo hace, por nombre, si no, por tipo. Por tipo funcionaría solo si tiene un solo bean de tipo MovieFinderen su contexto. Más de 1 llevaría a una excepción.
Avi
@Avi, impresionante respuesta. Pero no entiendo cómo funciona la @Autowiredanotación en el preparemétodo del Ejemplo 2 . Está inicializando MovieRecommenderpero, técnicamente, NO es un setter.
Karan Chadha
@KaranChadha: @Autowiredtambién funciona para constructores. Encuentra las dependencias requeridas y las inyecta al constructor.
Avi
21

Nada en el ejemplo dice que las "clases implementan la misma interfaz". MovieCataloges un tipo y CustomerPreferenceDaoes otro tipo. La primavera puede distinguirlos fácilmente.

En Spring 2.x, el cableado de los beans se realizó principalmente a través de ID o nombres de beans. Spring 3.x todavía lo admite, pero a menudo, tendrá una instancia de un bean con un cierto tipo: la mayoría de los servicios son singletons. Crear nombres para esos es tedioso. Entonces Spring comenzó a admitir "autowire by type".

Lo que muestran los ejemplos son varias formas que puede usar para inyectar beans en campos, métodos y constructores.

El XML ya contiene toda la información que Spring necesita, ya que debe especificar el nombre de clase completo en cada bean. Sin embargo, debe tener un poco de cuidado con las interfaces:

Este cableado automático fallará:

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

Como Java no mantiene los nombres de los parámetros en el código de bytes, Spring ya no puede distinguir entre los dos beans. La solución es usar @Qualifier:

 @Autowired
 public void prepare( @Qualifier("bean1") Interface1 bean1,
     @Qualifier("bean2")  Interface1 bean2 ) { ... }
Aaron Digulla
fuente
@AaronDigulla Eso estuvo bien. Sin embargo, quiero saber cómo se llama a la función prepare, ¿qué parámetros se utilizarán para llamar a esta función?
Nguyen Quang Anh
@NguyenQuangAnh No estoy llamando al método, Spring lo hará cuando se cree el bean. Esto sucede exactamente cuando @Autowiredse inyectan los campos. Spring verá que se necesitan parámetros y utilizará las mismas reglas que se usan para la inyección de campo para encontrar los parámetros.
Aaron Digulla
5

Sí, puede configurar el archivo xml de contexto de servlet Spring para definir sus beans (es decir, clases), de modo que pueda realizar la inyección automática por usted. Sin embargo, tenga en cuenta que debe hacer otras configuraciones para tener Spring en funcionamiento y la mejor manera de hacerlo es seguir un tutorial desde cero.

Una vez que haya configurado su Spring probablemente, puede hacer lo siguiente en su archivo xml de contexto de servlet Spring para que el Ejemplo 1 anterior funcione ( reemplace el nombre del paquete de com.movies por cuál es el verdadero nombre del paquete y si se trata de un tercero) clase, luego asegúrese de que el archivo jar apropiado esté en el classpath):

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

o si la clase MovieFinder tiene un constructor con un valor primitivo, entonces podría hacer algo como esto,

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

o si la clase MovieFinder tiene un constructor que espera otra clase, entonces podrías hacer algo como esto,

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

... donde ' otherBeanRef ' es otro bean que tiene una referencia a la clase esperada.

Cem Sultan
fuente
44
Al definir todo el cableado en el XML simplemente se pierde toda la idea de@Autowired
Avi