- Введение
- Пример в реальном времени
- Как помогает JSonView
- Краткое изложение важных моментов, касающихся JsonView
- Подробная реализация и учебник
- Уровень 1: JsonView с одним классом
- Шаг 1.1 : Создание класса JsonView
- Шаг 1.2 : Создайте Java-класс с @JsonView на необходимых переменных
- Шаг 1.3 : Создайте контроллер и конечные точки GET
- Шаг 1.4: Тестирование конечных точек GET
- Шаг 1.5: Добавление конечных точек POST
- Шаг 1.6 : Тестирование конечных точек POST
- Выводы из первого уровня
- Уровень 2 : JsonView с несколькими классами
- Шаг 2.1 : Создайте класс JsonView
- Шаг 2.2 : Создание нескольких Java-классов с @JsonView для необходимых переменных
- Шаг 2.3 : Создание контроллера и конечных точек GET
- Шаг 2.4: Протестируйте конечные точки GET
- Шаг 2.5 : Добавьте конечные точки POST
- Шаг 2.6 : Тестирование конечных точек POST
- Выводы из Уровня 2
- Уровень 3: Использование сущностей с JsonView
- Шаг 3.1 : Создайте класс JsonView
- Шаг 3.2 : Создайте Java-сущность с @JsonView на необходимых переменных
- Шаг 3.3 : Создайте контроллер и конечные точки GET
- Шаг 3.4: Тестирование конечных точек GET
- Вывод из уровня 3
- Заключение
Введение
Если вы являетесь разработчиком SpringBoot и работаете над большим проектом, вы должны знать об аннотации, предоставляемой Jackson —@JsonView
, потому что она чрезвычайно мощная и элегантная. @JsonView
от Jackson помогает выборочно сериализовать и де-сериализовать объекты. Тем самым он сокращает вашу кодовую базу на тонну, избегая ненужных классов, а также раздражающих геттеров и сеттеров для копирования значений одного объекта в другой.
Пример в реальном времени
Посмотрите на приведенный ниже объект JSON
{
"userId": "markbdsouza",
"firstName": "Mark",
"lastName": "Dsouza",
"socialMediaAccount: {
"twitterUrl": "https://twitter.com/MarkBDsouza",
"githubUrl": "https://github.com/markbdsouza",
"devUrl": "",
"hashNodeUrl":""
},
"articles": [
{
"summary": "JS: Sort an Array of Objects on multiple columns/keys",
"url": "https://dev.to/markbdsouza/js-sort-an-array-of-objects-on-multiple-columns-keys-2bj1"
},
{
"summary": "Spring Cloud Config Server: Step by Step",
"url": "https://dev.to/markbdsouza/spring-cloud-config-server-step-by-step-14fd"
}
]
}
Здесь у нас есть Json-объект User.
Мы видим, что один пользователь имеет один объект SocialMediaAccount (связь OneToOne), а один клиент имеет несколько статей (связь OneToMany).
В Java для такой структуры нам понадобится 3 класса — User, SocialMediaAccount, Article.
Эти 3 класса будут выглядеть следующим образом
public class User{
private String userId;
private String firstName;
private String lastName;
private SocialMediaAccount socialMediaAccount;
private List<Article> articles;
// excluding getters and setters for brevity
}
public class SocialMediaAccount {
private String twitterUrl;
private String githubUrl;
// excluding getters and setters for brevity
}
public class Article{
private String summary;
private String url;
// excluding getters and setters for brevity
}
Как помогает JSonView
Теперь, используя эту единую структуру, мы можем создавать JSON-объекты, содержащие любую комбинацию полей, чтобы сформировать следующий JSON из конечной точки 2
{
"userId": "markbdsouza",
"socialMediaAccount: {
"twitterUrl": "https://twitter.com/MarkBDsouza"
},
"articles": [
{
"summary": "JS: Sort an Array of Objects on multiple columns/keys",
"url": "https://dev.to/markbdsouza/js-sort-an-array-of-objects-on-multiple-columns-keys-2bj1"
},
{
"summary": "Spring Cloud Config Server: Step by Step",
"url": "https://dev.to/markbdsouza/spring-cloud-config-server-step-by-step-14fd"
}
]
}
ИЛИ следующая структура из конечной точки 3
{
"userId": "markbdsouza",
"firstName": "Mark",
"socialMediaAccount: {
"twitterUrl": "https://twitter.com/MarkBDsouza",
"githubUrl": "https://github.com/markbdsouza",
"devUrl": "",
"hashNodeUrl"
}
}
ИЛИ любая другая структура, состоящая из тех же переменных в любой перестановке/комбинации.
Бизнес-логика для всех конечных точек может быть абсолютно одинаковой. Единственное различие заключается в аннотации, которая должна быть предоставлена таким образом, чтобы при преобразовании POJO в JSON или наоборот, Jackson проверял, какие поля должны быть включены при сериализации/десериализации. Таким образом, нам не придется создавать новый класс с определенной структурой, а затем копировать данные из исходного объекта в новый объект класса для каждой конечной точки.
Краткое изложение важных моментов, касающихся JsonView
Приведенные ниже пункты служат кратким резюме всего, что вам, вероятно, нужно знать о JsonView, чтобы приступить к реализации.
- Представление в JsonView определяет, как вы хотите представить класс, когда он преобразуется из POJO в JSON пакетом Jackson. Jackson будет сериализовать/де-сериализовать только поля с соответствующим
@JsonView
. - Создайте простой Java класс (аннотация на уровне класса не требуется), который будет содержать интерфейсы/дочерние классы, которые являются именами наших представлений. Мы можем использовать наследование, чтобы View 2 также включал все, что присутствует в View 1. Это полезно, когда вы создаете новый View, который расширяет поля, видимые в уже существующем View.
public class Views{
public interface Public{}
public interface CustomerDetails extends Public{}
}
- После создания класса View аннотация необходима в двух местах. 1) В самом классе или в переменных экземпляра класса, которые необходимо сериализовать/де-сериализовать. 2) В конечных точках в вашем контроллере, чтобы Jackson знал, где применить View.
- На уровне класса (того, что мы сериализуем/де-сериализуем), нам нужно аннотировать необходимую переменную экземпляра
@JsonView(Views.Public.class)
, гдеViews
— это класс, созданный в шаге выше, аPublic
— это интерфейс/подкласс, созданный в нем. Это то, что указывает Jackson, какую переменную экземпляра класса сериализовать/де-сериализовать. Пример
@JsonView(Views.Public.class)
private String firstName;
- Одна переменная экземпляра может иметь любое количество JsonViews, присоединенных к ней. Пример:
@JsonView({Views.Public.class, Views.UserDetails.class})
private String email;
- Если у нас есть объект класса B как переменная экземпляра класса A. И мы прикрепили
@JsonView
к объекту, он будет внутренне также сериализовать/де-сериализовать только аннотированные переменные экземпляра класса B.Пример:
public class A{
@JsonView(Views.Public.class)
private B b;
}
class B{
private String password; // will not be included by jackson
@JsonView(Views.Public.class)
private String username;
}
Если мы не используем @JsonView
в классе B, то ни одно поле не будет сериализовано/де-сериализовано из B, и это будет пустой объект, когда A будет сериализован/де-сериализована.
- В качестве альтернативы мы можем присвоить аннотацию всему классу.
- В контроллерах нам нужно аннотировать конечные точки соответствующим образом, чтобы Jackson знал, какие конечные точки имеют логику при преобразовании POJO в JSON или наоборот.
- Для конечных точек GET, если мы возвращаем класс и хотим предоставить только видимые поля, мы аннотируем конечную точку следующим образом
@JsonView(Views.Public.class)
@GetMapping("/1")
public Customer getDetails() {
// business logic to return customer
}
- Если мы возвращаем тот же класс в другой конечной точке, то необязательно иметь
@JsonView
. Если ваш контроллер не имеет@JsonView
, все поля возвращаемого POJO будут преобразованы в JSON. - Если это HTTP POST вызов и мы отправляем класс в нашем теле, используя
@JsonView
мы можем принять только поля, указанные в нашем View. Чтобы сделать это
@PostMapping
public Student postLevelTwoBasic(@RequestBody @JsonView(LevelTwoViews.Public.class) Student student) {
// some POST implementation
}
Подробная реализация и учебник
В следующем разделе мы рассмотрим код, настроим JsonView и посмотрим, как его можно использовать. Вы можете найти ссылку на github со всем кодом: https://github.com/markbdsouza/jsonview-springboot-tutorial.
Учебник разбит на 3 уровня. Мы будем использовать все больше возможностей и изучать другие применения JsonView.
Уровень 1: JsonView с одним классом
Для начала мы создадим один JAVA класс и используем аннотацию JsonView на нем. Мы будем проверять как GET, так и POST запросы и посмотрим, как все это работает вместе. В последующих уровнях мы попробуем использовать несколько классов и даже сущностей.
Шаг 1.1 : Создание класса JsonView
Создайте класс, содержащий различные представления, которые необходимо создать.
Обратите внимание, как мы используем наследование в этих интерфейсах. Public сохраняется в качестве базового представления. У нас есть 2 различных представления, которые наследуют Public — UserNameDetails и OtherDetails. UserDetails расширяет UserNameDetails, поэтому он расширяет Public по своей сути.
public class LevelOneViews {
/*
* Any variable associated with Public will be picked up by this view
*/
public interface Public {
}
/*
* Any variable associated with UserNameDetails or Public will be picked up by this view
*/
public interface UserNameDetails extends Public {
}
/*
* Any variable associated with UserDetails, UserNameDetails or Public(since UserNameDetails internally
* extends Public) will be picked up by this view
*/
public interface UserDetails extends UserNameDetails {
}
/*
* Any variable associated with OtherDetails or Public will be picked up by this view.
* Note: Will not pick up variables associated with UserNameDetails or UserDetails
*/
public interface OtherDetails extends Public {
}
}
Здесь мы создали 4 представления. Потратьте минуту, чтобы увидеть наследование и понять дерево наследования.
Шаг 1.2 : Создайте Java-класс с @JsonView на необходимых переменных
Создайте класс User с переменными экземпляра с представлениями, созданными в шаге выше.
@Data
@ToString
public class User {
@JsonView({LevelOneViews.Public.class})
private int userId;
@JsonView(LevelOneViews.UserNameDetails.class)
private String firstName;
@JsonView(LevelOneViews.UserNameDetails.class)
private String lastName;
@JsonView({LevelOneViews.UserDetails.class, LevelOneViews.OtherDetails.class})
private String email;
@JsonView({LevelOneViews.UserDetails.class, LevelOneViews.OtherDetails.class})
private String organization;
private Boolean isActive;
}
Шаг 1.3 : Создайте контроллер и конечные точки GET
Создайте конечные точки с 4 созданными представлениями. У нас есть приватный статический метод для создания объекта User. Обратите внимание, что для всех методов реализация одинакова. Мы вызываем только метод createUser(). Единственное различие во всех методах заключается в том, какой вид мы используем. Для каждого из этих GET-маппингов с использованием JsonView нам нужно аннотировать весь метод с помощью @JsonView
с указанием представления. Пример: @JsonView(LevelOneViews.UserNameDetails.class)
.
@RestController
public class UserController {
/**
* GET without any view
*
* @return created User
*/
@GetMapping("/levelOne/default")
public User getLevelOneUser() {
return createUser();
}
/**
* GET with Public View
*
* @return created User
*/
@JsonView(LevelOneViews.Public.class)
@GetMapping("/levelOne/public")
public User getLevelOnePublicUser() {
return createUser();
}
/**
* GET with UserNameDetails View which extends Public
*
* @return created User
*/
@JsonView(LevelOneViews.UserNameDetails.class)
@GetMapping("/levelOne/name")
public User getLevelOneNameUser() {
return createUser();
}
/**
* GET with UserDetails View which extends UserNameDetails and also Public
*
* @return created User
*/
@JsonView(LevelOneViews.UserDetails.class)
@GetMapping("/levelOne/details")
public User getLevelDetailsUser() {
return createUser();
}
/**
* GET with OtherDetails View which extends Public
*
* @return created User
*/
@JsonView(LevelOneViews.OtherDetails.class)
@GetMapping("/levelOne/other")
public User getLevelOneOther() {
return createUser();
}
/**
* @return created user
*/
private static User createUser() {
User user = new User();
user.setUserId(1);
user.setFirstName("Mark");
user.setLastName("Dsouza");
user.setEmail("mark.benjamin.dsouza@google.com");
user.setIsActive(true);
user.setOrganization("DEV.TO");
return user;
}
}
Существует 5 конечных точек. 1 без представления и 4 с представлением.
Шаг 1.4: Тестирование конечных точек GET
Запустите приложение springboot и протестируйте наши конечные точки.
Конечная точка 1: без представления
Все поля нашего объекта User Object будут десериализованы
http://localhost:8080/levelOne/default
Конечная точка 2: С публичным представлением
Public View — это наше базовое представление по умолчанию, которое имеет наименьшее количество переменных
http://localhost:8080/levelOne/public
Обратите внимание, что возвращается только столбец userId, хотя код для конечной точки 1 и 2 абсолютно одинаков. Это делается с помощью JsonView.
Конечная точка 3: с помощью представления UserNameDetails
http://localhost:8080/levelOne/name UserNameDetails расширяет Public. Таким образом, благодаря наследованию, он отображает все поля с UserNameDetails, а также Public. Таким образом, мы видим 3 поля в нашем ответе.
Конечная точка 4: С представлением UserDetails
http://localhost:8080/levelOne/details UserDetails расширяет UserNameDetails, что через наследование означает, что UserDetails также расширяет Public. Таким образом, все поля с UserDetails, UserNameDetails и Public будут доступны в ответе.
Конечная точка 5: С представлением OtherDetails
http://localhost:8080/levelOne/other OtherDetails не расширяет вышеуказанные представления, а только представление Public. Поэтому в ответе будут доступны только поля с OtherDetails или Public.
Шаг 1.5: Добавление конечных точек POST
Logger log = LoggerFactory.getLogger(UserController.class);
/**
* POST without any view
*
* @param user
*/
@PostMapping("/levelOne/default")
public User postLevelOneDefault(@RequestBody User user) {
log.info(String.valueOf(user));
return user;
}
/**
* POST with Public view
*
* @param user
*/
@PostMapping("/levelOne/name")
public User postLevelOneDetails(@RequestBody @JsonView(LevelOneViews.UserNameDetails.class) User user) {
log.info(String.valueOf(user));
return user;
}
Шаг 1.6 : Тестирование конечных точек POST
Конечная точка 1: Без просмотра
Пост вызов URL:http://localhost:8080/levelOne/default
Тело запроса: Мы отправляем всю структуру.
Логгер: User(userId=1, firstName=Mark, lastName=Dsouza, email=mark.benjamin.dsouza@google.com, organization=DEV.TO, isActive=true)
.
Все данные регистрируются.
Все данные возвращаются.
Конечная точка 2: с помощью JsonView
Давайте попробуем сделать это с помощью конечной точки JsonView
Пост вызов URL: http://localhost:8080/levelOne/name
Логгер: User(userId=1, firstName=Mark, lastName=Dsouza, email=null, organization=null, isActive=null)
Обратите внимание, что несмотря на то, что мы отправили целый объект, только 3 поля были де-сериализованы. Ответ — это то, что мы увидели в логах. Сам контроллер не получил все поля, поэтому только некоторые поля, связанные с соответствующим представлением, были де-сериализованы.
Выводы из первого уровня
Мы увидели, какие классы необходимы для реализации JsonView и как мы можем использовать его для HTTP GET и POST запросов. Нам нужно было добавить аннотацию к классу, который сериализуется/десериализуется, а также к конечной точке контроллера/API. Кроме того, при определении представлений и их использовании можно использовать наследование, чтобы одно поле было легко связано с несколькими представлениями.
Уровень 2 : JsonView с несколькими классами
На этом уровне мы посмотрим, как все работает, когда это не простой класс Java, содержащий только строки и числа. Давайте посмотрим, как это работает, когда у нас есть объекты других классов. Это, как правило, отношения OneToOne (один объект) и OneToMany (список объектов).
Шаг 2.1 : Создайте класс JsonView
Создайте класс, содержащий различные представления, которые необходимо создать.
public class LevelTwoViews {
/*
* Any variable associated with Public will be picked up by this view
*/
public interface Public {
}
/*
* Any variable associated with BasicStudentDetails or Public will be picked up by this view
*/
public interface BasicStudentDetails extends Public {
}
/*
* Any variable associated with BasicStudentDetails, AllStudentDetails or Public will be picked up by this view
*/
public interface AllStudentDetails extends BasicStudentDetails {
}
}
Здесь мы снова использовали наследование и создали 3 представления с возрастающим доступом.
Шаг 2.2 : Создание нескольких Java-классов с @JsonView для необходимых переменных
На втором уровне мы будем создавать несколько классов, чтобы проиллюстрировать, как это работает
Создайте студента
@Data
@ToString
public class Student {
@JsonView(LevelTwoViews.Public.class)
private String fullName;
@JsonView(LevelTwoViews.BasicStudentDetails.class)
private float height;
@JsonView(LevelTwoViews.BasicStudentDetails.class)
private float weight;
@JsonView(LevelTwoViews.Public.class)
private int age;
@JsonView(LevelTwoViews.BasicStudentDetails.class)
private String className;
// One to Many Relationship
@JsonView(LevelTwoViews.AllStudentDetails.class)
private List<SubjectScore> scores;
// One to One Relationship
@JsonView(LevelTwoViews.BasicStudentDetails.class)
private School school;
}
Создать школу
Один ученик имеет одну школу — отношения один к одному
@Data
@ToString
public class School {
@JsonView(LevelTwoViews.BasicStudentDetails.class)
private String schoolName;
}
Создать предметный балл
У одного ученика несколько предметных оценок — связь «один ко многим
@Data
@ToString
public class SubjectScore {
@JsonView(LevelTwoViews.AllStudentDetails.class)
private String subject;
@JsonView(LevelTwoViews.AllStudentDetails.class)
private int score;
private String teacher;
}
Обратите внимание, что у учителя нет представления. Поэтому независимо от того, какой JsonView мы предоставим на уровне контроллера, учитель никогда не будет сериализован/де-сериализован.
Шаг 2.3 : Создание контроллера и конечных точек GET
Создайте конечные точки для созданных представлений. У нас есть несколько частных статических методов для создания фиктивного объекта. Здесь также для всех методов реализация одинакова, и единственное различие заключается в том, какое представление мы используем.
@RestController
public class StudentController {
/**
* GET without any json view
*
* @return created Student
*/
@GetMapping("/levelTwo/default")
public Student getLevelTwoDefault() {
return createStudent();
}
/**
* GET with lowest view Public
*
* @return created Student
*/
@JsonView(LevelTwoViews.Public.class)
@GetMapping("/levelTwo/public")
public Student getLevelTwoPublic() {
return createStudent();
}
/**
* GET with BasicStudentDetails View which extends Public
*
* @return created Student
*/
@JsonView(LevelTwoViews.BasicStudentDetails.class)
@GetMapping("/levelTwo/basic")
public Student getLevelTwoBasicDetails() {
return createStudent();
}
/**
* GET with AllStudentDetails View which extends BasicStudentDetails,Public
*
* @return created Student
*/
@JsonView(LevelTwoViews.AllStudentDetails.class)
@GetMapping("/levelTwo/all")
public Student getLevelTwoAllDetails() {
return createStudent();
}
private static Student createStudent() {
Student student = new Student();
student.setAge(15);
student.setFullName("Mark Dsouza");
student.setWeight(49);
student.setHeight(150);
student.setClassName("VIII");
student.setSchool(createSchool());
student.setScores(createSubjectScoreList());
return student;
}
private static School createSchool() {
School school = new School();
school.setSchoolName("Indian Public School");
return school;
}
private static List<SubjectScore> createSubjectScoreList() {
List<SubjectScore> subjectScoreList = new ArrayList<>();
SubjectScore mathScore = new SubjectScore();
mathScore.setScore(80);
mathScore.setSubject("Math");
mathScore.setTeacher("Mr. John Watts");
SubjectScore englishScore = new SubjectScore();
englishScore.setScore(80);
englishScore.setSubject("English");
englishScore.setTeacher("Mrs. Mary Johnson");
subjectScoreList.add(mathScore);
subjectScoreList.add(englishScore);
return subjectScoreList;
}
Мы создали 4 конечные точки GET, одну без представления и три с представлениями, которые мы создали.
Шаг 2.4: Протестируйте конечные точки GET
Запустите приложение SpringBoot и протестируйте наши конечные точки для методов GET уровня 2.
Конечная точка 1: Без представления
Метод Http: GET
URL: http://localhost:8080/levelTwo/default
Ответ:
Здесь мы получаем обратно все данные объекта и дочерних объектов.
Конечная точка 2: С публичным представлением
Метод Http: GET
URL: http://localhost:8080/levelTwo/public
Ответ:
Мы видим только данные с публичным представлением
Конечная точка 3: С BasicStudentDetails
Метод Http: GET
URL: http://localhost:8080/levelTwo/basic
Ответ:
Обратите внимание, что оценки не включены в это представление, поскольку мы не задали требуемое представление для этого объекта в нашем основном классе Student.
Конечная точка 4: С представлением AllStudentDetails
Метод Http: GET
URL: http://localhost:8080/levelTwo/all
Ответ:
Аналогично, здесь учитель класса Score не присутствует, так как представление не было определено.
Шаг 2.5 : Добавьте конечные точки POST
Теперь создайте конечные точки POST
Logger log = LoggerFactory.getLogger(StudentController.class);
/**
* POST Mapping without any views
*
* @param student
* @return same student
*/
@PostMapping("/levelTwo/default")
public Student postLevelTwoDefault(@RequestBody Student student) {
log.info(student.toString());
return student;
}
/**
* POST Mapping with Public View. Instance variables of Student without Public view will not be deserialized
*
* @param student
* @return same student
*/
@PostMapping("/levelTwo/public")
public Student postLevelTwoBasic(@RequestBody @JsonView(LevelTwoViews.Public.class) Student student) {
log.info(student.toString());
return student;
}
Шаг 2.6 : Тестирование конечных точек POST
Конечная точка 5: POST без представления
Метод Http: POST
URL: http://localhost:8080/levelTwo/default
Тело запроса:
Логгер: Student(fullName=Mary Jane, height=137.0, weight=34.0, age=16, className=X, scores=[SubjectScore(subject=Math, score=90, teacher=Mr. John Watts), SubjectScore(subject=English, score=40, teacher=Mrs. Mary Johnson)], school=School(schoolName=New York Public School))
.
Мы записали в журнал то, что получено контроллером, и видим, что все поля имеют достоверные данные.
Отклик:
Мы видим, что те же данные, которые были отправлены, возвращаются полностью.
Конечная точка 6: POST с представлением
Метод Http: POST
URL: http://localhost:8080/levelTwo/public
Тело запроса: Мы используем тот же запрос, что и ранее
Логгер: Student(fullName=Mary Jane, height=0.0, weight=0.0, age=16, className=null, scores=null, school=null)
.
Обратите внимание на разницу, почти ВСЁ (кроме fullName и возраста) отображается по умолчанию, несмотря на то, что мы отправили все данные в теле запроса. Именно здесь JsonView работает по своему волшебству. Несмотря на то, что мы отправляем все данные, JsonView де-сериализует ТОЛЬКО те данные, которые указаны в соответствующем представлении (в данном случае Public, которое присвоено только двум полям).
Реакция:
Поскольку контроллер сам не получил данные, то и возвращаемые данные — это только то, что стало видимым благодаря JsonView.
Выводы из Уровня 2
На Уровне 2 мы увидели, как можно работать с несколькими классами и как можно использовать представления между ними для достижения нужной нам структуры данных.
Уровень 3: Использование сущностей с JsonView
В Уровне 3 мы будем использовать базу данных H2 для выбора данных, а не просто фиктивные данные. И мы будем непосредственно использовать @JsonView
на самой сущности. Это поможет нам отказаться от использования каких-либо DTO, RequestModel, ResponseModel.
Примечание: Код на Github включает data.sql и schema.sql в папку src/main/resources и некоторые свойства в application.properties, которые будут автоматически создавать схему и вставлять данные в H2 DB.
Шаг 3.1 : Создайте класс JsonView
Создайте класс, содержащий различные представления, которые необходимо создать.
/**
* List of Views for LevelThree functionality.
*/
public class LevelThreeViews {
/*
* Any variable associated with Public will be picked up by this view
*/
public interface Public {
}
/*
* Any variable associated with CustomerDetails or Public will be picked up by this view
*/
public interface CustomerDetails extends Public {
}
Мы создали 2 представления, Public и CustomerDetails.
Шаг 3.2 : Создайте Java-сущность с @JsonView на необходимых переменных
При создании нашей сущности добавьте дополнительные @JsonView
для необходимых переменных экземпляра.
@Entity
@Table(name = "customer")
@Data
public class Customer {
@Id
@GeneratedValue
private Long id;
@Column(name = "CUSTOMER_ID", nullable = false)
@JsonView(LevelThreeViews.Public.class)
private String customerId;
@Column(name = "FIRST_NAME", nullable = false, length = 50)
@JsonView(LevelThreeViews.CustomerDetails.class)
private String firstName;
@Column(name = "LAST_NAME", nullable = false, length = 50)
@JsonView(LevelThreeViews.CustomerDetails.class)
private String lastName;
}
Столбец ‘id’ не имеет никакого представления, связанного с ним. Мы не хотим раскрывать первичный ключ нашей БД для любого клиента нашего API. Вместо этого мы раскрываем только CUSTOMER_ID, который является строковым значением.
Шаг 3.3 : Создайте контроллер и конечные точки GET
Создайте конечные точки для нескольких созданных представлений. Здесь для всех методов мы получаем данные из БД. Код абсолютно одинаков для всех API, разница лишь в том, какое представление мы используем.
@RestController
public class CustomerController {
@Autowired
private CustomerRepository customerRepository;
/**
* GET Mapping without JsonView
* @return List of Customer inserted in the DB
*/
@GetMapping("/levelThree/default")
public List<Customer> getLevelThree() {
return customerRepository.findAll();
}
/**
* GET Mapping with Public JsonView
* @return List of Customer inserted in the DB
*/
@JsonView(LevelThreeViews.Public.class)
@GetMapping("/levelThree/public")
public List<Customer> getLevelThreePublic() {
return customerRepository.findAll();
}
/**
* GET Mapping with CustomerDetails JsonView which extends Public as well
* @return List of Customer inserted in the DB
*/
@JsonView(LevelThreeViews.CustomerDetails.class)
@GetMapping("/levelThree/details")
public List<Customer> getLevelThreeDetails() {
return customerRepository.findAll();
}
}
В целом у нас есть 3 конечные точки, чтобы увидеть разницу между NoView, PublicView и CustomerDetailsView.
Шаг 3.4: Тестирование конечных точек GET
Запустите приложение springboot и протестируйте наши конечные точки.
Конечная точка 1: Без представления
Метод Http: GET
URL: http://localhost:8080/levelThree/default
Ответ:
Возвращаются все поля, поскольку для этого API нет представления.
Конечная точка 2: С публичным представлением
Метод Http: GET
URL: http://localhost:8080/levelThree/public
Ответ:
Обратите внимание, что возвращаются только поля с Public View.
Конечная точка 3: С CustomerDetailsView
Метод Http: GET
URL: http://localhost:8080/levelThree/details
Ответ:
Обратите внимание, что возвращаются поля с Public и CustomerDetailsView. Но они не содержат столбца id, поскольку мы не указали аннотацию и имя представления для столбца id.
Вывод из уровня 3
Уровень 3 показал нам, как мы можем использовать сущности напрямую и надежно скрывать данные, которые мы не хотим раскрывать в API. Это включает в себя любые столбцы, создаваемые последовательностью, столбцы аудита или любые другие столбцы. Аналогично тому, что мы видели на уровне 2, мы можем иметь связанные сущности с помощью аннотаций OneToOne, OneToMany и ManyToOne.
Побочное замечание: Если вы реализуете это в своем коде и имеете несколько отношений, вы можете столкнуться с проблемой циклических ссылок при десериализации. Проверьте JsonManagedReference и JsonBackReference на StackOverflow, чтобы решить эту проблему.
Заключение
В этом руководстве мы рассмотрели различные примеры использования @JsonView
для HTTP-вызовов, когда объект является частью ответа или объект является частью тела запроса. Мы также увидели, как мы можем использовать несколько классов и сущностей с JsonView.
Надеюсь, вы найдете это полезным и дайте мне знать в комментариях или напишите мне в twitter, если у вас есть вопросы. Здоровья вам и счастливого кодинга!