繁体   English   中英

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

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

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

谢谢

以下对我有用:

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

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

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

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

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

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

@Autowired
MyServiceFactory serviceFactory;

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

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

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

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

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

一个更清楚的例子

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

where 列出编译时 MyService 接口的实现列表。

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

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

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

我想你使用 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" />

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

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

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 循环就可以完成这项工作。

尝试这个:

public interface MyService {
 //Code
}

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

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

我想知道在第三篇文章的续篇(DruidKuma发表)中,将从何处创建或注入“服务”对象? -是从MyServiceFactory创建或注入的地方?

这里需要创建/注入“服务”对象,并且需要将所有其他服务对象添加到列表中,这一步需要从哪里注入工厂对象?

@Service
public class MyServiceFactory {

    @Autowired
    private List<MyService> services;

遵循DruidKumajumping_monkey的回答

您还可以包含可选的,并使您的代码更漂亮和更清洁:

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

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

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

如果没有明确的路径来确定它应该在构建时使用哪一个,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();
}
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