Преобразование автоматически в централизованный компонент для нескольких объектов домена
Я создаю проект, который будет реагировать на сбор нескольких объектов bean, сохранить его в базе данных и вернуть статус транзакции. Может быть несколько объектов, которые могут быть отправлены клиентом. Для каждого объекта они имеют отдельную базу данных, таким образом, отдельный контроллер.
Поэтому я планировал создать фреймворк, который может принимать несколько объектов из нескольких контроллеров и отправлять только один централизованный объект. Но я не уверен, как использовать централизованный объект в качестве возвращаемого типа в контроллере (в настоящее время я возвращал их как Object
). Ниже мой код:
контроллер:
@RestController
@RequestMapping("/stat/player")
public class PlayerController {
@Autowired
private StatService<PlayerValue> statPlayer;
@RequestMapping("/number/{number}")
public Object findByNumber(@PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Обслуживание:
@Service
@Transactional(isolation = Isolation.READ_COMMITTED)
public class PlayerServiceImpl implements StatService<PlayerValue> {
@Autowired
private PlayerRepository repository;
@Override
public PlayerValue findByNumber(String number) {
Optional<PlayerEntity> numberValue = repository.findByNumber(number);
return numberValue.map(PlayerEntity::toValue).orElse(null);
}
}
В службе я вернул объект PlayerValue
но я хочу, чтобы этот объект был PlayerValue
в централизованный компонент ResponseValue. Я создал для этого
@Aspect
@Component
public class Converter {
private static final Logger LOG = LoggerFactory.getLogger(Converter.class);
@Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
public void restControllerClassMethod() {}
private <T> ResponseValue<T> convert(List<T> results) {
String message = results.isEmpty() ? "No result found" : ResponseValueStatus.OK.toString();
return new ResponseValue<>(ResponseValueStatus.OK, message, results);
}
@Around("restControllerClassMethod()")
@SuppressWarnings("unchecked")
public <T> ResponseValue<T> convert(ProceedingJoinPoint joinPoint) {
ResponseValue value;
try {
Object findObject = joinPoint.proceed();
List<Object> objects = toList(findObject);
value = convert(objects);
} catch (NullPointerException e) {
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString()));
//this exception will go in a controller advice and create a response value with this message
} catch (Throwable e) {
LOG.error("Exception occurred while converting the object", e);
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s with exception message %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString(), e.getMessage()));
}
return value;
}
private List<Object> toList(Object findObject) {
List<Object> objects = new ArrayList<>();
if (findObject instanceof List) {
((List) findObject).forEach(item -> objects.add(findObject));
} else {
objects.add(findObject);
}
return objects;
}
}
Подводя итог, может существовать несколько объектов, похожих на PlayerValue. Мне нужен способ вернуть результат в централизованном компоненте. Над работой над процессом, НО для этого я должен предоставить возвращаемый тип как Object
in Controller
. У кого-нибудь есть идея, как я могу использовать возвращаемый тип как List или T в контроллере. Также я знаю, что это можно сделать, реализовав интерфейс ValueConverter
, но это преобразование является простым. Поэтому было бы неплохо, если бы любой другой разработчик не должен был внедрять ValueConverter
каждый раз, когда он хочет добавить другой контроллер.
Также не стесняйтесь ознакомиться с реализацией и дайте мне знать, есть ли у кого-нибудь альтернативные идеи или комментарии.
Примечание. Я уменьшаю количество кода в вопросе, чтобы его было легче понять, не понимая контекста фактических требований. Пожалуйста, дайте мне знать, если кому-то нужна дополнительная информация.
Ответы
Ответ 1
После некоторых исследований я столкнулся с лучшим дизайнерским решением для фреймворка (но, конечно, с недостатками), чтобы добиться преобразования в централизованный компонент для нескольких доменных объектов - использовать интерфейс маркера.
Интерфейс маркера может обеспечить централизованный тип для всех компонентов. Следующее правило должно сопровождаться тем, что клиент должен реализовать этот интерфейс маркера. Таким образом, основное решение
Интерфейс маркера:
public interface StateResponseValue<T> {
}
Внедрите интерфейс во всех компонентах.
public class PlayerValue implements StateResponseValue<PlayerValue> {
}
public class ResponseValue<T> implements StateResponseValue<T> {
//fields and their getter and setter
}
Измените тип возврата в службе и контроллере.
public interface StatService<T> {
StateResponseValue<T> findByNumber(String number);
}
Изменение типа возврата в контроллере
@RestController
@RequestMapping("/stat/player")
public class PlayerController {
@Autowired
private StatService<PlayerValue> statPlayer;
@RequestMapping("/number/{number}")
public StateResponseValue<T> findByNumber(@PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Примечание. Главный недостаток, который я чувствую, заключается в том, что всякий раз, когда мы хотим получить доступ к клиенту поля, необходимо явно передать объект ResponseValue
который все еще довольно уродлив.
Ответ 2
Что делать, если вы создаете генератор AbstractStatController
который является общим?
Общий интерфейс StatService
:
public interface StatService<T> {
T findByNumber(String number);
}
Общий абстрактный класс AbstractStatController
:
public abstract class AbstractStatController<T> {
abstract StatService<T> getStatService();
@RequestMapping("/number/{number}")
public T findByNumber(@PathVariable String number) {
return getStatService().findByNumber(number);
}
}
Конкретный класс PlayerController
:
@RestController
@RequestMapping("/stat/player")
public class PlayerController extends AbstractStatController<Player> {
private final PlayerService playerService;
public PlayerController(PlayerService playerService) {
this.playerService = playerService;
}
@Override
StatService<Player> getStatService() {
return playerService;
}
}