【问题标题】:Implement a simple factory pattern with Spring 3 annotations使用 Spring 3 注解实现一个简单的工厂模式
【发布时间】:2011-09-17 11:05:39
【问题描述】:

我想知道如何使用 Spring 3 注释实现简单工厂模式。我在文档中看到您可以创建调用工厂类并运行工厂方法的 bean。我想知道这是否可能仅使用注释。

我有一个当前调用的控制器

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

MyService 是一个接口,其中包含一个名为 checkStatus() 的方法。

我的工厂类如下所示:

@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 类如下所示:

@Autowired
private LocationService locationService;

public boolean checkStatus() {
      //do stuff
}

当我运行此代码时,locationService 变量始终为空。我相信这是因为我自己在工厂内创建对象并且没有进行自动装配。有没有办法添加注释以使其正常工作?

谢谢

【问题讨论】:

  • 来晚了,但我认为此信息可能对其他人有所帮助。您可以在 Spring 中使用 ServiceLocatorFactoryBean of。处理工厂模式真的很巧妙。
  • 你找到正确答案了吗?这里有很多错误的答案

标签: java spring annotations factory-pattern


【解决方案1】:

如果没有明确的路径来确定在构建时应该使用哪一个,Spring 不会自动装配 bean。由于工厂没有改变,您可以在那里自动连接您的 LocationService 并将其传递给您的不同服务。如果您的类有多个依赖项,例如,这可能会有点麻烦。服务、回购等。

如果您不打算对“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);
        }
    }
}

你的 MyServiceOne 类:

@Service
public class MyServiceOne implements MyService{

    public LocationService locationService;

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

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

我的服务接口

interface MyService{
    boolean checkStatus();
}

【讨论】:

    【解决方案2】:

    这是创建新实例的上述答案的变体。

