Автопроводка двух bean-компонентов, реализующих один и тот же интерфейс - как установить bean-компонент по умолчанию в autowire?

138

Задний план:

У меня есть приложение Spring 2.5 / Java / Tomcat. Существует следующий компонент, который используется во всем приложении во многих местах.

public class HibernateDeviceDao implements DeviceDao

и следующий боб, который является новым:

public class JdbcDeviceDao implements DeviceDao

Первый компонент настроен так (все компоненты пакета включены)

<context:component-scan base-package="com.initech.service.dao.hibernate" />

Второй (новый) компонент настраивается отдельно

<bean id="jdbcDeviceDao" class="com.initech.service.dao.jdbc.JdbcDeviceDao">
    <property name="dataSource" ref="jdbcDataSource">
</bean>

Это приводит (конечно) к исключению при запуске сервера:

вложенным исключением является org.springframework.beans.factory.NoSuchBeanDefinitionException: не определен уникальный бин типа [com.sevenp.mobile.samplemgmt.service.dao.DeviceDao]: ожидается один соответствующий бин, но найдено 2: [deviceDao, jdbcDeviceDao]

из класса, пытающегося автоматически подключить боб, как это

@Autowired
private DeviceDao hibernateDevicDao;

потому что есть два компонента, реализующие один и тот же интерфейс.

Вопрос:

Можно ли настроить бины так, чтобы

1. Мне не нужно вносить изменения в существующие классы, которые уже имеют HibernateDeviceDaoавтопроводку

2. все еще в состоянии использовать второй (новый) компонент следующим образом:

@Autowired
@Qualifier("jdbcDeviceDao")

Т.е. мне понадобится способ настроить HibernateDeviceDaobean-компонент в качестве bean-компонента по умолчанию для автоматической проводки, позволяя одновременно использовать a JdbcDeviceDaoпри явном указании этого с @Qualifierаннотацией.

Что я уже пробовал:

Я пытался установить свойство

autowire-candidate="false"

в конфигурации компонента для JdbcDeviceDao:

<bean id="jdbcDeviceDao" class="com.initech.service.dao.jdbc.JdbcDeviceDao" autowire-candidate="false">
    <property name="dataSource" ref="jdbcDataSource"/>
</bean>

потому что в документации Spring сказано, что

Указывает, следует ли учитывать этот компонент при поиске подходящих кандидатов для соответствия требованиям к автоматическому подключению другого компонента. Обратите внимание, что это не влияет на явные ссылки по имени, которые будут разрешены, даже если указанный bean-компонент не помечен как кандидат для автоматической передачи. *

что я интерпретировал, чтобы означать, что я все еще могу автоматически подключаться JdbcDeviceDaoс использованием @Qualifierаннотации и иметь HibernateDeviceDaoкомпонент по умолчанию. Очевидно, что моя интерпретация была неверной, так как это приводит к следующему сообщению об ошибке при запуске сервера:

Неудовлетворенная зависимость типа [class com.sevenp.mobile.samplemgmt.service.dao.jdbc.JdbcDeviceDao]: ожидается как минимум 1 соответствующий компонент

пришедший из класса, где я пытался автоматически связать бин с классификатором:

@Autowired
@Qualifier("jdbcDeviceDao")

Решение:

skaffman в предложение попробовать @Resource аннотацию работал. Таким образом, для конфигурации autowire-кандидата установлено значение false для jdbcDeviceDao, и при использовании jdbcDeviceDao я обращаюсь к нему с помощью аннотации @Resource (вместо @Qualifier):

@Resource(name = "jdbcDeviceDao")
private JdbcDeviceListItemDao jdbcDeviceDao;
Саймон
источник
Если я использую этот интерфейс на 100 мест в коде, и я хочу переключить все на другую реализацию, я не хочу менять квалификатор или аннотации ресурсов во всех местах. И я также не хочу менять код обеих реализаций. Почему нет явных возможностей связывания, как в Guice?
Даниэль Хари

Ответы:

134

Я хотел бы предложить маркировку класса Hibernate DAO с @Primary, то есть (если вы использовали @Repositoryна HibernateDeviceDao):

@Primary
@Repository
public class HibernateDeviceDao implements DeviceDao

