如何使用 Spring Framework 的工具实现多元素模式?
https://en.wikipedia.org/wiki/Multiton_pattern
我想编写一个以一对客户和供应商为参数的工厂。工厂应该总是返回一个类型为 T 的 bean。对于给定的一对客户和供应商,T 返回的实例应该是一个单例,但对于不同的客户和供应商对,它将是一个不同的 T 实例。请建议一种无需实现 Spring 可能已经提供的样板代码即可实现此功能的方法。
Interface ClientSdk {
sendRequestToClient();
}
class ClientASdk implements ClientSdk {
}
class ClientBSdk implements ClientSdk {
}
enum Client {
ClientA,
ClientB;
}
enum Supplier {
SupplierA,
SupplierB;
}
class ClientSupplier {
private Client client;
private Supplier supplier;
}
class SdkFactory {
public ClientSdk getClientSdk(ClientSupplier clientSupplier) {
//For a given ClientSupplier, always return the same
//ClientSupplier instance
}
}
@Service
class ClientRequestService {
public sendRequestToClient(ClientSupplier clientSupplier) {
ClientSdk clientSdk = SdkFactory.getSdk(clientSupplier);
clientSdk.sendRequestToClient();
}
}
答案 0 :(得分:1)
这是您问题的解决方案。正如@crizzis 所建议的那样,它确实使 SdkFactory
成为一个 bean,但它也为每个 ClientSdk
实例创建了 bean 实例,以便它们中的每一个都可以自动装配或以其他方式由 Spring 提供帮助。请注意,我向 ident()
接口添加了一个 ClientSdk
方法,只是为了表明 MyClientSdk
bean 实际上已与 Spring Environment
自动装配:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
interface ClientSdk {
void sendRequestToClient();
}
// This class is instantiated via a @Bean method inside SdkFactory. Since it is annotated as a Prototype bean,
// multiple instances of this class can be created as beans.
class MyClientSdk implements ClientSdk {
@Autowired
Environment environment;
private final String clientSupplier;
MyClientSdk(String clientSupplier) {
this.clientSupplier = clientSupplier;
System.out.printf("@@@ Created MyClientSdk for: %s\n", clientSupplier);
}
public void sendRequestToClient() {
System.out.printf("Sending request for client: %s\n", clientSupplier);
System.out.printf("CS: %s Environment Prop: %s\n", clientSupplier, environment.getProperty("spring.application.name"));
}
}
@Component
class SdkFactory implements BeanFactoryAware {
private Map<String, ClientSdk> sdks = new HashMap<>();
private BeanFactory beanFactory;
// Here's the key logic to insure that we get just one instance of ClientSdk per clientSupplier value.
ClientSdk getClientSdk(String clientSupplier) {
if (!sdks.containsKey(clientSupplier))
sdks.put(clientSupplier, beanFactory.getBean(ClientSdk.class, clientSupplier));
return sdks.get(clientSupplier);
}
// This is probably the most important bit. This creates a Spring Bean unique to a particular 'clientSupplier'
// value, but only does so when requested so that the factory can control when these beans are created, creating
// only one per a particular `clientSupplier` value.
@Bean
@Scope("prototype")
ClientSdk createSdk(String clientSupplier) {
return new MyClientSdk(clientSupplier);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}
@Service
class ClientRequestService {
@Autowired
SdkFactory factory;
public void sendRequestToClient(String clientSupplier) {
ClientSdk clientSdk = factory.getClientSdk(clientSupplier);
clientSdk.sendRequestToClient();
}
}
@SpringBootApplication
public class HarmonyTestApp implements CommandLineRunner {
@Autowired
ClientRequestService service;
public static void main(String[] args) {
try {
ApplicationContext applicationContext = new SpringApplication(new Class<?>[]{HarmonyTestApp.class}).run(args);
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public void run(String... args) {
service.sendRequestToClient("client1");
service.sendRequestToClient("client2");
service.sendRequestToClient("client1");
service.sendRequestToClient("client1");
service.sendRequestToClient("client2");
}
}
结果:
@@@ Created MyClientSdk for: client1
Sending request for client: client1
CS: client1 Environment Prop: TestApp
@@@ Created MyClientSdk for: client2
Sending request for client: client2
CS: client2 Environment Prop: TestApp
Sending request for client: client1
CS: client1 Environment Prop: TestApp
Sending request for client: client1
CS: client1 Environment Prop: TestApp
Sending request for client: client2
CS: client2 Environment Prop: TestApp
请注意,根据输出,每个 client1
和 client2
的 ClientSdk 对象仅创建一次,即使它们被多次使用。另请注意,由于 ident()
中对 sendRequestToClient
的调用打印了由自动装配的 Environment
实例获得的属性的值,因此每个 ClientSdk
对象的自动装配都有效。
我确实意识到我使用 String
而不是 ClientSupplier
对象作为每个 ClientSdk 对象的标识键。我这样做只是为了使示例尽可能简单。我希望您可以扩展示例以将 clientSupplier
String
替换为 ClientSupplier
的实例,并以某种方式使用该对象作为键/标识符以确保只有一个 ClientSdk
实例是根据 ClientSupplier
创建的。这个细节对于这里的基本思想来说并不是很重要。
另外,请注意,在我进行实施时,问题本身发生了变化。鉴于现在 ClientSdk 正好有两个子类,您可以简单地制作那些常规的 @Component
Spring bean。有一个小的静态数量会使这个问题不那么有趣。我在此演示的技术允许 ClientSdk
类的 bean 实例数量不受限制,而无需为每个实例定义唯一的类。这要求 Spring 根据运行时信息创建它们的任意实例。这就是问题的原始形式似乎要求的内容。