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