java-使用Spring 3注释实现简单的工厂模式

我想知道如何用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变量始终为null。 我相信这是因为我是在工厂内部自己创建对象,并且不会自动装配。 有没有添加注释以使其正常工作的方法?

谢谢

blong824 asked 2020-08-05T20:04:00Z
10个解决方案
66 votes

以下为我工作:

该接口由逻辑方法和其他标识方法组成:

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一样简单,而无需在其他地方进行任何更改。

DruidKuma answered 2020-08-05T20:04:53Z
33 votes

是的,通过手动创建对象,您不会让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实现并将MyServiceAdapter/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)。 但这实际上是一个很好的起点(提示:地图和策略模式)。

Tomasz Nurkiewicz answered 2020-08-05T20:04:15Z
9 votes

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

@Autowired
MyServiceFactory serviceFactory;

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

这样,您可以根据需要将每个服务提供商分成模块,Spring会自动选择所有已部署和可用的服务提供商。

Jeff Wang answered 2020-08-05T20:05:18Z
8 votes

您可以手动要求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接线。

rogermenezes answered 2020-08-05T20:05:51Z
6 votes

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

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

这是一个使用Spring实现Factory模式的示例

一个更清楚的例子

kyiu answered 2020-08-05T20:06:25Z
4 votes

以下DruidKuma的回答

用自动装配的构造函数对其工厂进行少许重构:

@Service
public class MyServiceFactory {

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

    @Autowired
    public 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;
    }
}
Pavel Černý answered 2020-08-05T20:06:49Z
2 votes

我想您使用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" />
Mikhail answered 2020-08-05T20:07:14Z
1 votes

您可以通过传递所有服务类作为参数来实例化“ 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);
    }
}
OlivierTerrien answered 2020-08-05T20:07:34Z
1 votes

基于此处的PavelČerný的解决方案我们可以对此模式进行通用类型的实现。为此,我们需要引入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);
    }
}

其中列出了在编译时MyService接口的实现列表。

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

如果您有不同的逻辑来生成对象,则可以将此附加逻辑移至其他位置,并与这些工厂(按名称获取对象)结合使用。

Andreas Gelever answered 2020-08-05T20:08:16Z
0 votes

试试这个:

public interface MyService {
 //Code
}

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

@Component("Two")
public class MyServiceTwo implements MyService {
 //Code
}
Viren Dholakia answered 2020-08-05T20:08:36Z
translate from https://stackoverflow.com:/questions/6390810/implement-a-simple-factory-pattern-with-spring-3-annotations