Понимание использования Spring @Autowired

309

Я читаю справочную документацию по Spring 3.0.x, чтобы понять аннотацию Spring Autowired:

3.9.2 @Autowired и @Inject

Я не могу понять приведенные ниже примеры. Нужно ли что-то делать в XML, чтобы это работало?

Пример 1

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

Пример 2

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

Как можно автоматически связать два класса, реализуя один и тот же интерфейс и используя один и тот же класс?

Пример:

class Red implements Color
class Blue implements Color

class myMainClass{
    @Autowired 
    private Color color;

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

Какой метод проектирования будет называться? Как сделать так, чтобы вызывался метод дизайна класса Red, а не Blue?

NewQueries
источник

Ответы:

542

TL; DR

Аннотация @Autowired избавляет вас от необходимости самостоятельно выполнять разводку в файле XML (или любым другим способом) и просто находит для вас, что нужно вводить, куда и делает это для вас.

Полное объяснение

@AutowiredАннотаций позволяет пропускать конфигурации в другом месте , что вводить и просто делает это для вас. Предполагая, что ваш пакет - com.mycompany.moviesвы должны поместить этот тег в ваш XML (файл контекста приложения):

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

Этот тег выполнит автоматическое сканирование. Предполагая, что каждый класс, который должен стать компонентом, помечается правильной аннотацией, например @Component(для простого компонента) или @Controller(для элемента управления сервлета) или @Repository(для DAOклассов), и эти классы находятся где-то в пакете com.mycompany.movies, Spring найдет все это и создаст боб для каждого. Это делается в 2 сканированиях классов - в первый раз он просто ищет классы, которые должны стать бином, и отображает инъекции, которые он должен делать, а во втором сканировании он вводит бины. Конечно, вы можете определить ваши bean-компоненты в более традиционном XML-файле или с помощью класса @Configuration (или любой их комбинации).

@AutowiredАннотаций сообщает Spring , где инъекции должно произойти. Если вы помещаете его в метод, setMovieFinderон понимает (по префиксу set+ @Autowiredаннотации), что бин должен быть введен. Во втором сканировании Spring ищет bean-компонент типа MovieFinder, и если он находит такой bean-компонент, он внедряет его в этот метод. Если он найдет два таких боба, вы получите Exception. Чтобы избежать этого Exception, вы можете использовать @Qualifierаннотацию и сообщить ей, какой из двух bean-компонентов нужно внедрить, следующим образом:

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

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

Или, если вы предпочитаете объявлять компоненты в вашем XML, это будет выглядеть примерно так:

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

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

В @Autowiredобъявлении также необходимо добавить, @Qualifierчтобы указать, какой из двух цветных бобов нужно внедрить:

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

Если вы не хотите использовать две аннотации ( @Autowiredи @Qualifier), вы можете использовать их @Resourceдля объединения:

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

@Resource(Вы можете прочитать некоторые дополнительные данные о нем в первый комментарий на этот ответ) избавляет вас использование двух аннотаций и вместо того, чтобы использовать только один.

Я просто добавлю еще два комментария:

  1. Хорошей практикой будет использование @Injectвместо того, @Autowiredпотому что это не зависит от Spring и является частью JSR-330стандарта .
  2. Еще одна хорошая практика - помещать @Inject/ @Autowiredна конструктор вместо метода. Если вы поместите его в конструктор, вы можете проверить, что введенные bean-компоненты не равны NULL и быстро перестают работать, когда вы пытаетесь запустить приложение и избегать того, NullPointerExceptionкогда вам действительно нужно использовать bean-компонент.

Обновление : чтобы завершить картину, я создал новый вопрос о @Configurationклассе.

Avi
источник
6
Просто завершите ваш замечательный ответ: «@Resource» является частью стандарта JSR-250 и имеет дополнительную семантику сверх простого внедрения (как вы сказали, «@Autowired» происходит из Spring; а «@Inject» является частью JSR-330) :)
Игнасио Рубио
Если MovieFinderэто интерфейс, и у нас есть компонент для MovieFinderImpl(bean id = movieFinder), Spring автоматически внедрит его по типу или по имени?
Jaskey
@jaskey - это зависит от того, используете ли вы @Qualifier. Если вы делаете - по имени, если нет - по типу. По типу будет работать, только если у вас есть только один тип компонента MovieFinderв вашем контексте. Более 1 приведет к исключению.
Ави
@Avi, Отличный ответ. Но я не понимаю, как @Autowiredработает аннотация к prepareметоду в Примере 2 . Это инициализация, MovieRecommenderно, технически, это НЕ сеттер.
Каран Чадха
@KaranChadha - @Autowiredтакже работает для конструкторов. Он находит необходимые зависимости и вставляет их в конструктор.
Ави
21

Ничто в примере не говорит о том, что «классы реализуют один и тот же интерфейс». MovieCatalogэто тип, а CustomerPreferenceDaoдругой тип. Весна может легко отличить их.

В Spring 2.x проводка bean-компонентов в основном происходила через идентификаторы или имена bean-компонентов. Это все еще поддерживается в Spring 3.x, но часто у вас будет один экземпляр компонента с определенным типом - большинство сервисов являются синглетонами. Создание названий для них утомительно. Поэтому Spring начал поддерживать «autowire по типу».

Примеры показывают различные способы, которыми вы можете использовать для внедрения bean-компонентов в поля, методы и конструкторы.

XML уже содержит всю информацию, которая нужна Spring, поскольку вы должны указать полное имя класса в каждом бине. Вы должны быть немного осторожнее с интерфейсами, хотя:

Эта автопроводка потерпит неудачу:

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

Поскольку Java не хранит имена параметров в байт-коде, Spring больше не может различать два bean-компонента. Исправление заключается в использовании @Qualifier:

 @Autowired
 public void prepare( @Qualifier("bean1") Interface1 bean1,
     @Qualifier("bean2")  Interface1 bean2 ) { ... }
Аарон Дигулла
источник
@AaronDigulla Это было мило. Однако я хочу знать, как вы вызываете функцию prepare, какие параметры будут использоваться для вызова этой функции?
Нгуен Куанг Ан
@NguyenQuangAnh Я не вызываю метод, Spring сделает это при создании компонента. Это происходит именно тогда, когда @Autowiredполя вводятся. Затем Spring увидит, что параметры необходимы, и будет использовать те же правила, что и для ввода полей, чтобы найти параметры.
Аарон Дигулла,
5

Да, вы можете настроить XML-файл контекста сервлета Spring для определения ваших bean-компонентов (т. Е. Классов), чтобы он мог выполнять автоматическое внедрение для вас. Тем не менее, обратите внимание, что вам нужно выполнить другие конфигурации, чтобы запустить Spring и запустить его, и лучший способ сделать это - пройти курс обучения.

После того, как вы, вероятно, настроили свой Spring, вы можете сделать следующее в своем xml-файле контекста Spring для примера 1, приведенного выше, (пожалуйста, замените имя пакета com.movies на истинное имя пакета, и если это сторонняя версия class, затем убедитесь, что соответствующий файл jar находится в пути к классам):

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

или если класс MovieFinder имеет конструктор с примитивным значением, то вы могли бы что-то вроде этого,

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

или если класс MovieFinder имеет конструктор, ожидающий другой класс, то вы можете сделать что-то вроде этого,

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

... где « otherBeanRef » - это другой компонент, имеющий ссылку на ожидаемый класс.

Джем Султан
источник
4
Определение всей проводки в XML просто пропускает саму идею@Autowired
Avi