×

Задачка на Spring Framework

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Всем привет

Spring Framework — один из самых популярных фреймворков для Enterprise Java. По большому счету, Spring — это не просто фреймворк, а платформа для разработки и интеграции различных технологий.
С другой стороны, его технологическая сложность приводит к тому, что не просто сразу разобраться в его конфигурации, особенно для больших проектов.

На нашем последнем тренинге по Spring Framework(it-simulator.com/...​3/osnovy-spring-framework) возникла интересная ситуация, когда один студент не сразу разобрался, как Spring загружает конфигурацию для задания.

Отсюда родилась интересная задача. Представим себе Spring приложение с тремя бинами:

@Component
class Service {	
	public Service(Repository rep){}
}

interface Repository {}

@Component  
class DBRepository implements Repository {}

@Component 
class XmlRepository implements Repository {}

Если вы используете Spring 4.3 и выше, то у вас в конструкторе класса Service сработает auto-wiring, но Spring не сможет подобрать подходящий бин, так как у нас два бина реализуют интерфейс Repository. И в конце концов мы получим Exception.

Но не зря же говорят, что в основе Spring лежит annotation-driven development. Итак, нужно, НЕ меняя кода, а лишь добавляя аннотации, найти все возможные варианты, чтобы сработал auto-wiring и один из бинов Repository был передан в конструктор.

Всем удачи!

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Добавить Qualifier в параметры конструктора:

public Service(@Qualifier("dBRepository") Repository rep){}

В таком случае спринг кидает NoUniqueBeanDefinitionException, по которому за пол минуты гуглится ответ

Зачем на этот вопрос целый топик на ДОУ — непонятно
А хотя нет, понятно — автор решил попиарить свои курсы, расходимся

Где Евгений Борисов? Задачка его уровня =)

1) создать аннотацию, например

public @interface MainRepo {}
2) пометить этой аннотацией один из репозиториев, пусть xmlRepository
@Component
@MainRepo
class XmlRepository implements Repository {}
3) в конструкторе добавить аннотацию к аргументу
@Component
class Service {
public Service(@MainRepo Repository rep){}
}
4) ??????

5) PROFIT

Я понимаю, конечно, что тренинг, задача учебная и т.д., но формулировка вопроса меня смутила.

НЕ меняя кода, а лишь добавляя аннотации
Хотелось, чтобы у студентов было бы понимание, что добавление аннотаций — это тоже изменение кода и писать его желательно так, чтобы конфигурирование происходило без перекомпиляции.
изменение кода и писать его желательно так, чтобы конфигурирование происходило без перекомпиляции.
это как и зачем?

Имелось в виду, что использовать в сервисе другую реализацию репозитория с помощью изменения аннотаций — возможно, не лучшая идея.

Задача на Спринг и вполне валидная. Скажем, в открытом у меня проекте в продакшин контексте 4 датасорца разных и куча сериализаторов, которых нужно правильно выбирать. В другом контексте может, например профили бы лучше подошли, но при чем тут конкретно эта задача?

Если рассматривать с такой точки зрения — вопросов нет. Ниже в комментариях, например,

@Component("rep«)
звучало — вполне валидный ответ, но, на мой вгляд, самый неочевидный. Поскольку в тексте есть «тренинг и студенты», то хотелось бы сделать акцент на более правильные техники, если так можно выразиться — те же профили или JavaConfig.
на более правильные техники, если так можно выразиться — те же профили или JavaConfig.
Ой та чего мелочиться — ударим толстым иксемелем по студенческому лбу!

зато без перекомпіляції :)

Вы, имхо, напрасно иронизируете.

Да, вы совершенно правы. Имелось в виду, не меняя содержимое классов и интерфейсов, а лишь применяя аннотации.

Это понятно по условию задачи. Я делал акцент на том, что, применяя аннотации, выбирать реализацию репозитория — возможно, не лучшая идея.

А какие варианты вы бы сами предложили ?

Зависит от условий задачи. Но могу попробовать угадать. Вначале я бы задал уточняющий вопрос: зачем и для чего в контексте спринга одновременно поднимаются две реализации репозитория? В большинстве случаев это не нужно. Подчёркиваю, речь идёт о контексте.

1. Самый универсальный вариант — это JavaConfig. Очень правильно и хорошо, что в сервисе используется constructor-, а не field- injection. Поэтому проще и понятнее явно вызвать конструктор и передать туда нужную реализацию в зависимости от условий.
2. Если, например, для тестов подменяется реализация, или в продакшене приложение может работать либо в db, либо в xml режиме, то — @Profile.
3. Опять же, загрузить в контекст только одну реализацию можно с помощью FactoryBean или @Conditional. Но, имхо, JavaConfig удобнее.
4. И, наконец, если категорически требуются обе реализации в контексте, например, один сервис использует одну, а другой — другую, а JavaConfig не используется, то остается только autowiring by name.

Во времена spring 2.5 задача решалась элементарно через factory bean/factory method, который возвращал нужный инстанс по условию.

@Primary добавить над нужным бином

незачет. Нужный бин в одном месте не нужен в другом. Может нужно одновременно оба репозитория использовать?

В примере кода он только один) Мы не знаем что там может или нет

DBRepository
и
XmlRepository
, это как бы не два? Если бы один был, то и проблемы бы не было — само бы заинжектилось.

Я имел ввиду одно место использования

BeanPostProcessor написать уже предлагали?

Пока нет. Борисов еще не залогинился

Да, была такая идея. Но тут смысл в том, чтобы обойтись только Spring аннотациями.

Самопиар такой самопиар.

Спасибо, Руслан, за интересный ответ.

знову задачки в стилі «документацію не читай — ’код’ на вентилятор накидай».

чи у вас там тренінги для тих, хто не осилив документацію почитати?

Зачем читать доку в 900 страниц если в реальности это @Autowired и Spring MVC контроллеры ? Остальное гуглится.

Остальное гуглится.
приблизно це я і мав на увазі.
коли виникне подібна ситуація, відкриваєш spring reference і читаєш, як вирішити проблему.
для цього не потрібно йти ще на якийсь тренінг і ще й платити за це гроші.

Над одним из репозиториев написать @Primary
Вроде так

Или добавить @Qualifier("rep")

Совершенно верно, Виктория. Это правильный ответ. Но еще есть и другие варианты решения)

Ну можно еще в @Component name передавать, использовать @Resource. Вариантов действительно много.

Да, можно указать @Component("rep") для DBRepository, и тогда он будет выбран для auto-wiring. Есть и еще варианты)

Ну можна через профілі зробити (@Profile). Плюс був, здається, якийсь метод зробити це на етапі ініціалізації (задати свій BeanProcessor) — хз, точно не пам’ятаю, ні разу не користувався.

Совершенно верно, указав @Profile, можно убрать бин из загрузки в контекст. Через BeanPostProcessor тоже можно, но задача была на аннотации)

@Qualifier("rep")
Для данной задачи это выглядит как trick. Давайте всё же думать о сопровождении кода.

Підписатись на коментарі