如何使用 Spring Framework 实现 multiton 模式

时间:2021-03-28 00:58:33

标签: spring multiton

如何使用 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();
    }
}

1 个答案:

答案 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

请注意,根据输出,每个 client1client2 的 ClientSdk 对象仅创建一次,即使它们被多次使用。另请注意,由于 ident() 中对 sendRequestToClient 的调用打印了由自动装配的 Environment 实例获得的属性的值,因此每个 ClientSdk 对象的自动装配都有效。

我确实意识到我使用 String 而不是 ClientSupplier 对象作为每个 ClientSdk 对象的标识键。我这样做只是为了使示例尽可能简单。我希望您可以扩展示例以将 clientSupplier String 替换为 ClientSupplier 的实例,并以某种方式使用该对象作为键/标识符以确保只有一个 ClientSdk 实例是根据 ClientSupplier 创建的。这个细节对于这里的基本思想来说并不是很重要。

另外,请注意,在我进行实施时,问题本身发生了变化。鉴于现在 ClientSdk 正好有两个子类,您可以简单地制作那些常规的 @Component Spring bean。有一个小的静态数量会使这个问题不那么有趣。我在此演示的技术允许 ClientSdk 类的 bean 实例数量不受限制,而无需为每个实例定义唯一的类。这要求 Spring 根据运行时信息创建它们的任意实例。这就是问题的原始形式似乎要求的内容。

相关问题