Java, как хранить объект различного типа

В настоящее время я использую Spring MVC и Hibernate для разработки моего веб-приложения. Все еще изучает внутреннюю работу Java.

Я нахожу себя в ситуации, когда мне нужно хранить данные в поле, которое может принимать разные типы объектов вместе с некоторыми другими строками данных. Точнее, я хочу создать объект ReturnObject, который может содержать сообщения, код ошибки... и т.д. Таким образом, мое возвращение JSON может оставаться постоянным через api.

Вот как я создал свой ReturnObject

public class ReturnResponse {

//Set fields
private Object returnObj;      <----- Need this to accept different types
private HttpStatus httpStatus;
private String message;
private String developerMessage;

// Start build

public ReturnResponse(){
    this.returnObj =        returnObj;
    this.httpStatus =.....etc.
}

// Setters... getters...
}

private Object returnObj;, чтобы это поле могло принимать Collection, Maps, Class..etc, но безопасно ли это?

Кажется, что я помню, что всегда хорошая практика объявлять конкретный тип объекта в поле для обеспечения безопасного типа.

Вопросы

  • Видите ли вы прогнозируемую проблему в будущем, есть ли лучший способ сделать это?

  • Если это не безопасный тип, как бы я сделал его более безопасным.

Ответ 1

Вы можете использовать общий:

public class ReturnResponse<ObjectType> {

    private ObjectType returnObj;
    private HttpStatus httpStatus;
    private String message;
    private String developerMessage;

    public ReturnResponse(ObjectType returnObj, /*other parameters...*/) {
        this.returnObj = returnObj;
        // set other parameters
    }

    public ObjectType getReturnObj() {
        return this.returnObj;
    }

    // ...
}

Он будет работать, если вы знаете, когда вы инициируете свой ReturnResponse тип возвращаемого объекта. Я использую этот шаблон в большинстве своих API без проблем.

Ответ 2

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

Проблема в том, что вам нужно каким-то образом расшифровать код в своем коде, какой тип объекта - который в большинстве случаев, я думаю, будет нежелательным и потребует небезопасного кастинга.

Единственный способ, который я могу придумать, чтобы сделать его более безопасным, - сделать какую-то оболочку вроде:

public class Bean {
   private String string;
   private Integer integer;
   private List<String> list;
   private Bicycle bicycle;


   //setters
   //...

   public Optional<Bicycle> getBicycle() {
      return Optional.ofNullable(bicycle);
   }

   //... and so on...
}

Ответ 3

Обработчик ошибок должен находиться в контроллере, и он должен ответить на ошибку http. Это означает правильный HTTP-статус ошибки и желаемое сообщение об ошибке. Ошибка не должна выглядеть как успешный запрос (нет кода состояния 200). Это ошибка. В своем интерфейсе вы должны соответствующим образом обрабатывать ответ об ошибке http.

С spring это может быть очень легко сделать. Вот пример обработчика ошибок моего проекта. Это собственный класс с аннотацией @ControllerAdvice. spring будет автоматически использовать это.

Этот класс автоматически поймает любое необработанное исключение, которое определено с помощью @ExceptionHandler, и отправьте в моем случае a ShortExceptionResponse, который содержит тип и сообщение об исключении, с правильным статусом ошибки Http, определенным с помощью @ResponseStatus.

В своем интерфейсе вы можете реагировать на различные коды ошибок HTTP-статуса. Это приятный и общий.

@ControllerAdvice
public class RestExceptionResponseHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(SetRestController.class);

    @ExceptionHandler(NoSuchElementException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public @ResponseBody
    ShortExceptionResponse noSuchElementExceptionHandler(Exception ex) {
    LOGGER.error("An error occured processing a rest request", ex);
    return new ShortExceptionResponse(ex);
    }

    @ExceptionHandler(value = {EntityAlreadyExistsException.class})
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public @ResponseBody
    ShortExceptionResponse entityAlreadyExistsExceptionHandler(EntityAlreadyExistsException ex) {
    LOGGER.debug("A rest request could not been process due an illegal state of the target entity", ex);
    return new ShortExceptionResponse(ex);
    }

    @ExceptionHandler(value = {IllegalArgumentException.class, UnsupportedOperationException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public @ResponseBody
    ShortExceptionResponse illegalArgumentExceptionHandler(Exception ex) {
    LOGGER.error("An error occured processing a rest request", ex);
    return new ShortExceptionResponse(ex);
    }

    @ExceptionHandler(value = {HttpMessageNotReadableException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public @ResponseBody
    ShortExceptionResponse httpMessageNotReadableExceptionHandler(Exception ex) {
    LOGGER.error("An error occured processing a rest request", ex);
    if (ex.getCause() instanceof InvalidFormatException) {
        return new ShortExceptionResponse(new InvalidValueException(((InvalidFormatException) ex.getCause()).getOriginalMessage()));
    }
    return new ShortExceptionResponse(ex);
    }
...
...
}

В реальном контроллере вы просто продолжаете бросать исключение, и он будет обрабатываться обработчиком ошибок

@RequestMapping(method = RequestMethod.POST)
public @ResponseBody
MetadataDTO createMetadata(@RequestBody MetadataDTO metaDataDTO) throws EntityAlreadyExistsException {
    MetadataDTO result = metaDataService.createMetadata(metaDataDTO.getName(), metaDataDTO.getDescription(), metaDataDTO.getType());
    return result;
}

Ответ 4

Вы можете создать класс 'model' для хранения полного объекта, который будет преобразован в json:

@AllArgsConstructor //or make a constructor with all the fields manually
class ResponseObject {
    User user;
    House house;
    Car car;
}

Поскольку вы используете Spring, у вас уже есть библиотека Джексона. Так что вы можете сделать:

@Autowired ObjectMapper objectMapper; // no need to configure anything to use this
public String getJson(){
    User user = new User("user", "password");
    House house = new House(4, true, ...);
    Car car = new Car();
    ResponseObject resp = new ResponseObject(user, house, car);
    String json = null;
    json = objectMapper.convertValue(resp, ResponseObject.class);
    // or:
    try {
        json = objectMapper.writeValueAsString(resp);
    } catch (Exception e) {
        ...
    }
    // or: (would need to use a google Gson dependency)
    Gson gson = new Gson();
    json = gson.toJson(resp, ResponseObject.class);
    return json;
}

В качестве альтернативы, если вам действительно нужна гибкость,

@Autowired ObjectMapper mapper;
public String getJson() {
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("number", 5);
    jsonMap.put("String", "string");
    jsonMap.put("kanky", 987L);
    String json = mapper.writeValueAsString(jsonMap);
    System.out.println("json: " + json);
} // works fine if your map values have a toString defined