Как создать простой шаблон фабрики с автоматически настроенными бобами весной? - PullRequest
0 голосов
/ 10 января 2019

У меня был контроллер с 4 очень похожими методами, который вызывал API на удаленном сервере для выполнения различных действий с разными типами пользователей. Что изменилось между этими вызовами API, так это только конечная точка и некоторые параметры.

Таким образом, все эти 4 метода вызвали службы с очень похожим кодом: они получили токен с сервера, задали параметры, возвратили ответ API. Поскольку дополнительные действия будут добавлены позже , я решил использовать создание ServiceFactory с использованием шаблона Factory Method и использовать шаблон Template на сервисах, чтобы избежать дублирования кода.

Моя проблема в том, что для того, чтобы фабрика автоматически подключала сервисы, она должна быть связана с ними, я должен @Autowire каждую реализацию. Есть ли лучшее решение?

Вот код, который у меня есть:

Контроллер покоя

@RestController
public class ActionController {
  @Autowired
  private SsoService ssoService;

  // this is the factory
  @Autowired
  private ServiceFactory factory;

  @PostMapping("/action")
  public MyResponse performAction(@RequestBody MyRequest request, HttpServletRequest req) {
    // template code (error treatment not included)
    request.setOperator(ssoService.getOperator(req));
    request.setDate(LocalDateTime.now());
    return serviceFactory.getService(request).do();
  }
}

Сервисный завод

@Component
public class ServiceFactory {

  @Autowired private ActivateUserService activateUserService;
  @Autowired private Action2UserType2Service anotherService;
  //etc

  public MyService getService(request) {
    if (Action.ACTIVATE.equals(request.getAction()) && UserType.USER.equals(request.getUserType()) {
      return activateUserService;
    }
    // etc
    return anotherService;
  }
}

Сервисная база, реализующая интерфейс MyService

public abstract class ServiceBase implements MyService {

  @Autowired private ApiService apiService;
  @Autowired private ActionRepository actionRepository;
  @Value("${api.path}") private String path;

  @Override
  public MyResponse do(MyRequest request) {
    String url = path + getEndpoint();
    String token = apiService.getToken();

    Map<String, String> params = getParams(request);
    // adds the common params to the hashmap

    HttpResult result = apiService.post(url, params); 
    if (result.getStatusCode() == 200) {
      // saves the performed action
      actionRepository.save(getAction());
    }
    // extracts the response from the HttpResult
    return response;
  }
}

Реализация сервиса (есть 4)

@Service
public class ActivateUserService extends ServiceBase {
  @Value("${api.user.activate}")
  private String endpoint;

  @Override
  public String getEndpoint() {
    return endpoint;
  }

  @Override
  public Map<String,String> getParams(MyRequest request) {
    Map<String, String> params = new HashMap<>();
    // adds custom params
    return params;
  }

  @Override
  public Action getAction() {
    return new Action().type(ActionType.ACTIVATED).userType(UserType.USER);
  }
}

1 Ответ

0 голосов
/ 10 января 2019

Вы можете @Autowired a List из MyService, что создаст List всех bean-компонентов, которые реализуют интерфейс MyService. Затем вы можете добавить метод к MyService, который принимает объект MyRequest и решает, сможет ли он обработать этот запрос. Затем вы можете отфильтровать List из MyService, чтобы найти первый MyService объект, который может обработать запрос.

Например:

public interface MyService {

    public boolean canHandle(MyRequest request);

    // ...existing methods...
}

@Service
public class ActivateUserService extends ServiceBase {

    @Override
    public boolean canHandle(MyRequest request) {
        return Action.ACTIVATE.equals(request.getAction()) && UserType.USER.equals(request.getUserType());
    }

    // ...existing methods...
}

@Component
public class ServiceFactory {

    @Autowired
    private List<MyService> myServices;

    public Optional<MyService> getService(MyRequest request) {
        return myServices.stream()
            .filter(service -> service.canHandle(request))
            .findFirst();
    }
}

Обратите внимание, что реализация ServiceFactory выше использует Java 8+. Если это невозможно для Java 8 или выше, вы можете реализовать класс ServiceFactory следующим образом:

@Component
public class ServiceFactory {

    @Autowired
    private List<MyService> myServices;

    public Optional<MyService> getService(MyRequest request) {

        for (MyService service: myServices) {
            if (service.canHandle(request)) {
                return Optional.of(service);
            }
        }

        return Optional.empty();
}

Для получения дополнительной информации об использовании @Autowired с List см. Автоссылка эталонных компонентов в список по типу .


Ядро этого решения перемещает логику принятия решения, может ли реализация MyService обрабатывать MyRequest от ServiceFactory (внешний клиент) до самой реализации MyService.

...