![](/img/trans.png)
[英]Dependency injection with Guice for several implementations of same interface
[英]Dynamic dependency injection for multiple implementations of the same interface with Spring MVC
我正在使用REST API,其中有一個接口,該接口定義了由4個不同的類實現的方法的列表,並可能在將來添加更多方法。
當我從客戶端收到HTTP請求時,URL中會包含一些信息,這些信息將確定需要使用哪種實現。
在我的控制器內,我希望端點方法包含一個switch語句,該語句檢查URL路徑變量,然后使用適當的實現。
我知道我可以定義具體的實現並將其注入到控制器中,然后在switch語句中插入我想在每種特定情況下使用的具體實現,但是由於兩個原因,這似乎不是很優雅或可擴展:
現在,即使我只需要使用其中的一個,我也必須實例化所有服務。
該代碼似乎更精簡,因為我實際上是使用相同的參數調用在接口中定義的相同方法,而在本示例中,這並不是真正的問題,但是在實現列表增加的情況下。 ..案件數和冗余代碼也是如此。
是否有更好的解決方案來解決這種情況? 我正在使用SpringBoot 2和JDK 10,理想情況下,我想實現最現代的解決方案。
我目前的方法
@RequestMapping(Requests.MY_BASE_API_URL)
public class MyController {
//== FIELDS ==
private final ConcreteServiceImpl1 concreteService1;
private final ConcreteServiceImpl2 concreteService2;
private final ConcreteServiceImpl3 concreteService3;
//== CONSTRUCTORS ==
@Autowired
public MyController(ConcreteServiceImpl1 concreteService1, ConcreteServiceImpl2 concreteService2,
ConcreteServiceImpl3 concreteService3){
this.concreteService1 = concreteService1;
this.concreteService2 = concreteService2;
this.concreteService3 = concreteService3;
}
//== REQUEST MAPPINGS ==
@GetMapping(Requests.SPECIFIC_REQUEST)
public ResponseEntity<?> handleSpecificRequest(@PathVariable String source,
@RequestParam String start,
@RequestParam String end){
source = source.toLowerCase();
if(MyConstants.SOURCES.contains(source)){
switch(source){
case("value1"):
concreteService1.doSomething(start, end);
break;
case("value2"):
concreteService2.doSomething(start, end);
break;
case("value3"):
concreteService3.doSomething(start, end);
break;
}
}else{
//An invalid source path variable was recieved
}
//Return something after additional processing
return null;
}
}
在Spring中,您可以通過注入List<T>
或Map<String, T>
字段來獲得接口的所有實現(例如T
)。 在第二種情況下,bean的名稱將成為地圖的鍵。 如果有很多可能的實現方式,或者它們經常更改,則可以考慮這一點。 多虧了它,您可以在不更改控制器的情況下添加或刪除實現。
在這種情況下,注入List
或Map
都具有一些優點和缺點。 如果注入List
,則可能需要添加一些方法來映射名稱和實現。 就像是 :
interface MyInterface() {
(...)
String name()
}
這樣,您可以將其轉換為Map<String, MyInterface>
,例如使用Streams API。 盡管這會更明確,但會稍微污染您的界面(為什么要知道有多個實現?)。
使用Map
您可能應該顯式命名bean,甚至引入注釋以遵循最少驚訝的原則。 如果使用配置類的類名或方法名來命名Bean,則可以通過重命名它們(實際上是更改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());
}
}
關於您的兩個疑問:
1.實例化服務對象應該不是問題,因為這是一項工作,控制器將需要它們來滿足所有類型的請求。
2.您可以使用精確的路徑映射來擺脫開關情況。 例如:
@GetMapping("/specificRequest/value1")
@GetMapping("/specificRequest/value2")
@GetMapping("/specificRequest/value3")
以上所有映射都將在單獨的方法上,該方法將處理特定的源值並調用相應的服務方法。 希望這將有助於使代碼更簡潔明了。
還有另一種選擇,可以在服務層上將其分開,並且只有一個終結點來服務所有類型的源,但是正如您所說的,每個源值都有不同的實現,然后它說源不過是您的應用程序的資源,並且具有單獨的資源URI /單獨方法在這里很有意義。 我在這里看到的一些優點是:
當您的源值有限時,上述方法應該很好。 如果您無法控制源值,那么我們需要在此處進行進一步的重新設計,方法是使源值相差一個以上的值(如sourceType等),然后為每個組的源類型分別設置控制器。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.