2016-05-24 3 views
1

Этот вопрос предназначен для ответа на полезную проблему.Как использовать динамическую реализацию интерфейса весной?

Предположим, у нас есть приложение Spring с @Controller, интерфейс и различные реализации этого интерфейса.

Мы хотим, чтобы @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 
} 

Вот один из реализации possibile:

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

А вот еще один:

Вот один из реализации possibile:

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

Ниже я покажу решение, которое я нашел, чтобы использовать один из реализаций динамически на основании запроса.

ответ

2

Решение, которое я нашел, является этим.

Во-первых, мы должны автоустановить ApplicationContext в @Controller.

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

Таким образом, весна впрыскивает правый боб в динамическом контексте, поэтому интерфейс будет решен с должным inmplementation.

+0

Аргумент 'String service' метода имеет значение ** Basic ** или ** Other ** во время выполнения? Вы говорите, что вызывающий Службой должен передать ** Basic ** или ** Other ** при вызове? – STaefi

+0

Да, в этом случае служебная переменная имеет одно из этих значений. Но вы всегда можете сделать чек раньше. –

+0

Хотя для Caller of the service почему-то странно знать об этих константах, всю идею можно манипулировать продуктивным образом. +1 для вашей документации. – STaefi

0

Я решил эту проблему так:

  • Пусть интерфейс реализован метод supports(...) и впрыснуть List<SampleInterface> в контроллер.
  • создать метод getCurrentImpl(...) в контроллере, чтобы решить ее с помощью supports
  • с весны 4, autowired список будет упорядоченным, если вы реализуете интерфейс Ordered или использовать аннотацию @Order.

Таким образом, вам не нужно явно использовать ApplicationContext.

0

Я не уверен в вашем решении, потому что существует неявная связь между значением параметра HTTP и спецификатором компонента. Невинная смена имени компонента приведет к катастрофе, которая может быть сложной для отладки.Я бы инкапсулировать всю необходимую информацию в одном месте, чтобы обеспечить любые изменения должны только быть сделано в одном компоненте:

@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. чтобы сделать его более надежным, я бы использовал перечисления вместо строк «Basic» и «Other».


Однако, если вы знаете, что есть только два типа сервиса на выбор, это будет «держать его просто глупо» путь:

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

Вы можете попытаться захватить BeansException для контроля, если параметр HTTP не соответствует ни одному компоненту приложения. –

0

Честно говоря, я не подумайте, что идея разоблачения внутренних деталей реализации в URL-адресе просто для того, чтобы избежать написания некоторых строк кода. Решение, предлагаемое @kriger, по крайней мере добавляет один шаг косвенности, используя подход «ключ/значение».

Я бы предпочел создать фабричный боб (чтобы быть еще более ориентированным на предприятие, даже шаблон абстрактной фабрики), который выберет конкретную реализацию для использования. Таким образом, вы сможете выбрать интерфейс в отдельном месте (фабричный метод), используя любую пользовательскую логику, которую вы хотите. И вы сможете отделить URL-адрес службы от конкретной реализации (что не очень безопасно).

Если вы создаете очень простой сервис, ваше решение будет работать, но в корпоративной среде использование шаблонов жизненно важно для обеспечения поддержки и масштабируемости.

Смежные вопросы