物件導向的特點是透過繼承與多型來達到的不同實作效果,而Spring容器管理的物件是以依賴注入取得,若介面有多個實作,並且要在執行期(runtime)依條件動態取得對應的bean的方法如下。
範例環境:
- Spring Boot 2.3.x
- JUnit 5
- Lombok
Spring環境中若要動態取得對應的bean,可使用BeanFactory
取得。
例如出貨介面ShippingService
定義了出貨方法ship()
,現在有黑貓貨運(TcatShippingService
)和郵局貨運(PostShippingService
)實作了各自的出貨方式。
出貨服務介面。
ShippingService
package com.abc.demo.service;
public interface ShippingService {
void ship(String orderId);
}
黑貓出貨,實作ShippingService
。
TcatShippingService
package com.abc.demo.service;
import org.springframework.stereotype.Service;
@Service
public class TcatShippingService implements ShippingService {
@Override
public void ship(String orderId) {
System.out.println("黑貓出貨, orderId=" + orderId);
}
}
郵局出貨,實作ShippingService
。
PostShippingService
package com.abc.demo.service;
import org.springframework.stereotype.Service;
@Service
public class PostShippingService implements ShippingService {
@Override
public void ship(String orderId) {
System.out.println("郵局出貨, orderId=" + orderId);
}
}
而出貨服務API /ship/{shipper}/{orderId}
傳入出貨業者的名稱並需要依此呼叫對應的出貨服務,這邊就能利用BeanFactory
動態取得對應的bean來進行出貨邏輯。
DemoController
ackage com.abc.demo.controller;
import com.abc.demo.enumeration.Shipper;
import com.abc.demo.service.ShippingService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@Autowired
private BeanFactory beanFactory;
@PostMapping("/ship/{shipper}/{orderId}")
public void ship(@PathVariable String shipper, @PathVariable String orderId) {
Shipper shipperEnum = Shipper.of(shipper);
if (shipperEnum == null) {
throw new IllegalArgumentException("shipper=" + shipper);
}
ShippingService shippingService =
beanFactory.getBean(shipperEnum.getShippingServiceClass()); // 依Class取得對應的bean
shippingService.ship(orderId);
}
}
Shipper
列舉維護出貨業者的資訊及對應的ShippingService
實作類,用來在DemoController.ship()
中提供給BeanFactory.getBean(Class<T> requiredType)
取得對應的ShippingService
的bean。
Shipper
package com.abc.demo.enumeration;
import com.abc.demo.service.PostShippingService;
import com.abc.demo.service.ShippingService;
import com.abc.demo.service.TcatShippingService;
import lombok.Getter;
public enum Shipper {
TCAT("tcat", TcatShippingService.class),
POST("post", PostShippingService.class);
@Getter
private final String name;
@Getter
private final Class<? extends ShippingService> shippingServiceClass;
Shipper(String name, Class<? extends ShippingService> shippingServiceClass) {
this.name = name;
this.shippingServiceClass = shippingServiceClass;
}
public static Shipper of(String name) {
for (Shipper shipper : Shipper.values()) {
if (shipper.getName().equalsIgnoreCase(name)) {
return shipper;
}
}
return null;
}
}
DemoControllerTests
測試。
DemoControllerTests
package com.abc.demo.controller;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.web.util.NestedServletException;
@AutoConfigureMockMvc
@SpringBootTest
class DemoControllerTests {
@Autowired
private MockMvc mockMvc;
/**
* 測試呼叫黑貓貨運出貨服務實作
* @throws Exception
*/
@Test
void ship_callTcatShipperService() throws Exception {
String url = "/ship/tcat/202010190000000001";
mockMvc.perform(MockMvcRequestBuilders.post(url))
.andExpect(MockMvcResultMatchers.status().isOk());
}
/**
* 測試呼叫郵局貨運出貨服務實作
* @throws Exception
*/
@Test
void ship_callPostShipperService() throws Exception {
String url = "/ship/post/202010190000000001";
mockMvc.perform(MockMvcRequestBuilders.post(url))
.andExpect(MockMvcResultMatchers.status().isOk());
}
/**
* 測試呼叫不支援的貨運出貨服務實作
*/
@Test
void ship_callUnsupportedShipperService() throws Exception {
String url = "/ship/other/202010190000000001";
Assertions.assertThrows(
NestedServletException.class,
() -> mockMvc.perform(MockMvcRequestBuilders.post(url)));
}
}
上述依照不同參數取得不同實例的做法,就是典型的簡單工廠模式的應用,所以這個用來取得bean實例的物件正叫做BeanFactory
。
參考github。
沒有留言:
張貼留言