如何在Spring中使用接口的动态实现?

时间:2016-05-24 08:24:54

标签: java spring spring-mvc

此问题旨在为有用的问题提供答案。

假设我们有一个带有@Controller的Spring应用程序,一个接口以及该接口的不同实现。

我们希望@Controller根据我们收到的请求使用具有正确实现的接口。

这是@Controller:

@Controller
public class SampleController {
    @RequestMapping(path = "/path/{service}", method = RequestMethod.GET)
    public void method(@PathVariable("service") String service){
        // here we have to use the right implementation of the interface
    }
}

这是界面:

public interface SampleInterface {
    public void sampleMethod(); // a sample method
}

这是可能的实施之一:

public class SampleInterfaceImpl implements SampleInterface {
    public void sampleMethod() {
        // ...
    }
}

这是另一个:

这是可能的实施之一:

public class SampleInterfaceOtherImpl implements SampleInterface {
    public void sampleMethod() {
        // ...
    }
}

下面我将展示我发现根据请求动态使用其中一个实现的解决方案。

4 个答案:

答案 0 :(得分:2)

我找到的解决方案就是这个。

首先,我们必须在@Controller中自动装配ApplicationContext。

@Autowired
private ApplicationContext appContext;

其次,我们必须在接口的实现中使用@Service注释。 在示例中,我给它们命名为“Basic”和“Other”。

@Service("Basic")
public class SampleInterfaceImpl implements SampleInterface {
    public void sampleMethod() {
        // ...
    }
}

@Service("Other")
public class SampleInterfaceOtherImpl implements SampleInterface {
    public void sampleMethod() {
        // ...
    }
}

接下来,我们必须在@Controller中获取实现。 这是一种可能的方式:

@Controller
public class SampleController {
    @Autowired
    private ApplicationContext appContext;

    @RequestMapping(path = "/path/{service}", method = RequestMethod.GET)
    public void method(@PathVariable("service") String service){
        SampleInterface sample = appContext.getBean(service, SampleInterface.class);
        sample.sampleMethod();
    }
}

通过这种方式,Spring在动态上下文中注入了正确的bean,因此通过正确的实现来解析接口。

答案 1 :(得分:0)

我解决了这个问题:

  • 让界面实现方法supports(...)并将List<SampleInterface>注入您的控制器。
  • 在控制器中创建一个方法getCurrentImpl(...),以便在supports
  • 的帮助下解决它
  • 从Spring 4开始,如果您实施Ordered界面或使用注释@Order,则会订购自动装配列表。

这样您就不需要显式使用ApplicationContext。

答案 2 :(得分:0)

我不相信你的解决方案,因为HTTP参数值和bean限定符之间存在隐式链接。无意中更改bean名称将导致灾难,调试可能很棘手。我会将所有必要的信息封装在一个地方,以确保只需要在一个bean中完成任何更改:

@Controller
public class SampleController {
    @Autowired
    private SampleInterfaceImpl basic;

    @Autowired
    private SampleInterfaceOtherImpl other;

    Map<String, SampleInterface> services;

    @PostConstruct
    void init() {
        services = new HashMap()<>;
        services.put("Basic", basic);
        services.put("Other", other);
    }

    @RequestMapping(path = "/path/{service}", method = RequestMethod.GET)
    public void method(@PathVariable("service") String service){
        SampleInterface sample = services.get(service);
        // remember to handle the case where there's no corresponding service
        sample.sampleMethod();
    }
}

此外,依赖ApplicationContext对象会使测试变得更加复杂。

NB。为了使它更健壮,我使用枚举而不是“基本”和“其他”字符串。

但是,如果你知道你只有两种类型的服务可供选择,那就是“保持简单愚蠢”的方式:

@Controller
public class SampleController {
    @Autowired
    private SampleInterfaceImpl basic;

    @Autowired
    private SampleInterfaceOtherImpl other;

    @RequestMapping(path = "/path/Basic", method = RequestMethod.GET)
    public void basic() {
        basic.sampleMethod();
    }

    @RequestMapping(path = "/path/Other", method = RequestMethod.GET)
    public void other() {
        other.sampleMethod();
    }
}

答案 3 :(得分:0)

老实说,我不认为在URL中公开内部实现细节只是为了避免编写一些代码行是好的。 @kriger提出的解决方案至少使用键/值方法添加一个间接步骤。

我更愿意创建一个工厂Bean(更加面向企业,甚至是抽象工厂模式),它将选择使用哪个具体实现。 通过这种方式,您可以使用任何自定义逻辑在单独的位置(工厂方法)中选择接口。 并且您将能够将服务URL与具体实现分离(这不是非常安全)。

如果您正在创建一个非常简单的服务,您的解决方案将起作用,但在企业环境中,使用模式对于确保可维护性和可伸缩性至关重要。