В этом посте я хочу немного покопаться в Spring mvc, раскрывая, что происходит за кулисами, когда запрос преобразуется в ваш объект параметров и наоборот. Прежде чем мы начнем, я хочу объяснить цель этих аннотаций.
Для чего нужны @RequestBody и @ResponseBody?
Они представляют собой аннотации Spring MVC Framework и могут использоваться в контроллере для реализации сериализации и десериализации смарт-объектов. Они помогают вам избежать стандартного кода, извлекая логику преобразования сообщений и делая его аспектом. Кроме того, они помогают вам поддерживать несколько форматов для одного ресурса REST без дублирования кода. Если вы аннотируете метод с помощью @ResponseBody, Spring попытается преобразовать его возвращаемое значение и автоматически записать его в ответ http. Если вы аннотируете параметр методов с помощью @RequestBody, Spring на лету попытается преобразовать содержимое тела входящего запроса в ваш объект параметра.
Вот пример
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
@Controller @RequestMapping (value = "/bookcase" ) public class BookCaseController { private BookCase bookCase; @RequestMapping (method = RequestMethod.GET) @ResponseBody public BookCase getBookCase() { return this .bookCase; } @RequestMapping (method = RequestMethod.PUT) @ResponseStatus (HttpStatus.NO_CONTENT) public void setBookCase( @RequestBody BookCase bookCase) { this .bookCase = bookCase; } } |
Так что же делает Spring за кулисами, когда мы используем эти аннотации?
В зависимости от вашей конфигурации у Spring есть список HttpMessageConverters, зарегистрированных в фоновом режиме. Ответственность HttpMessageConverters заключается в преобразовании тела запроса в определенный класс и обратно в тело ответа, в зависимости от предварительно определенного типа MIME . Каждый раз, когда выданный запрос попадает в пружину аннотации @RequestBody или @ResponseBody, проходит по всем зарегистрированным HttpMessageConverter, ища первое, которое соответствует заданному типу и классу mime, а затем использует его для фактического преобразования.
Как я могу добавить пользовательский HttpMessageConverter?
Добавляя @EnableWebMvc соответственно <mvc: annotation-driven />, Spring регистрирует несколько предопределенных конвертеров сообщений для JSON / XML и так далее. Вы можете добавить пользовательский конвертер, как показано ниже
01
02
03
04
05
06
07
08
09
10
|
@Configuration @EnableWebMvc @ComponentScan public class WebConfiguration extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) { httpMessageConverters.add( new BookCaseMessageConverter( new MediaType( "text" , "csv" ))); } } |
В этом примере я написал конвертер, который обрабатывает преобразование BookCase, который в основном является списком книг. Конвертер может конвертировать CSV-контент в BookCase и наоборот. Я использовал opencsv для разбора текста.
Вот модель
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class Book { private String isbn; private String title; public Book(String isbn, String title) { this .isbn = isbn; this .title = title; } // ... } public class BookCase extends ArrayList<Book> { public BookCase() { } public BookCase(Collection<? extends Book> c) { super (c); } } |
и фактический конвертер
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class BookCaseMessageConverter extends AbstractHttpMessageConverter<BookCase> { public BookCaseMessageConverter() { } public BookCaseMessageConverter(MediaType supportedMediaType) { super (supportedMediaType); } public BookCaseMessageConverter(MediaType... supportedMediaTypes) { super (supportedMediaTypes); } @Override protected boolean supports(Class<?> clazz) { return BookCase. class .equals(clazz); } @Override protected BookCase readInternal(Class<? extends BookCase> clazz, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException { CSVReader reader = new CSVReader( new InputStreamReader(httpInputMessage.getBody())); List<String[]> rows = reader.readAll(); BookCase bookCase = new BookCase(); for (String[] row : rows) { bookCase.add( new Book(row[ 0 ], row[ 1 ])); } return bookCase; } @Override protected void writeInternal(BookCase books, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException { CSVWriter writer = new CSVWriter( new OutputStreamWriter(httpOutputMessage.getBody())); for (Book book : books) { writer.writeNext( new String[]{book.getIsbn(), book.getTitle()}); } writer.close(); } } |
Результат
Теперь мы можем отправлять запросы text / csv на наш ресурс вместе с application / json и xml, которые в основном поддерживаются «из коробки».
-
1234567
PUT /bookcase
Content-Type: text/csv
"123"
,
"Spring in Action"
"456"
,
"Clean Code"
Response
204
No Content
-
1234567
GET /bookcase
Accept: text/csv
Response
200
OK
"123"
,
"Spring in Action"
"456"
,
"Clean Code"
Благодаря дизайну пружинного MVC, который следует принципу единой ответственности, наш контроллер остается тонким. Нам не нужно добавлять ни одной строки, если мы хотим поддерживать новые типы медиа.