網頁

2020/10/19

String Boot 使用BeanFactory動態取得bean BeanFactory get bean dynamically

物件導向的特點是透過繼承與多型來達到的不同實作效果,而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


沒有留言:

張貼留言