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

Posted

技术标签:

【中文标题】使用 Spring 3 注解实现一个简单的工厂模式【英文标题】:Implement a simple factory pattern with Spring 3 annotations 【发布时间】: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。处理工厂模式真的很巧妙。 你找到正确答案了吗?这里有很多错误的答案 【参考方案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 管理的自定义参数,您可以尝试以下草图之一

    通过添加空的构造函数确保 Bean 在首次发现时可以被实例化
@Component("One")
@Scope("prototype")
public class MyServiceOne implements MyService 
 //Code
   public MyServiceOne()
   ...
   

   public MyServiceOne(Dependency dep)
   ...
   

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


    或者您在要发现的配置中手动创建它们
\\ 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】:

遵循DruidKuma 和jumping_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

【讨论】:

ServiceLocatorFactoryBean 的另一个例子也可以在这里找到:jcombat.com/spring/…【参考方案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 种方法?您是否要在“适配器”中实现所有这些(这是一个误导性名称)

以上是关于使用 Spring 3 注解实现一个简单的工厂模式的主要内容,如果未能解决你的问题,请参考以下文章

Spring中如何使用工厂模式实现程序解耦?

Day51~(spring注解高级IOC)51

Spring实现事务管理

设计模式结合spring框架实现简单工厂模式

设计模式结合spring框架实现简单工厂模式

深入理解设计模式-策略模式(结合简单工厂反射Spring详细讲解)