如何從Spring之外的非託管物件訪問 Spring Bean?

banq發表於2022-02-07

實體、值物件、DTO或VO、record之類基本都是隻有getXX/setXX的物件(record除外),當DDD領域設計為這些物件賦予業務行為以後,這些業務行為會與技術環境如Srping管理的bean相互互動,在clean架構中實現為介面卡或埠,但是具體如何在Java中落地?

另外,我們可能需要將 Spring Beans 注入 JPA 實體或其他一些非託管物件,這可能表明我們需要重新考慮我們的架構,但有時這是無法避免的。可以使用@Configurable註釋來做到這一點,但要使其工作,必須使用 AspectJ 編織器來編織帶註釋的型別。在本教程中,我們將研究一種從非託管物件訪問 Spring 託管元件的替代方法,該方法可以說比注入更好。

 

作為示例,我們將使用一個簡單的 Spring 應用程式,我們將在其中建立一個TaxCalculator元件,該元件又可能具有一些依賴項,但在我們的示例中,它非常簡單:

@Component
public class TaxCalculator {

    public double calculate(double price) {
        return price * 0.25;
    }
}

現在我們將為非託管物件建立一條記錄:

public record Invoice(double price) {}

假設我們希望他們提供稅款。為了計算它,我們需要使用TaxCalculator,但是由於這個類的例項不是由 Spring 管理的,我們不能簡單地將這個依賴注入到它們中。

 

選項 1:使用靜態方式公開例項

由於 Spring bean預設是單例的InitializingBean,我們可以通過實現介面,或者使用註解將其注入到自己的靜態欄位中@PostConstruct,然後用靜態方法將其暴露出來。在本例中,我們將使用註解:

public class TaxCalculator {

    private static TaxCalculator instance;

    public static TaxCalculator getInstance() {
        return instance;
    }

    @PostConstruct
    private void registerInstance() {
        instance = this;
    }

    // ...
}

現在我們可以在我們的非託管物件中使用這個方法:

public record Invoice(double price) {

    public double calculateTaxUsingComponent() {
        return TaxCalculator.getInstance().calculate(this.price);
    }
}

讓我們通過執行來編寫一個測試,我們可以驗證一切正常:

@SpringBootTest
class InvoiceTest {

    @Test
    void calculateTaxUsingComponent() {
        // given
        var invoice = new Invoice(20);

        // when
        var result = invoice.calculateTaxUsingComponent();

        // then
        assertThat(result).isEqualTo(5.0);
    }
}

 

 

選項 2:實現一個提供者

也許我們不想改變元件的類或者不想依賴特定的實現,在這種情況下,我們可以實現一個提供者來為我們提供元件,或者如果我們使用介面然後是例項在 IoC 容器中找到它的實現。例如,讓我們更新我們的元件並使其實現以下介面:

public interface ITaxCalculator {

    double calculate(double price);
}

然後我們實現一個提供者實現,在它的靜態欄位中我們注入我們的元件並使用靜態方法公開它:

@Component
public class TaxCalculatorProvider {

    private static ITaxCalculator calculator;

    public TaxCalculatorProvider(ITaxCalculator calculator) {
        TaxCalculatorProvider.calculator = calculator;
    }

    public static ITaxCalculator getCalculator() {
        return calculator;
    }
}

現在我們可以在我們的非託管物件中使用提供者:

public record Invoice(double price) {

    public double calculateTaxUsingProvider() {
        return TaxCalculatorProvider.getCalculator().calculate(this.price);
    }
}

最後,我們可以通過一個簡單的測試來驗證一切是否正常:

@SpringBootTest
class InvoiceTest {

    @Test
    void calculateTaxUsingProvider() {
        // given
        var invoice = new Invoice(50);

        // when
        var result = invoice.calculateTaxUsingProvider();

        // then
        assertThat(result).isEqualTo(12.5);
    }
}

 

相關文章