如何从Spring之外的非托管对象访问 Spring Bean?


实体、值对象、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);
    }
}