Таким образом, он будет выбран как кандидат по умолчанию для автоматической передачи, без необходимости autowire-candidateиспользования другого компонента.

Кроме того, вместо того, чтобы использовать @Autowired @Qualifier, я считаю более элегантным использовать @Resourceдля выбора конкретных бобов, то есть

@Resource(name="jdbcDeviceDao")
DeviceDao deviceDao;
skaffman
источник
Я забыл упомянуть в вопросе, что я использую Spring 2.5 (я сейчас редактировал вопрос), поэтому @Primary не вариант.
Симон
1
@ Симон: Да, это было довольно важно. Попробуйте @Resourceаннотацию, как я также предложил.
Скаффман
1
Спасибо, аннотация ресурса решила проблему - теперь свойство autowire-кандидата работает, как я и ожидал.
Симон
Спасибо! В чем разница между указанием имени компонента с помощью @Resourceи @Qualifier, кроме того факта, что первый относительно новее, чем второй?
спрашивает
1
@asgs Использование аннотации ресурса упрощает вещи. Вместо комбо autowired / qualifier вы можете пометить его для внедрения зависимостей и указать имя в одной строке. Обратите внимание, что решение Саймона является избыточным, аннотацию с автопроводкой можно удалить.
Гилберт Аренас Кинжал
37

Как насчет @Primary?

Указывает, что бобу следует отдавать предпочтение, когда несколько кандидатов имеют право на автоматическую передачу однозначной зависимости. Если среди кандидатов существует только один «основной» компонент, это будет значение с автопроводкой. Эта аннотация семантически эквивалентна атрибуту <bean>элемента primaryв Spring XML.

@Primary
public class HibernateDeviceDao implements DeviceDao

Или, если вы хотите, чтобы ваша версия Jdbc использовалась по умолчанию:

<bean id="jdbcDeviceDao" primary="true" class="com.initech.service.dao.jdbc.JdbcDeviceDao">

@Primary также отлично подходит для интеграционного тестирования, когда вы можете легко заменить рабочий компонент на заглушенную версию, пометив ее аннотацией.

Томаш Нуркевич
источник
Я забыл упомянуть в вопросе, что я использую Spring 2.5 (я сейчас редактировал вопрос), поэтому @Primary не вариант.
Симон
1
@ Симон: Я считаю, что primary=""атрибут был доступен ранее. Просто объявите HibernateDeviceDaoв XML и исключите его из сканирования компонентов / аннотаций.
Томаш Нуркевич
1
Согласно документации, она доступна с версии 3.0: static.springsource.org/spring/docs/3.1.x/javadoc-api/org/… Хороший совет, в любом случае, я запомню аннотацию Primary для следующего проекта, когда смогу использовать Spring 3.x
Simon
8

Для весны 2.5 нет @Primary. Единственный способ - это использовать @Qualifier.

Blang
источник
2
The use of @Qualifier will solve the issue.
Explained as below example : 
public interface PersonType {} // MasterInterface

@Component(value="1.2") 
public class Person implements  PersonType { //Bean implementing the interface
@Qualifier("1.2")
    public void setPerson(PersonType person) {
        this.person = person;
    }
}

@Component(value="1.5")
public class NewPerson implements  PersonType { 
@Qualifier("1.5")
    public void setNewPerson(PersonType newPerson) {
        this.newPerson = newPerson;
    }
}

Now get the application context object in any component class :

Object obj= BeanFactoryAnnotationUtils.qualifiedBeanOfType((ctx).getAutowireCapableBeanFactory(), PersonType.class, type);//type is the qualifier id

you can the object of class of which qualifier id is passed.
Сандип Джайн
источник
0

Причина, по которой @Resource (name = "{имя вашего дочернего класса}") работает, но @Autowired иногда не работает, заключается в разнице их последовательности соответствия

Соответствующая последовательность @Autowire
Type, Qualifier, Name

Соответствующая последовательность @Resource
Name, Type, Qualifier

Более подробное объяснение можно найти здесь:
Inject and Resource и Autowired аннотации

В этом случае другой дочерний класс, унаследованный от родительского класса или интерфейса, сбивает с толку @Autowire, поскольку они принадлежат к одному типу; Поскольку @Resource использует Name в качестве первого соответствующего приоритета, это работает.

RAY
источник