В Spring вы можете получить все реализации интерфейса (скажем, T
), введя поле List<T>
или Map<String, T>
.Во втором случае имена бобов станут ключами карты.Вы можете рассмотреть это, если существует много возможных реализаций или если они часто меняются.Благодаря этому вы можете добавить или удалить реализацию, не меняя контроллер.
Обе инъекции List
или Map
в этом случае имеют некоторые преимущества и недостатки.Если вы введете List
, вам, вероятно, потребуется добавить какой-либо метод для сопоставления имени и реализации.Что-то вроде:
interface MyInterface() {
(...)
String name()
}
Таким образом, вы можете преобразовать его в Map<String, MyInterface>
, например, используя Streams API.Хотя это было бы более явно, это немного улучшило бы ваш интерфейс (зачем ему знать, что существует несколько реализаций?).
При использовании Map
вам, вероятно, следует явно назвать бины или даже представитьаннотация следовать принципу наименьшего удивления.Если вы называете компоненты с помощью имени класса или имени метода класса конфигурации, вы можете сломать приложение, переименовав их (и фактически изменив URL), что обычно является безопасной операцией.
Упрощенная реализация в Spring Boot может выглядеть следующим образом:
@SpringBootApplication
public class DynamicDependencyInjectionForMultipleImplementationsApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDependencyInjectionForMultipleImplementationsApplication.class, args);
}
interface MyInterface {
Object getStuff();
}
class Implementation1 implements MyInterface {
@Override public Object getStuff() {
return "foo";
}
}
class Implementation2 implements MyInterface {
@Override public Object getStuff() {
return "bar";
}
}
@Configuration
class Config {
@Bean("getFoo")
Implementation1 implementation1() {
return new Implementation1();
}
@Bean("getBar")
Implementation2 implementation2() {
return new Implementation2();
}
}
@RestController
class Controller {
private final Map<String, MyInterface> implementations;
Controller(Map<String, MyInterface> implementations) {
this.implementations = implementations;
}
@GetMapping("/run/{beanName}")
Object runSelectedImplementation(@PathVariable String beanName) {
return Optional.ofNullable(implementations.get(beanName))
.orElseThrow(UnknownImplementation::new)
.getStuff();
}
@ResponseStatus(BAD_REQUEST)
class UnknownImplementation extends RuntimeException {
}
}
}
Он проходит следующие тесты:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class DynamicDependencyInjectionForMultipleImplementationsApplicationTests {
@Autowired
private MockMvc mockMvc;
@Test
public void shouldCallImplementation1() throws Exception {
mockMvc.perform(get("/run/getFoo"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("foo")));
}
@Test
public void shouldCallImplementation2() throws Exception {
mockMvc.perform(get("/run/getBar"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("bar")));
}
@Test
public void shouldRejectUnknownImplementations() throws Exception {
mockMvc.perform(get("/run/getSomethingElse"))
.andExpect(status().isBadRequest());
}
}