Spring 4 не определяет автоматически универсальные типы в autowire

ПРОБЛЕМА ВЫЯВЛЕНА, ОБНОВЛЕНА ОПУБЛИКОВАНА (прокрутите вниз)

Я разрабатываю настольное приложение, в настоящее время использующее Spring (spring-context, 4.1.6.RELEASE) для IoC и внедрения зависимостей. Я использую конфигурацию аннотации, используя @ComponentScan. Проблема, с которой я столкнулся, должна быть реализована как функция в 4.X.X, как указано здесь и здесь, но я я получаю старое исключение 3.X.X.

У меня есть параметризованный интерфейс, представляющий общий репозиторий:

public interface DomainRepository<T> {

    T add(T entity) throws ServiceException, IllegalArgumentException;

    // ...etc

}

Затем у меня есть две конкретные реализации этого, ChunkRepositoryImpl и ProjectRepositoryImpl, которые параметризованы соответственно. Они имеют общую реализацию из абстрактного класса, но объявлены так:

@Repository
public class ChunkRepositoryImpl extends AbstractRepositoryImpl<Chunk> implements DomainRepository<Chunk> {

    // ...+ various method implementations

}

@Repository
public class ProjectRepositoryImpl extends AbstractRepositoryImpl<Project> implements DomainRepository<Project> {

    // ...+ various method implementations

}

Понимание приведенных выше ссылок приводит меня к мысли, что я смогу автоматически подключать их без необходимости вручную указывать bean-компоненты через @Qualifier. Однако когда я это сделаю:

@Autowired
private DomainRepository<Project> repository;

Я получаю следующее исключение (которому, конечно же, предшествует длинная трассировка стека):

Вызвано: org.springframework.beans.factory.NoUniqueBeanDefinitionException: не определен квалифицирующий bean-компонент типа [com.foo.bar.repositories.DomainRepository]: ожидаемый единственный соответствующий bean-компонент, но найдено 2: chunkRepositoryImpl, projectRepositoryImpl

Может ли кто-нибудь пролить свет на то, почему это могло происходить? Я бы ожидал этого исключения в 3.X.X, но этого не должно происходить в 4.X.X. В чем разница между моей ситуацией и описанной здесь?

ОБНОВЛЕНИЕ

Я обнаружил источник проблемы. Один из методов в моем DomainRepository<T> интерфейсе отмечен как @Async и использует асинхронные возможности Spring. Удаление этого означает, что бобы правильно квалифицированы. Я предполагаю, что Spring преобразует классы с @Async методами под капотом в какой-то другой класс, и этот процесс удаляет информацию о типе, что означает, что он не может отличить bean-компоненты.

Это означает, что теперь у меня есть два вопроса:

  1. Это предполагаемое поведение?
  2. Кто-нибудь может предложить обходной путь?

Вот проект, демонстрирующий проблему. Просто удалите аннотацию @Async из интерфейса DomainRepository<T>, и проблема исчезнет.


person Will Faithfull    schedule 06.10.2015    source источник
comment
У меня это отлично работает на 4.x.x. Выложите, пожалуйста, полный и воспроизводимый пример.   -  person Sotirios Delimanolis    schedule 07.10.2015
comment
@SotiriosDelimanolis Работаю над воспроизводимым примером в другом проекте, но пока он работает и у меня. Однако в исходном проекте исключение все еще сохраняется. Это очень сбивает с толку.   -  person Will Faithfull    schedule 07.10.2015
comment
Затенено ли имя Project в классе с полем repository?   -  person Sotirios Delimanolis    schedule 07.10.2015
comment
Я бы не подумал об этом, но, к сожалению, нет. Объявление в классе с полем repository определенно ссылается на тот же объект домена, что и объявление репозитория.   -  person Will Faithfull    schedule 07.10.2015
comment
@SotiriosDelimanolis Eureka! Я обнаружил источник проблемы, но не причину. Один из методов в моем интерфейсе отмечен @Async. Без этой аннотации он работает должным образом. С, это не сработает с вышеуказанным.   -  person Will Faithfull    schedule 07.10.2015
comment
Вы можете привести пример?   -  person Sotirios Delimanolis    schedule 07.10.2015
comment
да. Я отправлю минимальный проект на GitHub, а затем отредактирую этот пост со ссылкой. Вот он.   -  person Will Faithfull    schedule 07.10.2015


Ответы (1)


Я предполагаю, что Spring преобразует классы с методами @Async под капотом в какой-то другой класс, и этот процесс удаляет информацию о типе, что означает, что он не может отличить bean-компоненты.

да. Именно так и происходит.

Spring 4 поддерживает внедрение bean-компонентов по их полной общей сигнатуре. Учитывая цель закачки

@Autowired
private DomainRepository<Project> repository;

и bean-компонент типа ProjectRepositoryImpl, Spring правильно разрешит и вставит этот bean-компонент в поле (или аргумент метода, или аргумент конструктора).

Однако в вашем коде фактически нет bean-компонента типа ProjectRepositoryImpl, даже типа DomainRepository<Project>. Фактически у вас есть компонент типа java.lang.Proxy (фактически его динамический подкласс), который реализует DomainRepository, org.springframework.aop.SpringProxy и org.springframework.aop.framework.Advised.

С @Async Spring необходимо проксировать ваш bean-компонент, чтобы добавить асинхронное поведение диспетчеризации. Этот прокси-сервер по умолчанию является прокси-сервером JDK. Прокси-серверы JDK могут наследовать только интерфейсы целевого типа. Прокси JDK создаются с помощью фабричного метода _ 10_. Обратите внимание, что он принимает только Class аргументов, а не Type. Таким образом, он может получить дескриптор типа только для DomainRepository, но не для DomainRepository<Chunk>.

Следовательно, у вас нет bean-компонента, реализующего ваш параметризованный целевой тип DocumentRepository<Project>. Spring вернется к исходному типу DocumentRepository и найдет два кандидата-боба. Это неоднозначный матч, поэтому он проваливается.

Решение - использовать прокси CGLIB с

@EnableAsync(proxyTargetClass = true)

Прокси-серверы CGLIB позволяют Spring получать полную информацию о типах, а не только интерфейсы. Таким образом, ваш прокси-сервер фактически будет иметь тип, который, например, является подтипом ProjectRepositoryImpl, который несет с собой информацию о типе DocumentRepository<Project>.


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

person Sotirios Delimanolis    schedule 07.10.2015