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


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 як бон за замовчуванням для автоматичного з'єднання, одночасно дозволяючи використовувати 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>

тому що весняна документація говорить про це

Вказує на те, чи слід враховувати цей боб під час пошуку відповідних кандидатів, щоб задовольнити вимоги до електромонтажу іншого боба. Зауважте, що це не впливає на явні посилання на ім’я, які будуть вирішені, навіть якщо вказаний боб не буде позначений як кандидат автозаправки. *

що я інтерпретував так, що я все ще можу зробити автопровід JdbcDeviceDaoза допомогою @Qualifierанотації та мати за HibernateDeviceDaoзамовчуванням боб. Мабуть, моє тлумачення було невірним, оскільки це призводить до наступного повідомлення про помилку при запуску сервера:

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

виходить з класу, де я спробував автоматичне з'єднання квасолі з класифікатором:

@Autowired
@Qualifier("jdbcDeviceDao")

Рішення:

skaffman в пропозицію спробувати @Resource анотацію працював. Таким чином, у конфігурації встановлено параметр autowire-kandidat для 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;

У запитанні я забув згадати, що я використовую Spring 2.5 (я зараз редагував це питання), тому @Primary - це не варіант.
симон

1
@simon: Так, це було досить важливо. Спробуйте @Resourceпримітку, як я також запропонував.
скафман

1
Дякую, анотація до ресурсів вирішила проблему - зараз властивість autowire-kandidat працює як я очікував.
симон

Дякую! Яка різниця між зазначенням імені квасолі через @Resourceта @Qualifier, крім того, що перший відносно новіший, ніж останній?
асгс

1
@asgs Використання анотації про ресурси спрощує завдання. Замість того, щоб мати комбінований автоматичний провідник / кваліфікатор, ви можете позначити його для введення залежності та вказати ім'я в одному рядку. Зауважте, що рішення симона є зайвим, авто-провідену примітку можна видалити.
Кинджал Гілберта Аренаса

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
@simon: Я вважаю, що primary=""атрибут був доступний і раніше. Просто задекларуйте HibernateDeviceDaoу XML та виключіть його із сканування компонентів / анотацій.
Томаш Нуркевич

1
Відповідно до документації, вона доступна з 3.0: static.springsource.org/spring/docs/3.1.x/javadoc-api/org/… Добре підказка все одно, я згадаю первинну анотацію для наступного проекту, коли зможу використовувати Spring 3.x
simon

8

На весну 2.5 немає @Primary. Єдиний спосіб - це використовувати @Qualifier.


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
, Кваліфікатор, Ім'я

Відповідність послідовності @Resource
Name, Type, Qualifier

Більш детальне пояснення можна знайти тут:
Анотації щодо ін'єкційних та ресурсних та автоматичних проводів

У цьому випадку різні дочірні класи, успадковані від батьківського класу чи інтерфейсу, плутають @Autowire, оскільки вони є одним типом; Оскільки @Resource використовує ім'я як перший пріоритет, він працює.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.