inflearn logo
강의

Khóa học

Chia sẻ kiến thức

[Phiên bản sửa đổi 2023-11-27] Phát triển RESTful Web Services bằng Spring Boot 3.x

Triển khai Tài liệu Swagger - Sử dụng Spring Boot 2.7 ②

Swagger와 Jackson Filter 사용 시 Swagger-ui의 example value

1571

cnw529

1 câu hỏi đã được viết

0

Jackson Filter를 적용한 코드에서는 Swagger-UI에서 example value 값이 "filters"와 "value"로만 보입니다. 필터가 적용된 example value 값을 온전히 표시하기 위해서는 어떻게 해야하는지 궁금합니다.

swagger jacksonfilter rest-api spring-boot

Câu trả lời 2

0

cnw529

저 또한 Postman을 이용하여 'GET /users/1'에 대한 Request로 다음과 같은 Response를 정상적으로 받고 있습니다.

User controller의 'GET /users/{id}' 요청은 JsonFilter를 적용하여 MappingJacksonValue를 반환하도록 코드를 작성하였으며, 이로인해 Swagger-ui에서 User controller의 'GET /users/{id}'에 대한 Example value에서 다음과 같이 User Domain 클래스의 필드 구조를 제대로 보여주지 않았고 이를 문제라고 생각하였습니다.

필터를 적용하지 않고, EntityModel<User>를 반환하는 경우는 다음과 같이 example value가 올바르게 표시됩니다. 저는 MappingJacksonValue와 EntitiyModel<User>, 즉, 반환값의 차이에서 이러한 문제가 발생했다고 생각하고 있습니다.

swagger-ui Models의 User에서는 User Domain Class에 대한 정보를 올바르게 표시하고 있습니다.

다음은 작성한 User.java, UserController.java, pom.xml의 코드 입니다.

User.java

package com.example.restfulwebservice.user;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
//@JsonIgnoreProperties(value = {"password", "ssn"})
@JsonFilter("UserInfo")
@ApiModel(description = "사용자 상세 정보를 위한 도메인 객체")
public class User {
private Integer id;

@Size(min=2, message = "Name 2글자 이상 입력해 주세요.")
@ApiModelProperty(notes = "사용자 이름을 입력해 주세요.")
private String name;
// 미래 데이터 사용 X
@Past
@ApiModelProperty(notes = "사용자 등록일을 입력해 주세요.")
private Date joinDate;

@ApiModelProperty(notes = "사용자 비빌번호를 입력해 주세요.")
private String password;
@ApiModelProperty(notes = "사용자 주민등록번호를 입력해 주세요.")
private String ssn;
}

UserController.java

package com.example.restfulwebservice.user;

import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.validation.Valid;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

@RestController
public class UserController {
@Autowired
private UserDaoService service;

// public UserController(UserDaoService service) {
// this.service = service;
// }

@GetMapping("/users")
public List<EntityModel<User>> retrieveAllUsers() {
List<EntityModel<User>> models = new ArrayList<>();
List<User> users = service.findAll();

// HATEOAS
for (User user : users) {
EntityModel model = EntityModel.of(user);
model.add(linkTo(methodOn(this.getClass()).retrieveAllUsers()).withSelfRel());
models.add(model);
}

// JacksonFilter
// SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
// .filterOutAllExcept("id", "name", "joinDate");
// FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
//
// MappingJacksonValue mapping = new MappingJacksonValue(models);
// mapping.setFilters(filters);

return models;
}

@GetMapping("/users/{id}")
public MappingJacksonValue retrieveUser(@PathVariable int id) {
User user = service.findOne(id);
if (user == null) {
throw new UserNotFoundException(id);
}

// HATEOAS
EntityModel<User> model = EntityModel.of(user);
WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
model.add(linkTo.withRel("all-users"));
linkTo = linkTo(methodOn(this.getClass()).updateUser(user, id));
model.add(linkTo.withRel("update-user"));

// JacksonFilter
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);

MappingJacksonValue mapping = new MappingJacksonValue(model);
mapping.setFilters(filters);
return mapping;
}

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
User savedUser = service.save(user);

URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedUser.getId())
.toUri();
return ResponseEntity.created(location).build();
}

@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable int id) {
User user = service.deleteById(id);

if (user == null) {
throw new UserNotFoundException(id);
}
}

@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable int id) {
User updatedUser = service.update(user, id);

if (updatedUser != null) {
return ResponseEntity.noContent().build();
}
else {
throw new UserNotFoundException(id);

}
}
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>restful-web-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>restful-web-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>16</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
</dependency>

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-explorer</artifactId>
<version>3.5.1</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

0

zpzp6271230209

해결 하셧나요 똑같네요 현상이

0

tree

저도 같은 상황이라 해결해 보려고 꽤 찾아봤는데 모르겠네여 😂

3

Jack

Swagger 가 인식하는 것은 Endpoint 의 `반환타입`입니다.

고로 Wrapper 로 지정한 클래스가 보이게 됩니다.

근데 사실 Wrapper 는 편의상의 이유로 사용하게 되는데

여기서 MappingJacksonValue 는 `Serialization` 에서 `동적 Filter` 을 위해서 쓰였네요.

그렇다고 Swagger 보자고 소스코드 바꾸고 그러진 마시고

DTO class 에 지정하신 것처럼 `Endpoint` method 에

아래 `Annotation` 을 사용해서 Document UI 에 보여줄 실제 클래스를 지정해주실 수 있어요.

@ApiResponses
({
@ApiResponse(responseCode = "200", content = { @Content(mediaType = "application/json", schema = @Schema(implementation = 보여주고싶은클래스.class)) })
})

 

 

0

Dowon Lee

안녕하세요, 이도원입니다. 

위에 올려주신 코드를 그대로 적용하여 확인해 보았을 때, 오류 발생 없이 다음과 같이 메시지가 출력되는 것을 확인하였습니다. 작성하신 코드에는 문제가 없어 보입니다. 괜찮으시면 작업하신 User.java, pom.xml 코드 등을 공유해 주실 수 있을까요? 오류를 같이 찾아 보도록 하겠습니다. 

edowon0623@gmail.com

참고로 아래 github에서 작업된 코드를 확인해 보실 수 있습니다.

https://github.com/joneconsulting/my-restful-services/settings

감사합니다. 

JPA

0

63

1

jpa dependency를 추가하고 SecurityConfig클래스에서 오류가 납니다.

0

72

1

웹 브라우저 400 bad request

0

73

1

@Size는 되는데 @Past는 안 됩니다.

0

65

1

pdf 자료는 없나요?

0

76

2

locale 정보가 null 이면 무조건 messages_ko.properties이 호출 되는 문제

0

91

2

Swagger 강의, Unable to infer base url 이거 뜨시는 분들 도움되시라고

0

122

1

강의에서나온 화면 피피티

0

161

1

HelloWorldBean 관련 에러

0

189

2

Swagger API 3.x 오류..

0

223

1

java: variable message not initialized in the default constructor 에러는 어찌하면 좋을까요?

1

283

1

현재 GIT에 올리신 소스를 실행해봤습니다.

0

191

2

고양이 소리가 귀엽네요 !!

0

140

2

git에서 소스받고 실습중인데

0

120

1

post가 안되요

0

107

1

한국어 같은 경우 언어코드인 messages_ko.properties 로 생성하는게 더 좋지 않나요?

0

169

2

리턴타입으로서 EntityModel<User> 와 ResponseEntity질문

0

107

1

예외처리쪽 관련 질문있습니다.

0

161

2

엔티티가 바로 응답으로 나가도 되나요??

0

230

2

안녕하세여 Cannot invoke "co.kr.joneconsulting.resfulservice.repository.PostRepository.save(Object)" because "this.postRepository" is null

0

142

2

사용자 등록하고 나서 H2 에서 보면 신규 사용자의 password, ssn 이 null 로 되어 있습니다.

0

144

2

ApplictionContext 질문

0

196

2

롬북이 안먹히는것같아요

0

159

1

인텔리제이에서스프링부트 파일 실행하면

0

242

1