简体   繁体   English

使用 Spring 3 注解实现一个简单的工厂模式

[英]Implement a simple factory pattern with Spring 3 annotations

I was wondering how I could implement the simple factory pattern with Spring 3 annotations.我想知道如何使用 Spring 3 注释实现简单的工厂模式。 I saw in the documentation that you can create beans that call the factory class and run a factory method.我在文档中看到您可以创建调用工厂类并运行工厂方法的 bean。 I was wondering if this was possible using annotations only.我想知道这是否可能仅使用注释。

I have a controller that currently calls我有一个当前调用的控制器

MyService myService = myServiceFactory.getMyService(test);
result = myService.checkStatus();

MyService is an interface with one method called checkStatus(). MyService 是一个接口,其中一个方法称为 checkStatus()。

My factory class looks like this:我的工厂类如下所示:

@Component
public class MyServiceFactory {

    public static MyService getMyService(String service) {
        MyService myService;
        
        service = service.toLowerCase();
        
        if (service.equals("one")) {
            myService = new MyServiceOne();
        } else if (service.equals("two")) {
            myService = new MyServiceTwo();
        } else if (service.equals("three")) {
            myService = new MyServiceThree();
        } else {
            myService = new MyServiceDefault();
        }
        
        return myService;
    }
}

MyServiceOne class looks like this : MyServiceOne 类如下所示:

@Autowired
private LocationService locationService;

public boolean checkStatus() {
      //do stuff
}

When I run this code the locationService variable is always null.当我运行此代码时,locationService 变量始终为空。 I believe this is because I am creating the objects myself inside the factory and autowiring is not taking place.我相信这是因为我自己在工厂内创建对象并且没有进行自动装配。 Is there a way to add annotations to make this work correctly?有没有办法添加注释以使其正常工作?

Thanks谢谢

The following worked for me:以下对我有用:

The interface consist of you logic methods plus additional identity method:该接口由您的逻辑方法和附加标识方法组成:

public interface MyService {
    String getType();
    void checkStatus();
}

Some implementations:一些实现:

