[Программирование, Java] Spring Boot + ControllerAdvice + ResponseBodyAdvice или как обернуть ответ контроллеров
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
ВведениеВсем привет, друзья! Сегодня хочу рассказать про способ использования ControllerAdvice для оборачивания объекта, возвращаемого контроллерами, в новый класс на уровне DispatcherServlet.Пример:
Допустим, некоторый метод отдавал информацию о пользователе
{
"name": "Ivan",
"surname": "Ivanov"
}
И есть еще десяток методов, которые отдают некоторую информацию о пользователеНо теперь мы хотим, чтобы каждый метод отдавал дополнительно еще несколько общих полей (например, серию и номер паспорта)
{
"name": "Ivan",
"surname": "Ivanov",
"passport": "1111 111111"
}
Я расскажу про интересное применение ControllerAdvice и покажу один из способов, которым можно решить такую задачуP.S. В жизни такой подход был удобен, когда в проекте существовала отдельная библиотека, импортирующая в микросервисы модель данных API, а по бизнес требованиям стало необходимо добавить в некоторых микросервисах к моделям дополнительные данные. Возможны и иные кейсыРешение задачиРешение удобно при его многократном использовании. В одном сервисе будет удобнее, скорее всего, использовать иной подход. Поэтому будем писать стартерНаши задачи:
- Создать удобный способ использования стартера в коде - через аннотации
- Создать возможность гибкой настройки добавляемых данных в класс-обертку
- Создать класс с @ControllerAdvicе, обрабатывающий методы контроллеров
- Собрать все в стартер Spring Boot
АннотацииДля начала создадим 2 аннотации, который будут включать оборачивание для контроллера и выключать его для конкретного методаАннотация, включающая обработку методов
@RestController
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableResponseWrapper {
Class<? extends IWrapperModel> wrapperClass();
}
Так как наша аннотация всегда висит над контроллером, а у аннотаций в java не существует понятия наследования - то вешаем над нашей аннотацией @RestController - это позволит использовать @EnableResponseWrapper вместо @RestControlle@Target(ElementType.TYPE) - указывает на то, что наша аннотация может висеть над классом, интерфейсом или enum-ом@Retention(RetentionPolicy.RUNTIME) - указывает область видимости аннотации - во время выполнения кодаАннотация, в качестве аргумента, принимает класс, который описывает оболочку Class<? extends IWrapperModel> wrapperClass();Аннотация, отключающая обработку метода
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DisableResponseWrapper {
}
Сами по себе аннотации, безусловно, никакой функциональности не добавляют. Обработкой аннотаций займемся позднееИнтерфейсыДля работы нам понадобится сущность, возвращающая дополнительные данные и сущность, описывающая обертку. Создадим интерфейсыИнтерфейс сервиса, через который будем получать данный для наполнения обертки
@Service
public interface IWrapperService {
Object getData(Object body);
}
Через метод getData(Object body) будем получать данные, затем кладем в класс-обертку. Интерфейс класса-обертки
public interface IWrapperModel {
void setData(Object object);
void setBody(Object object);
}
Через метод setData(Object object) устанавливаем те данные, которые получили в методе getData(Object object).
Через setBody(Object object) устанавливаем объект-ответ, который вернул обрабатываемый методЭдвайсСоздадим основной класс стартера, обрабатывающий методы контроллеров
@AllArgsConstructor
@ControllerAdvice(annotations = EnableResponseWrapper.class)
public class ResponseWrapperAdvice implements ResponseBodyAdvice<Object> {
}
Аннотация
@ControllerAdvice(annotations = EnableResponseWrapper.class) указывает, что методы данного компонента будут использоваться сразу несколькими контроллерами. Также указываем, что наши методы будут обрабатывать только те контроллеры, которые помечены @EnableResponseWrapperКласс реализует интерфейс ResponseBodyAdvice<>, который позволяет настраивать ответ, после его возвращения методом @ResponseBody или контроллером ResponseEntity, но до того, как тело будет записано с помощью HttpMessageConverterВ классе необходимо реализовать 2 методаПервый позволяет отсеить методы на обрабатываемы и не обрабатываемые
Именно в нем мы проверим, не аннотирован ли случайно наш метод @DisableResponseWrapper. Получим все аннотации, которые висят над методом, и поищем среди них нужную нам аннотацию.
@Override
public boolean supports(MethodParameter returnType, @NonNull Class converterType) {
for (Annotation a : returnType.getMethodAnnotations()) {
if (a.annotationType() == DisableResponseWrapper.class) {
return false;
}
}
return true;
}
Второй метод класса вызывается только для тех методов, для которых метод supports возвращает true
@SneakyThrows
@Override
public Object beforeBodyWrite(
@Nullable Object body,
@NonNull MethodParameter returnType,
@NonNull MediaType selectedContentType,
@NonNull Class selectedConverterType,
@NonNull ServerHttpRequest request,
@NonNull ServerHttpResponse response
) {
if (body == null) {
return null;
}
// получаем wrapperClass из аннотации
Class<? extends IWrapperModel> wrapperClass = null;
for (Annotation annotation : returnType.getContainingClass().getAnnotations()) {
if (annotation.annotationType() == EnableResponseWrapper.class) {
wrapperClass = ((EnableResponseWrapper) annotation).wrapperClass();
break;
}
}
if (wrapperClass == null) {
return body;
}
...
Достаем класс-обертку из аннотацииДалее будем работать с объектом, который возвращает наш метод (в методе beforeBodyWrite() он передается первым параметром Object body)Рассмотрим две ситуации: когда метод возвращает коллекцию и когда возвращает единичный объект. В случае коллекции мы хотим, чтобы был обернут каждый объект коллекции:
...
// проверяем, был ли передан Collection или наследник Collection
if (Collection.class.isAssignableFrom(body.getClass())) {
try {
Collection<?> bodyCollection = (Collection<?>) body;
// проверяем, что collection не пустой
if (bodyCollection.isEmpty()) {
return body;
}
// оборачиваем каждый элемент коллекции
return generateListOfResponseWrapper(bodyCollection, wrapperClass);
} catch (Exception e) {
return body;
}
}
...
И если обрабатываемый метод отдает не коллекцию:
...
return generateResponseWrapper(body, wrapperClass);
...
Функции generateListOfResponseWrapper и generateResponseWrapper генерируют обертку для коллекции и для единичного элемента:
...
private List<IWrapperModel> generateListOfResponseWrapper(Collection<?> bodyCollection, Class<? extends IWrapperModel> wrapperClass) {
return bodyCollection.stream()
.map((t) -> t == null ?
null :
generateResponseWrapper(t, wrapperClass)
)
.collect(Collectors.toList());
}
...
...
@SneakyThrows
private IWrapperModel generateResponseWrapper(Object body, Class<? extends IWrapperModel> wrapperClass) {
// wrapperClass должен иметь конструктор без параметров - получаем объект класса, реализующего IWrapperModel
IWrapperModel wrapper = wrapperClass.getDeclaredConstructor().newInstance();
wrapper.setBody(body);
wrapper.setData(wrapperService.getData(body));
return wrapper;
}
...
Обратим внимание, что из класса нам необходимо получить объект
IWrapperModel wrapper = wrapperClass.getDeclaredConstructor().newInstance(); , но такой подход требует наличия в классе конструктора без параметров. Используем @SneakyThrows библиотеки Lombok для того, чтобы обработать это исключениеПолный код
@AllArgsConstructor
@ControllerAdvice(annotations = EnableResponseWrapper.class)
public class ResponseWrapperAdvice implements ResponseBodyAdvice<Object> {
private final IWrapperService wrapperService;
/**
* Метод не будет обработан, если помечен аннотацией {@link DisableResponseWrapper} <br/> <br/>
*
* @param returnType the return type
* @param converterType the selected converter type
* @return {@code true} if {@link #beforeBodyWrite} should be invoked;
* {@code false} otherwise
*/
@Override
public boolean supports(MethodParameter returnType, @NonNull Class converterType) {
for (Annotation a : returnType.getMethodAnnotations()) {
if (a.annotationType() == DisableResponseWrapper.class) {
return false;
}
}
return true;
}
/**
* Оборачиваем ответ
*
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified (possibly new) instance
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(
@Nullable Object body,
@NonNull MethodParameter returnType,
@NonNull MediaType selectedContentType,
@NonNull Class selectedConverterType,
@NonNull ServerHttpRequest request,
@NonNull ServerHttpResponse response
) {
if (body == null) {
return null;
}
// получаем wrapperClass из аннотации
Class<? extends IWrapperModel> wrapperClass = null;
for (Annotation annotation : returnType.getContainingClass().getAnnotations()) {
if (annotation.annotationType() == EnableResponseWrapper.class) {
wrapperClass = ((EnableResponseWrapper) annotation).wrapperClass();
break;
}
}
if (wrapperClass == null) {
return body;
}
// проверяем, был ли передан Collection или наследник Collection
if (Collection.class.isAssignableFrom(body.getClass())) {
try {
Collection<?> bodyCollection = (Collection<?>) body;
// проверяем, что collection не пустой
if (bodyCollection.isEmpty()) {
return body;
}
// оборачиваем каждый элемент коллекции
return generateListOfResponseWrapper(bodyCollection, wrapperClass);
} catch (Exception e) {
return body;
}
}
// если не collection
return generateResponseWrapper(body, wrapperClass);
}
/**
* Генерируем список оберток для коллекции (те информация добавляется внутрь списка)
*
* @param bodyCollection список объектов, которые необходимо обернуть
* @param wrapperClass объект обертки
* @return список оберток
*/
private List<IWrapperModel> generateListOfResponseWrapper(Collection<?> bodyCollection, Class<? extends IWrapperModel> wrapperClass) {
return bodyCollection.stream()
.map((t) -> t == null ?
null :
generateResponseWrapper(t, wrapperClass)
)
.collect(Collectors.toList());
}
/**
* Генерируем обертку вокруг объекта
*
* @param body объект который необходимо поместить в обертку
* @param wrapperClass объект обертки
* @return обертка
*/
@SneakyThrows
private IWrapperModel generateResponseWrapper(Object body, Class<? extends IWrapperModel> wrapperClass) {
// wrapperClass должен иметь конструктор без параметров - получаем объект IWrapperModel
IWrapperModel wrapper = wrapperClass.getDeclaredConstructor().newInstance();
wrapper.setBody(body);
wrapper.setData(wrapperService.getData(body));
return wrapper;
}
}
СтартерТеперь нам необходимо превратить наш проект в стартер. Для этого создадим класс автоконфигурации, в котором будет создавать бин из класса ResponseWrapperAdvice
@Configuration
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
@AllArgsConstructor
public class ResponseWrapperAutoConfiguration {
private final IWrapperService wrapperService;
@Bean
@ConditionalOnMissingBean
public ResponseWrapperAdvice responseWrapperAdvice() {
return new ResponseWrapperAdvice(wrapperService);
}
}
@AutoConfigureAfter(WebMvcAutoConfiguration.class) - говорит о том, что наши бины подключатся после того, как сконфигурируются и подключатся бины web mvcА также в resources/META-INF/ создадим файл spring.factories, в котором укажем, где Spring Boot-у искать наши настроенные бины для добавления в контекст
org.springframework.boot.autoconfigure.EnableAutoConfiguration=ru.emilnasyrov.lib.response.wrapper.config.ResponseWrapperAutoConfiguration
СборкаСоберем стартер в jar файл с помощью команды
gradle jar
Наш jar-ник появится в build/libs/response-wrapper-starter-0.0.1-SNAPSHOT-plain.jar - для удобства обрезаем -plain. Стартер готов и нам остается только подключить его к проектуДемоДля демо создадим отдельный проект, в котором будем использовать стартерВ проекте создадим папку libs, в которую положим jar стартераВ build.gradle стартер подключаем следующим образом:
repositories {
...
flatDir {
dirs 'libs'
}
}
dependencies {
...
implementation 'ru.emilnasyrov.lib:response-wrapper-starter:0.0.1-SNAPSHOT'
...
}
Модели данныхMainModel - изначальные данные
@Data
@AllArgsConstructor
public class MainModel {
private String name;
private String surname;
// переопределяем toString, hashCode, equals
}
И Wrapper - класс-обертка
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Wrapper implements IWrapperModel {
@JsonUnwrapped
Object main;
String someInfo;
@Override
public void setData(Object object) {
someInfo = object.toString();
}
@Override
public void setBody(Object object) {
main = object;
}
// переопределяем toString, hashCode, equals
}
Сервис, ответственный за получение данных извне
@Service
public class WrapperServiceImpl implements IWrapperService {
@Override
public Object getData(Object body) {
return "Additional Information";
}
}
КонтроллерControllerСоздадим контроллер, на который повесим аннотацию @EnableResponseWrapper(wrapperClass = Wrapper.class) с указанием класса-обертки
@EnableResponseWrapper(wrapperClass = Wrapper.class)
@RequestMapping("/test")
public class Controller {
@GetMapping
public MainModel test() {
return new MainModel("Name", "Surname");
}
@GetMapping("/collection")
public Collection<MainModel> testList() {
Collection<MainModel> mainModels = new ArrayList<>();
mainModels.add(new MainModel("Name1", "Surname1"));
mainModels.add(new MainModel("Name2", "Surname2"));
return mainModels;
}
@DisableResponseWrapper
@GetMapping("/unwrapped")
public MainModel unwrapped() {
return new MainModel("Name", "Surname");
}
}
Точки входа с оберткой одного объекта /test, с оберткой коллекции объектов /test/collection и /test/unwrapped - отключение обработки для конкретного методаЗапустим проект и проверим запросаPostman
Тест обертки единичного объекта
Тест обертки списка объектов
Тест метода с аннотацией @DisableResponseWrapperРезюмеМы рассмотрели интересный способ использования ControllerAdvice для работы с ответом точек входа контроллера. Также создали pet-библиотеку с реализацией в виде стартера Spring BootПочему не будет работать обычное AOP? Потому что AOP создает прокси класса с помощью CGLib или JDK Dynamic Proxy. Но когда DispatcherServlet, сканируя наши контроллеры, увидит, что контроллер должен возвращать один класс, а возвращает в итоге иной (обертку), то он отдаст ошибку. Однако подход, описанный в статье, позволяет провернуть такую интересную штуку
Ссылка на на полный код проекта: GitHub
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, WebGL] Знакомство фронтендера с WegGL (часть 1)
- [JavaScript] DTO в JS
- [Информационная безопасность, Java, Разработка под Android, GitHub] Пишем паническую кнопку под андроид (Часть 2)
- [JavaScript, Разработка мобильных приложений] Разработка под iOS без Xcode
- [JavaScript] Как я писал тестовое задание на Angular и почему некоторым разработчикам не стоит давать тестовое задание
- [JavaScript, Node.JS] Создаем свой сайт или блог на Ghost в образе Docker
- [Информационная безопасность, Java, Разработка под Android, Хакатоны] Пишем паническую кнопку под Android (Часть 1)
- [JavaScript, Алгоритмы] Быстрая математика для графиков, на примере вычисления среднего
- [Python, Программирование, C++, Алгоритмы] Оптимизация на простых типах данных. Часть №2 «Числа»
- [Программирование, Big Data, Конференции, Искусственный интеллект] BeeTech 2021: обзор докладов big-data, искуственный интеллект, IT-архитектура, QA, Back-End
Теги для поиска: #_programmirovanie (Программирование), #_java, #_java, #_spring, #_springboot, #_controlleradvice, #_programmirovanie (
Программирование
), #_java
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:37
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
ВведениеВсем привет, друзья! Сегодня хочу рассказать про способ использования ControllerAdvice для оборачивания объекта, возвращаемого контроллерами, в новый класс на уровне DispatcherServlet.Пример: Допустим, некоторый метод отдавал информацию о пользователе {
"name": "Ivan", "surname": "Ivanov" } {
"name": "Ivan", "surname": "Ivanov", "passport": "1111 111111" }
@RestController
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface EnableResponseWrapper { Class<? extends IWrapperModel> wrapperClass(); } @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) public @interface DisableResponseWrapper { } @Service
public interface IWrapperService { Object getData(Object body); } public interface IWrapperModel {
void setData(Object object); void setBody(Object object); } Через setBody(Object object) устанавливаем объект-ответ, который вернул обрабатываемый методЭдвайсСоздадим основной класс стартера, обрабатывающий методы контроллеров @AllArgsConstructor
@ControllerAdvice(annotations = EnableResponseWrapper.class) public class ResponseWrapperAdvice implements ResponseBodyAdvice<Object> { } @ControllerAdvice(annotations = EnableResponseWrapper.class) указывает, что методы данного компонента будут использоваться сразу несколькими контроллерами. Также указываем, что наши методы будут обрабатывать только те контроллеры, которые помечены @EnableResponseWrapperКласс реализует интерфейс ResponseBodyAdvice<>, который позволяет настраивать ответ, после его возвращения методом @ResponseBody или контроллером ResponseEntity, но до того, как тело будет записано с помощью HttpMessageConverterВ классе необходимо реализовать 2 методаПервый позволяет отсеить методы на обрабатываемы и не обрабатываемые Именно в нем мы проверим, не аннотирован ли случайно наш метод @DisableResponseWrapper. Получим все аннотации, которые висят над методом, и поищем среди них нужную нам аннотацию. @Override
public boolean supports(MethodParameter returnType, @NonNull Class converterType) { for (Annotation a : returnType.getMethodAnnotations()) { if (a.annotationType() == DisableResponseWrapper.class) { return false; } } return true; } @SneakyThrows
@Override public Object beforeBodyWrite( @Nullable Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType, @NonNull Class selectedConverterType, @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response ) { if (body == null) { return null; } // получаем wrapperClass из аннотации Class<? extends IWrapperModel> wrapperClass = null; for (Annotation annotation : returnType.getContainingClass().getAnnotations()) { if (annotation.annotationType() == EnableResponseWrapper.class) { wrapperClass = ((EnableResponseWrapper) annotation).wrapperClass(); break; } } if (wrapperClass == null) { return body; } ... ...
// проверяем, был ли передан Collection или наследник Collection if (Collection.class.isAssignableFrom(body.getClass())) { try { Collection<?> bodyCollection = (Collection<?>) body; // проверяем, что collection не пустой if (bodyCollection.isEmpty()) { return body; } // оборачиваем каждый элемент коллекции return generateListOfResponseWrapper(bodyCollection, wrapperClass); } catch (Exception e) { return body; } } ... ...
return generateResponseWrapper(body, wrapperClass); ... ...
private List<IWrapperModel> generateListOfResponseWrapper(Collection<?> bodyCollection, Class<? extends IWrapperModel> wrapperClass) { return bodyCollection.stream() .map((t) -> t == null ? null : generateResponseWrapper(t, wrapperClass) ) .collect(Collectors.toList()); } ... ...
@SneakyThrows private IWrapperModel generateResponseWrapper(Object body, Class<? extends IWrapperModel> wrapperClass) { // wrapperClass должен иметь конструктор без параметров - получаем объект класса, реализующего IWrapperModel IWrapperModel wrapper = wrapperClass.getDeclaredConstructor().newInstance(); wrapper.setBody(body); wrapper.setData(wrapperService.getData(body)); return wrapper; } ... IWrapperModel wrapper = wrapperClass.getDeclaredConstructor().newInstance(); , но такой подход требует наличия в классе конструктора без параметров. Используем @SneakyThrows библиотеки Lombok для того, чтобы обработать это исключениеПолный код @AllArgsConstructor
@ControllerAdvice(annotations = EnableResponseWrapper.class) public class ResponseWrapperAdvice implements ResponseBodyAdvice<Object> { private final IWrapperService wrapperService; /** * Метод не будет обработан, если помечен аннотацией {@link DisableResponseWrapper} <br/> <br/> * * @param returnType the return type * @param converterType the selected converter type * @return {@code true} if {@link #beforeBodyWrite} should be invoked; * {@code false} otherwise */ @Override public boolean supports(MethodParameter returnType, @NonNull Class converterType) { for (Annotation a : returnType.getMethodAnnotations()) { if (a.annotationType() == DisableResponseWrapper.class) { return false; } } return true; } /** * Оборачиваем ответ * * @param body the body to be written * @param returnType the return type of the controller method * @param selectedContentType the content type selected through content negotiation * @param selectedConverterType the converter type selected to write to the response * @param request the current request * @param response the current response * @return the body that was passed in or a modified (possibly new) instance */ @SneakyThrows @Override public Object beforeBodyWrite( @Nullable Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType, @NonNull Class selectedConverterType, @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response ) { if (body == null) { return null; } // получаем wrapperClass из аннотации Class<? extends IWrapperModel> wrapperClass = null; for (Annotation annotation : returnType.getContainingClass().getAnnotations()) { if (annotation.annotationType() == EnableResponseWrapper.class) { wrapperClass = ((EnableResponseWrapper) annotation).wrapperClass(); break; } } if (wrapperClass == null) { return body; } // проверяем, был ли передан Collection или наследник Collection if (Collection.class.isAssignableFrom(body.getClass())) { try { Collection<?> bodyCollection = (Collection<?>) body; // проверяем, что collection не пустой if (bodyCollection.isEmpty()) { return body; } // оборачиваем каждый элемент коллекции return generateListOfResponseWrapper(bodyCollection, wrapperClass); } catch (Exception e) { return body; } } // если не collection return generateResponseWrapper(body, wrapperClass); } /** * Генерируем список оберток для коллекции (те информация добавляется внутрь списка) * * @param bodyCollection список объектов, которые необходимо обернуть * @param wrapperClass объект обертки * @return список оберток */ private List<IWrapperModel> generateListOfResponseWrapper(Collection<?> bodyCollection, Class<? extends IWrapperModel> wrapperClass) { return bodyCollection.stream() .map((t) -> t == null ? null : generateResponseWrapper(t, wrapperClass) ) .collect(Collectors.toList()); } /** * Генерируем обертку вокруг объекта * * @param body объект который необходимо поместить в обертку * @param wrapperClass объект обертки * @return обертка */ @SneakyThrows private IWrapperModel generateResponseWrapper(Object body, Class<? extends IWrapperModel> wrapperClass) { // wrapperClass должен иметь конструктор без параметров - получаем объект IWrapperModel IWrapperModel wrapper = wrapperClass.getDeclaredConstructor().newInstance(); wrapper.setBody(body); wrapper.setData(wrapperService.getData(body)); return wrapper; } } @Configuration
@AutoConfigureAfter(WebMvcAutoConfiguration.class) @AllArgsConstructor public class ResponseWrapperAutoConfiguration { private final IWrapperService wrapperService; @Bean @ConditionalOnMissingBean public ResponseWrapperAdvice responseWrapperAdvice() { return new ResponseWrapperAdvice(wrapperService); } } org.springframework.boot.autoconfigure.EnableAutoConfiguration=ru.emilnasyrov.lib.response.wrapper.config.ResponseWrapperAutoConfiguration
gradle jar
repositories {
... flatDir { dirs 'libs' } } dependencies { ... implementation 'ru.emilnasyrov.lib:response-wrapper-starter:0.0.1-SNAPSHOT' ... } @Data
@AllArgsConstructor public class MainModel { private String name; private String surname; // переопределяем toString, hashCode, equals } И Wrapper - класс-обертка @Data @AllArgsConstructor @NoArgsConstructor public class Wrapper implements IWrapperModel { @JsonUnwrapped Object main; String someInfo; @Override public void setData(Object object) { someInfo = object.toString(); } @Override public void setBody(Object object) { main = object; } // переопределяем toString, hashCode, equals } @Service
public class WrapperServiceImpl implements IWrapperService { @Override public Object getData(Object body) { return "Additional Information"; } } @EnableResponseWrapper(wrapperClass = Wrapper.class)
@RequestMapping("/test") public class Controller { @GetMapping public MainModel test() { return new MainModel("Name", "Surname"); } @GetMapping("/collection") public Collection<MainModel> testList() { Collection<MainModel> mainModels = new ArrayList<>(); mainModels.add(new MainModel("Name1", "Surname1")); mainModels.add(new MainModel("Name2", "Surname2")); return mainModels; } @DisableResponseWrapper @GetMapping("/unwrapped") public MainModel unwrapped() { return new MainModel("Name", "Surname"); } } Тест обертки единичного объекта Тест обертки списка объектов Тест метода с аннотацией @DisableResponseWrapperРезюмеМы рассмотрели интересный способ использования ControllerAdvice для работы с ответом точек входа контроллера. Также создали pet-библиотеку с реализацией в виде стартера Spring BootПочему не будет работать обычное AOP? Потому что AOP создает прокси класса с помощью CGLib или JDK Dynamic Proxy. Но когда DispatcherServlet, сканируя наши контроллеры, увидит, что контроллер должен возвращать один класс, а возвращает в итоге иной (обертку), то он отдаст ошибку. Однако подход, описанный в статье, позволяет провернуть такую интересную штуку Ссылка на на полный код проекта: GitHub =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_java |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:37
Часовой пояс: UTC + 5