    如果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);
        }
    }
    
    
    
    

    如果您想在工厂方法中包含并非全部由 Spring 管理的自定义参数,您可以尝试以下草图之一

    1. 通过添加空的构造函数确保 Bean 在首次发现时可以被实例化
    @Component("One")
    @Scope("prototype")
    public class MyServiceOne implements MyService {
     //Code
       public MyServiceOne(){
       ...
       }
    
       public MyServiceOne(Dependency dep){
       ...
       }
    
       public MyServiceOne(Dependency dep, Integer myFactoryValue){
       ...
       }
    }
    
    
    1. 或者您在要发现的配置中手动创建它们
    \\ 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);
        }
    }
    
    

    这两种解决方案都允许您像这样定义工厂方法

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

    【讨论】:

      【解决方案3】:

      我最近处理了类似的需求,我想使用工厂模式,但我对 if else 逻辑不满意,它在未来会继续增长并违反单一责任原则。

      第一步,创建一个接口并拥有一个 getType() 方法,在给定的上下文中它将返回“one”、“two”等,否则它可以是任何东西。 这是上面大多数人建议的常见解决方案。

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

      一些实现:

      @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
          }
      }
      

      工厂本身如下:

      @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));
              
          }
      }
      

      此解决方案不需要额外的 Map 来根据类型存储实例的键值。此解决方案无需任何进一步的代码更改即可扩展,因为工厂具有 List 自动布线,因此 MyService 的任何未来实现都将很容易工作。因此也保证了单一职责原则。

      我在使用 Java 8 时使用了流()和谓词,因为早期版本简单的 for 循环就可以完成这项工作。

      【讨论】:

        【解决方案4】:

        遵循DruidKumajumping_monkey 的回答

        您还可以包含可选的,让您的代码更漂亮、更简洁:

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

        【讨论】:

          【解决方案5】:

          关注DruidKuma的回答

          使用自动装配的构造函数对他的工厂进行少量重构:

          @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;
              }
          }
          

          【讨论】:

          • Nice Pavel,锦上添花,考虑将构造函数的可访问性更改为私有,因为类不需要从外部构造/访问,Spring 仍然可以做到通过反射自动接线。我测试了它,它可以工作。
          • @jumping_monkey 'List services' 将在何时何地被初始化?我没有看到变量 'List services' 的初始化
          • @FerrySanjaya,这就是@Autowired 的美妙之处,它很神奇。所有实现“MyService”的类都将自动连接到服务List。试试看。
          • 在这种情况下测试呢?如果构造函数是私有的,你将如何进行单元测试?
          【解决方案6】:

          你可以手动让 Spring 自动装配它。

          让您的工厂实现 ApplicationContextAware。然后在您的工厂中提供以下实现:

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

          然后在创建 bean 后执行以下操作:

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

          这将完美地自动连接您的 LocationService。

          【讨论】:

            【解决方案7】:

            基于 Pavel Černý here 的解决方案 我们可以对这种模式进行通用类型化的实现。 为此,我们需要引入 NamedService 接口:

                public interface NamedService {
                   String name();
                }
            

            并添加抽象类:

            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;
                }
            }
            

            然后我们创建一个具体对象的具体工厂,例如 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 列出编译时 MyService 接口的实现列表。

            如果您在应用程序中有多个按名称生成对象的类似工厂(如果按名称生成对象当然足以满足您的业务逻辑),则此方法可以正常工作。 这里的 map 可以很好地使用 String 作为键,并保存您服务的所有现有实现。

            如果您有不同的逻辑来生成对象,这个额外的逻辑可以移动到另一个地方,并与这些工厂(通过名称获取对象)结合使用。

            【讨论】:

              【解决方案8】:

              试试这个:

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

              【讨论】:

                【解决方案9】:

                您还可以声明性地定义一个 ServiceLocatorFactoryBean 类型的 bean,它将充当工厂类。 Spring 3 支持它。

                一个 FactoryBean 实现,它采用一个接口,该接口必须具有一个或多个带有签名的方法(通常是 MyService getService() 或 MyService getService(String id)),并创建一个实现该接口的动态代理

                这里是an example of implementing the Factory pattern using Spring

                One more clearly example

                【讨论】:

                【解决方案10】:

                您可以通过将所有服务类作为参数传递来实例化“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);
                    }
                }
                

                【讨论】:

                • 如何在静态函数中引用 applicationContext ...?它给了我一个错误,因为它也不是静态的
                • 哦,是的,你显然是对的。一个简单的错误。我刚刚从方法“getMyService”中删除了“静态”
                • 如果我在 diff 实现类中有带 diff 参数的参数化构造函数,如何遵循上述技术?
                【解决方案11】:

                为什么不将接口FactoryBean添加到MyServiceFactory(告诉Spring它是一个工厂),然后添加一个寄存器(字符串服务,MyService实例),然后让每个服务调用:

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

                这样,您可以在必要时将每个服务提供者分成模块,Spring 会自动选取任何已部署和可用的服务提供者。

                【讨论】:

                • 我喜欢这个解决方案,只是为了扩展一点,而不是使用抽象 MyService 接口,它将自动装配工厂并执行 PostConstruct。所以你的服务是干净的。
                • 只是一个警告,我在使用@Transactional 注释注释的方法为spring服务类实现类似代码时遇到了一个问题。基本上,当您使用 `this` 关键字注册一个类并且该类被代理时,传递给您的服务工厂的类不会被代理。见spring.io/blog/2012/05/23/…
                【解决方案12】:

                以下内容对我有用:

                接口由逻辑方法和附加标识方法组成:

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

                一些实现:

                @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
                    }
                }
                

                工厂本身如下:

                @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;
                    }
                }
                

                我发现这样的实现更简单、更简洁且更具可扩展性。添加新的 MyService 就像创建另一个实现相同接口的 spring bean 一样简单,而无需在其他地方进行任何更改。

                【讨论】:

                • “private List services”是从哪里初始化的。它需要由 Spring 创建和管理 bean,而不是手动创建的 bean。
                • @sunilbhardwaj 如果您至少使用 Spring 2 来启用基于注释的处理,Spring 将自行创建和管理 bean,并将相应的注释放在您需要的类之上。这里 Component 和 Service 注解会告诉 Spring 创建相应类的 bean 实例
                • @DruidKuma 很棒的实现,但我认为默认情况下该服务是单例的,所以也许您可以删除静态部分。消费者可以将您的工厂与 Autowired 一起用作标准服务...
                • 因为魔法没有发生,收集所有实现接口的服务未连接:对我来说,它抛出异常:没有'java.util.List' 可用:预计至少有 1 个符合自动装配候选资格的 bean。依赖注解:{@org.springframework.beans.factory.annotation.Autowired(required=true)}
                • 这在将@Component 替换为@Service 之前不起作用。休息很好。
                【解决方案13】:

                我想你使用 org.springframework.beans.factory.config.ServiceLocatorFactoryBean。 这将大大简化您的代码。 除了 MyServiceAdapter 之外,您只能使用 MyService getMyService 方法和别名来创建接口 MyServiceAdapter 来注册您的类

                代码

                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" />
                

                【讨论】:

                  【解决方案14】:

                  你是对的,通过手动创建对象你不会让 Spring 执行自动装配。考虑通过 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;
                          }
                      }
                  }
                  

                  但我认为整体设计相当糟糕。拥有一个通用的MyService 实现并将one/two/three 字符串作为额外参数传递给checkStatus() 不是更好吗?你想达到什么目标?

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

                  仍然设计不佳,因为添加新的 MyService 实现还需要修改 MyServiceAdapter(违反 SRP)。但这实际上是一个很好的起点(提示:地图和策略模式)。

                  【讨论】:

                  • 如果你打算这样做,为什么不去掉中间人,直接将不同的服务类型自动装配到调用工厂的 bean 中?
                  • 好问题。也许是因为我们想将客户端与不同的实现分离?看看我在此期间所做的编辑。
                  • 您的建议使代码正常工作。我最初将代码放在一个类中,但我认为稍后添加实现 MyService 接口的服务会更容易,我只需要更改工厂类中的代码。
                  • 第一个选项不起作用,因为 getMyService() 是静态的。因此 myServiceOne、myServiceTwo 也必须是静态的。
                  • 什么?如果我的服务有 10 种方法?您是否要在“适配器”中实现所有这些(这是一个误导性名称)
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-11-14
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-03-13
                  相关资源
                  最近更新 更多