0

如何使用 Spring Framework 的工具来实现 multiton 模式?

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();
    }
}
4

1 回答 1

1

这是您的问题的解决方案。SdkFactory正如@crizzis 建议的那样,它确实创建了一个 bean,但它也为每个实例创建 bean 实例,以便每个ClientSdk实例都可以自动装配或由 Spring 提供帮助。请注意,我ident()在接口中添加了一个方法,ClientSdk只是为了表明MyClientSdkbean 实际上已经与 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()in的调用打印了由自动装配实例sendRequestToClient获得的属性的值,因此每个对象的自动装配都有效。EnvironmentClientSdk

我确实意识到我使用 aString而不是ClientSupplier对象作为每个 ClientSdk 对象的标识键。我这样做只是为了使示例尽可能简单。我希望您可以扩展示例以将 替换为clientSupplier String的实例,ClientSupplier并以某种方式将该对象用作键/标识符,以确保ClientSdk每个ClientSupplier. 这个细节并不是这里的基本想法。

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

于 2021-03-28T19:49:57.347 回答