@Component
public class MyServiceOne implements MyService {
    @Override
    public String getType() {
        return "one";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceTwo implements MyService {
    @Override
    public String getType() {
        return "two";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceThree implements MyService {
    @Override
    public String getType() {
        return "three";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

And the factory itself as following:工厂本身如下:

@Service
public class MyServiceFactory {

    @Autowired
    private List<MyService> services;

    private static final Map<String, MyService> myServiceCache = new HashMap<>();

    @PostConstruct
    public void initMyServiceCache() {
        for(MyService service : services) {
            myServiceCache.put(service.getType(), service);
        }
    }

    public static MyService getService(String type) {
        MyService service = myServiceCache.get(type);
        if(service == null) throw new RuntimeException("Unknown service type: " + type);
        return service;
    }
}

I've found such implementation easier, cleaner and much more extensible.我发现这样的实现更容易、更简洁、更可扩展。 Adding new MyService is as easy as creating another spring bean implementing same interface without making any changes in other places.添加新的 MyService 就像创建另一个实现相同接口的 spring bean 一样简单,而无需在其他地方进行任何更改。

You are right, by creating object manually you are not letting Spring to perform autowiring.你是对的,通过手动创建对象,你不会让 Spring 执行自动装配。 Consider managing your services by Spring as well:考虑通过 Spring 管理您的服务:

@Component
public class MyServiceFactory {

    @Autowired
    private MyServiceOne myServiceOne;

    @Autowired
    private MyServiceTwo myServiceTwo;

    @Autowired
    private MyServiceThree myServiceThree;

    @Autowired
    private MyServiceDefault myServiceDefault;

    public static MyService getMyService(String service) {
        service = service.toLowerCase();

        if (service.equals("one")) {
            return myServiceOne;
        } else if (service.equals("two")) {
            return myServiceTwo;
        } else if (service.equals("three")) {
            return myServiceThree;
        } else {
            return myServiceDefault;
        }
    }
}

But I would consider the overall design to be rather poor.但我认为整体设计相当糟糕。 Wouldn't it better to have one general MyService implementation and pass one / two / three string as extra parameter to checkStatus() ?拥有一个通用的MyService实现并将one / two / three字符串作为额外参数传递给checkStatus()不是更好吗? What do you want to achieve?你想达到什么目标?

@Component
public class MyServiceAdapter implements MyService {

    @Autowired
    private MyServiceOne myServiceOne;

    @Autowired
    private MyServiceTwo myServiceTwo;

    @Autowired
    private MyServiceThree myServiceThree;

    @Autowired
    private MyServiceDefault myServiceDefault;

    public boolean checkStatus(String service) {
        service = service.toLowerCase();

        if (service.equals("one")) {
            return myServiceOne.checkStatus();
        } else if (service.equals("two")) {
            return myServiceTwo.checkStatus();
        } else if (service.equals("three")) {
            return myServiceThree.checkStatus();
        } else {
            return myServiceDefault.checkStatus();
        }
    }
}

This is still poorly designed because adding new MyService implementation requires MyServiceAdapter modification as well (SRP violation).仍然设计得很糟糕,因为添加新的MyService实现也需要修改MyServiceAdapter (违反 SRP)。 But this is actually a good starting point (hint: map and Strategy pattern).但这实际上是一个很好的起点(提示:地图和策略模式)。

Following answer of DruidKuma跟随DruidKuma的回答

Litte refactor of his factory with autowired constructor:使用自动装配的构造函数对他的工厂进行小重构:

@Service
public class MyServiceFactory {

    private static final Map<String, MyService> myServiceCache = new HashMap<>();

    @Autowired
    private MyServiceFactory(List<MyService> services) {
        for(MyService service : services) {
            myServiceCache.put(service.getType(), service);
        }
    }

    public static MyService getService(String type) {
        MyService service = myServiceCache.get(type);
        if(service == null) throw new RuntimeException("Unknown service type: " + type);
        return service;
    }
}

You can manually ask Spring to Autowire it.您可以手动要求 Spring 自动装配它。

Have your factory implement ApplicationContextAware.让您的工厂实现 ApplicationContextAware。 Then provide the following implementation in your factory:然后在您的工厂中提供以下实现:

@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
}

and then do the following after creating your bean:然后在创建 bean 后执行以下操作:

YourBean bean = new YourBean();
applicationContext.getAutowireCapableBeanFactory().autowireBean(bean);
bean.init(); //If it has an init() method.

This will autowire your LocationService perfectly fine.这将完美地自动连接您的 LocationService。

Why not add the interface FactoryBean to MyServiceFactory (to tell Spring that it's a factory), add a register(String service, MyService instance) then, have each of the services call:为什么不将接口 FactoryBean 添加到 MyServiceFactory (告诉 Spring 它是一个工厂),然后添加一个寄存器(字符串服务,MyService 实例),然后让每个服务调用:

@Autowired
MyServiceFactory serviceFactory;

@PostConstruct
public void postConstruct() {
    serviceFactory.register(myName, this);
}

This way, you can separate each service provider into modules if necessary, and Spring will automagically pick up any deployed and available service providers.这样,您可以在必要时将每个服务提供者分成模块,Spring 将自动选择任何已部署和可用的服务提供者。

You could also declaratively define a bean of type ServiceLocatorFactoryBean that will act as a Factory class.您还可以以声明方式定义一个ServiceLocatorFactoryBean类型的 bean,它将充当工厂类。 it supported by Spring 3.它受 Spring 3 支持。

A FactoryBean implementation that takes an interface which must have one or more methods with the signatures (typically, MyService getService() or MyService getService(String id)) and creates a dynamic proxy which implements that interface一个 FactoryBean 实现,它采用一个接口,该接口必须具有一个或多个带有签名的方法(通常是 MyService getService() 或 MyService getService(String id)),并创建一个实现该接口的动态代理

Here's an example of implementing the Factory pattern using Spring这是一个使用 Spring 实现工厂模式的示例

One more clearly example 一个更清楚的例子

Based on solution by Pavel Černý here we can make an universal typed implementation of this pattern.基于 Pavel Černý解决方案,我们可以对这种模式进行通用类型化实现。 To to it, we need to introduce NamedService interface:为此,我们需要引入 NamedService 接口:

    public interface NamedService {
       String name();
    }

and add abstract class:并添加抽象类:

public abstract class AbstractFactory<T extends NamedService> {

    private final Map<String, T> map;

    protected AbstractFactory(List<T> list) {
        this.map = list
                .stream()
                .collect(Collectors.toMap(NamedService::name, Function.identity()));
    }

    /**
     * Factory method for getting an appropriate implementation of a service
     * @param name name of service impl.
     * @return concrete service impl.

     */
    public T getInstance(@NonNull final String name) {
        T t = map.get(name);
        if(t == null)
            throw new RuntimeException("Unknown service name: " + name);
        return t;
    }
}

Then we create a concrete factory of specific objects like MyService:然后我们创建一个具体对象的具体工厂,例如 MyService:

 public interface MyService extends NamedService {
           String name();
           void doJob();
 }

@Component
public class MyServiceFactory extends AbstractFactory<MyService> {

    @Autowired
    protected MyServiceFactory(List<MyService> list) {
        super(list);
    }
}

where List the list of implementations of MyService interface at compile time. where 列出编译时 MyService 接口的实现列表。

This approach works fine if you have multiple similar factories across app that produce objects by name (if producing objects by a name suffice you business logic of course).如果您在应用程序中有多个按名称生成对象的类似工厂(如果按名称生成对象当然足以满足您的业务逻辑),则此方法可以正常工作。 Here map works good with String as a key, and holds all the existing implementations of your services.这里的 map 可以很好地使用 String 作为键,并保存您服务的所有现有实现。

if you have different logic for producing objects, this additional logic can be moved to some another place and work in combination with these factories (that get objects by name).如果你有不同的逻辑来生成对象,这个额外的逻辑可以移动到另一个地方并与这些工厂(通过名称获取对象)结合使用。

You could instantiate "AnnotationConfigApplicationContext" by passing all your service classes as parameters.您可以通过将所有服务类作为参数传递来实例化“AnnotationConfigApplicationContext”。

@Component
public class MyServiceFactory {

    private ApplicationContext applicationContext;

    public MyServiceFactory() {
        applicationContext = new AnnotationConfigApplicationContext(
                MyServiceOne.class,
                MyServiceTwo.class,
                MyServiceThree.class,
                MyServiceDefault.class,
                LocationService.class 
        );
        /* I have added LocationService.class because this component is also autowired */
    }

    public MyService getMyService(String service) {

        if ("one".equalsIgnoreCase(service)) {
            return applicationContext.getBean(MyServiceOne.class);
        } 

        if ("two".equalsIgnoreCase(service)) {
            return applicationContext.getBean(MyServiceTwo.class);
        } 

        if ("three".equalsIgnoreCase(service)) {
            return applicationContext.getBean(MyServiceThree.class);
        } 

        return applicationContext.getBean(MyServiceDefault.class);
    }
}

I suppose you to use org.springframework.beans.factory.config.ServiceLocatorFactoryBean.我想你使用 org.springframework.beans.factory.config.ServiceLocatorFactoryBean。 It will much simplify your code.这将大大简化您的代码。 Except MyServiceAdapter u can only create interface MyServiceAdapter with method MyService getMyService and with alies to register your classes除了 MyServiceAdapter 之外,您只能使用 MyService getMyService 方法和别名创建接口 MyServiceAdapter 来注册您的类

Code代码

bean id="printStrategyFactory" class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
        <property name="YourInterface" value="factory.MyServiceAdapter" />
    </bean>

    <alias name="myServiceOne" alias="one" />
    <alias name="myServiceTwo" alias="two" />

I have recently worked on similar requirement where in I wanted to use factory pattern but I was not okay with the if else logic which keeps on growing in future and violates the Single Responsibility Principle.我最近处理过类似的需求,我想使用工厂模式,但我对 if else 逻辑不满意,这种逻辑在未来会继续增长,违反了单一责任原则。

First step, create an interface and have a getType() method, in the given context it will return "one", "two" etc, otherwise it can be anything.第一步,创建一个接口并拥有一个 getType() 方法,在给定的上下文中它将返回“一”、“二”等,否则它可以是任何东西。 This is the common solution most of the people suggested above.这是上面大多数人建议的常见解决方案。

public interface MyService {
    String getType();
    void checkStatus();
}

Some implementations:一些实现:

@Component
public class MyServiceOne implements MyService {
    @Override
    public String getType() {
        return "one";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceTwo implements MyService {
    @Override
    public String getType() {
        return "two";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceThree implements MyService {
    @Override
    public String getType() {
        return "three";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

And the factory itself as following:工厂本身如下:

@Service
public class MyServiceFactory {

    @Autowired
    private List<MyService> services;

    public static MyService getService(final String type) {
        return services
       .stream().filter(service -> type.equals(service.getType()))
       .findFirst()
       .orElseThrow(throw new RuntimeException("Unknown service type: " + type));
        
    }
}

This solution doesn't require extra Map for storing key value of the instances against the type.此解决方案不需要额外的 Map 来针对类型存储实例的键值。 This solution is extensible without any further code change, as the factory has List auto wiring, so any future implementation of MyService would be easily work.该解决方案无需任何进一步的代码更改即可扩展,因为工厂具有 List 自动布线,因此 MyService 的任何未来实现都将很容易工作。 And hence the Single Responsibility Principle is also ensured.因此,单一职责原则也得到了保证。

I have used streams() and predicates as I was using Java 8, for earlier version simple for loop would do the work.我在使用 Java 8 时使用了流()和谓词,因为早期版本简单的 for 循环就可以完成这项工作。

Try this:尝试这个:

public interface MyService {
 //Code
}

@Component("One")
public class MyServiceOne implements MyService {
 //Code
}

@Component("Two")
public class MyServiceTwo implements MyService {
 //Code
}

In Continuation to the 3rd post (posted by DruidKuma), i want to know, from where "services" object will be created or injected? 我想知道在第三篇文章的续篇(DruidKuma发表)中,将从何处创建或注入“服务”对象? - is it from where the MyServiceFactory created or injected? -是从MyServiceFactory创建或注入的地方?

Here "services" object needs created/injected and all the other services object are needs to be added the list, this step needs to do from where Factory objected injected? 这里需要创建/注入“服务”对象,并且需要将所有其他服务对象添加到列表中,这一步需要从哪里注入工厂对象?

@Service
public class MyServiceFactory {

    @Autowired
    private List<MyService> services;

Following the answer from DruidKuma and jumping_monkey遵循DruidKumajumping_monkey的回答

You can also include optional and make your code a bit nicer and cleaner:您还可以包含可选的,并使您的代码更漂亮和更清洁:

 public static MyService getService(String type) {
        return Optional.ofNullable(myServiceCache.get(type))
                .orElseThrow(() -> new RuntimeException("Unknown service type: " + type));
 }

This is a variation of the above answers that creates fresh instances.这是创建新实例的上述答案的变体。

If the Service depends only on Spring managed beans.如果Service仅依赖于Spring托管 bean。

public interface MyService {
 //Code
}

@Component("One")
@Scope("prototype")
public class MyServiceOne implements MyService {
 //Code
   public MyServiceOne(Dependency dep){
   ...
   }
}

@Component("Two")
@Scope("prototype")
public class MyServiceTwo implements MyService {
 //Code
}

public class Factory {
    Map<String,MyService> services;
    ApplicationContext context;
    Dependency dep; 

    public Factory(Map<String, MyService> components, ApplicationContext context, Dependency dep) {
     ...
    }

    MyService service(String type){
        return context.getBean(services.get(type).getClass());
    }
}

@Configuration
public class Config {
       @Bean
       Factory languageFactory(Map<String,Service> map, ApplicationContext context, Dependency dep){
        return new Factory(map,context,dep);
    }
}



If you want to include custom parameters in the factory method that are not all managed by Spring you can try one of the following sketched below如果您想在工厂方法中包含并非全部由Spring管理的自定义参数,您可以尝试以下草图之一

  1. ensure a Bean can be instantiated when first discovered by adding empty constructors通过添加空的构造函数确保第一次发现 Bean 时可以实例化
@Component("One")
@Scope("prototype")
public class MyServiceOne implements MyService {
 //Code
   public MyServiceOne(){
   ...
   }

   public MyServiceOne(Dependency dep){
   ...
   }

   public MyServiceOne(Dependency dep, Integer myFactoryValue){
   ...
   }
}

  1. Or you manually create them in the configuration to be discovered或者您在要发现的配置中手动创建它们
\\ no longer available in autoscan
public class MyServiceOne implements MyService {
 //Code
   public MyServiceOne(){
   ...
   }

   public MyServiceOne(Dependency dep, Integer myFactoryValue){
   ...
   }
}

@Configuration
public class Config {
       @Bean("One")
       @Scope("prototype")      
       Service serviceOne(){
            // used only for dynamic discovery
            return new ServiceOne();
       }
       ...

       @Bean
       Factory languageFactory(Map<String,Service> map, ApplicationContext context, Dependency dep){
        return new Factory(map,context,dep);
    }
}

And both solutions allow you to define your factory method like this这两种解决方案都允许您像这样定义工厂方法

public class Factory {
    ....

    MyService service(String type, Integer someParameter){
        // you provide the parameters for the constructor
        return context.getBean(services.get(type).getClass(),dep,someParameter);
    }
} 

Spring won't Autowire beans if there isn't a clear path as to which one it should use at build time.如果没有明确的路径来确定它应该在构建时使用哪一个,Spring 不会自动装配 bean。 Since the factory doesn't change, you can Autowire your LocationService there and pass it down to your different services.由于工厂没有改变,您可以在那里自动连接您的 LocationService 并将其传递给您的不同服务。 This can get a bit cumbersome if your classes have more than one dependency eg services, repos, etc.如果您的类有多个依赖项,例如服务、存储库等,这可能会有点麻烦。

If you don't intend on having a lot of dependencies for your "MyService" classes you can do this:如果您不打算对“MyService”类有很多依赖项,您可以这样做:

@Component
public class MyServiceFactory(){

    @Autowired
    LocationService locationService;
    
    public static MyService getMyService(String service){
        service = service.toLowerCase();
        switch(service){
            case "one":
                return new MyServiceOne(locationService);
            case "two":
                return new MyServiceTwo(locationService);
            case "three":
                return new MyServiceThree(locationService);
            default:
                return new MyServiceDefault(locationService);
        }
    }
}

Your MyServiceOne class:您的 MyServiceOne 课程:

@Service
public class MyServiceOne implements MyService{

    public LocationService locationService;

    public MyServiceOne(LocationService service){
        locationService = service;
    }

    @Override
    public checkStatus(){
        // your code
    }
}

MyService interface我的服务接口

interface MyService{
    boolean checkStatus();
}
public interface MyService {
    public void save(); 
//Code
}

@Component("One")
public class MyServiceOne implements MyService {
 //Code
    public void save(){
      System.out.println("one");
    }
}

@Component("Two")
public class MyServiceTwo implements MyService {
 //Code
    public void save(){
      System.out.println("two");
    }
}

public class FatoryClass{
    @Autowired
   //@Qualifier("One") //This is your default child class use qualifier or just my sample
    MyService One;
    public MyService setMyservice(int typeId){
      switch(typeId){
          case 1:
            One = new MyServiceTwo();
            break;
          default:
            System.out.println("Default child instance");
      }
      return One;
    }
}

@Service
public class serviceComponent{
    @Autowired
    FatoryClass facto;

    public void setFactoryMethod(int typeId){
        facto.setMyService(typeId);
        facto.save();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM