티스토리 뷰
1. Build.gradle에 Spring HATEOAS 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'io.projectreactor:reactor-test'
}
org.springframework.boot:spring-boot-starter-hateoas를 추가해야 합니다.
이 글을 적은 시점에서는 spring-hateoas 1.1.0을 적용되어있습니다. 0.25 버전이 아니므로 참고해주세요
2. 단일 Entity HATEOAS 적용
2.1 수동으로 Entity에 대해서 적용
수동으로 적용할 때는 EntityModel<T>클래스를 이용하고 Static Method로 객체를 만들기 때문에 EntityModel.of()를 통해서 객체를 생성합니다.
@GetMapping("/test1")
public ResponseEntity<EntityModel<Domain>> basicHateoas() {
Domain domain = domainService.getDomain();
return ResponseEntity.ok().body(
EntityModel.of(domain)
.add(linkTo(methodOn(DomainController.class).basicHateoas()).withSelfRel()) // /hateoas/test1 - self
.add(linkTo(DomainController.class).slash("test").withRel("test")) // /hateoas/test - test
.add(linkTo(methodOn(DomainController.class).dummy()).withRel("dummy")) // /hateoas/dummy - dummy
.add(linkTo(methodOn(DomainController.class).dummy2(1L)).withRel(IanaLinkRelations.TAG)) // /hateoas/1 - tag
);
}
add() 메소드를 통해서 link를 추가할 수 있습니다.
linkTo를 통해 Controller.class를 넣으면 Controller에 적용된 RequestMapping()이 들어가고
linkTo(methodOn(Controller.class).method(argument))이런식으로 넣으면 해당 API의 URI가 매핑이 됩니다.
webmvc를 이용하면 org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo와
org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn을 import static으로 지정해주세요
webflux를 이용하면 org.springframework.hateoas.server.mvc.WebFluxLinkBuilder.linkTo와 org.springframework.hateoas.server.mvc.WebFluxLinkBuilder.methodOn을 import static으로 지정해주세요
link에 대한 이름은 withSelRel()메소드와 withRel() 메소드가 있습니다.
withSelRel()메소드를 이용하면 link이름은 self로 지정이 됩니다.
그 외의 이름을 지정할 때는 withRel를 사용하고 String을 넣거나 IanaLinkRelations를 통한 유틸리티 클래스에 지정된 이름을 넣어줄 수 있습니다.
{
"id": 1,
"name": "domain",
"desc": "desc",
"_links": {
"self": {
"href": "http://localhost:8080/hatoeas/test1"
},
"test": {
"href": "http://localhost:8080/hatoeas/test"
},
"dummy": {
"href": "http://localhost:8080/hatoeas/dummy"
},
"tag": {
"href": "http://localhost:8080/hatoeas/1"
}
}
}
2.2 RepresentationModelAssemblerSupport<T,D>클래스를 이용
2.2.1 RepresentationModel 생성
RepresentationModelAssemblerSupport클래스는 RepresetionModel을 이용합니다.
@Getter
@Setter
public class DomainModel extends RepresentationModel<DomainModel> {
Long id;
String name;
String desc;
public DomainModel() {
super();
}
}
위에 Model에서 RepresentationModel<DomainModel>가 반드시 상속되어야 합니다.
2.2.2 RepresentationModelAssemblerSupport생성
생성자는 controller클래스와 DomainModel클래스를 넣습니다.
@Component
public class DomainRepresentationModelAssembler extends RepresentationModelAssemblerSupport<Domain, DomainModel> {
public DomainRepresentationModelAssembler(Class<?> controllerClass, Class<DomainModel> resourceType) {
super(controllerClass, resourceType);
}
public DomainRepresentationModelAssembler() {
super(DomainController.class, DomainModel.class);
}
@Override
public DomainModel toModel(Domain entity) {
DomainModel model = instantiateModel(entity);
//DomainModel model2= createModelWithId(entity.getId(), entity);
model.add(linkTo(methodOn(DomainController.class).basicHateoas()).withSelfRel()) // /hateoas/test1 - self
.add(linkTo(DomainController.class).slash("test").withRel("test")) // /hateoas/test - test
.add(linkTo(methodOn(DomainController.class).dummy()).withRel("dummy")); // /hateoas/dummy - dummy
model.setId(entity.getId());
model.setDesc(entity.getDesc());
model.setName(entity.getName());
return model;
}
}
instantiateModel를 통해 DomainModel 객체를 생성합니다.(new로 해도 상관없습니다.)
createModelWithId() 메소드를 사용해서 만들 수 있는데 이 메소드는 미리 self Link를 만들어 줍니다.
그다음 Link를 추가하고 변수들을 매핑시켜 값을 넣어주면 됩니다.
@GetMapping("/test2")
public ResponseEntity<DomainModel> assemblerHateoas() {
return ResponseEntity.ok().body(assembler.toModel(domainService.getDomain()));
}
2.3 RepresentationModelProcessor<T> 를 이용
해당 클래스는 T가 반환될 때 process메소드를 실행시켜주는 클래스입니다. T는 EntityModel를 사용합니다.
@Component
public class DomainRepresentationModelProcessor implements RepresentationModelProcessor<EntityModel<Domain>> {
@Override
public EntityModel<Domain> process(EntityModel<Domain> model) {
return model.add(linkTo(methodOn(DomainController.class).basicHateoas()).withSelfRel()) // /hateoas/test1 - self
.add(linkTo(DomainController.class).slash("test").withRel("test")) // /hateoas/test - test
.add(linkTo(methodOn(DomainController.class).dummy()).withRel("dummy")); // /hateoas/dummy - dummy
}
}
controller에서는 EntityModel를 만들어서 반환시켜주면 됩니다.
@GetMapping("/test3")
public ResponseEntity<EntityModel<Domain>> processorHateoas() {
return ResponseEntity.ok().body(EntityModel.of(domainService.getDomain()));
}
3. 복수 Entity HATEOAS 적용
3.1 수동으로 적용할 때
@GetMapping("/collection/test1")
public ResponseEntity<CollectionModel<EntityModel<Domain>>> hateoasCollection() {
List<EntityModel<Domain>> domains = domainService.getDomains().
stream()
.map((domain) ->
EntityModel.of(domain)
.add(linkTo(methodOn(DomainController.class).basicHateoas()).withSelfRel()) // /hateoas/test1 - self
.add(linkTo(DomainController.class).slash("test").withRel("test")) // /hateoas/test - test
.add(linkTo(methodOn(DomainController.class).dummy()).withRel("dummy")) // /hateoas/dummy - dummy
.add(linkTo(methodOn(DomainController.class).dummy2(1L)).withRel(IanaLinkRelations.TAG)) // /hateoas/1 - tag
).collect(Collectors.toList());
return ResponseEntity.ok().body(CollectionModel.of(domains)
.add(linkTo(methodOn(DomainController.class).hateoasCollection()).withSelfRel())
);
}
List에 있는 Entity를 EntityModel로 바꾸어주고 CollectionModel.of를 통해 값을 넣어주면 됩니다.
3.2 RepresentationModelAssemblerSupport를 사용
toEntityModel과 toCollectionModel를 정의해서 사용하면 됩니다.
@Component
public class DomainRepresentationModelAssembler extends RepresentationModelAssemblerSupport<Domain, DomainModel> {
public DomainRepresentationModelAssembler(Class<?> controllerClass, Class<DomainModel> resourceType) {
super(controllerClass, resourceType);
}
public DomainRepresentationModelAssembler() {
super(DomainController.class, DomainModel.class);
}
@Override
public DomainModel toModel(Domain entity) {
DomainModel model = instantiateModel(entity);
model.add(linkTo(methodOn(DomainController.class).basicHateoas()).withSelfRel()) // /hateoas/test1 - self
.add(linkTo(DomainController.class).slash("test").withRel("test")) // /hateoas/test - test
.add(linkTo(methodOn(DomainController.class).dummy()).withRel("dummy")); // /hateoas/dummy - dummy
model.setId(entity.getId());
model.setDesc(entity.getDesc());
model.setName(entity.getName());
return model;
}
@Override
public CollectionModel<DomainModel> toCollectionModel(Iterable<? extends Domain> entities) {
return super.toCollectionModel(entities)
.add(linkTo(methodOn(DomainController.class).hateoasCollection()).withSelfRel());
}
}
Controller에서는 toCollectionModel메소드만 불러와주면 됩니다.
@GetMapping("/collection/test2")
public ResponseEntity<CollectionModel<DomainModel>> assemblerHateoasCollection() {
return ResponseEntity.ok().body(assembler.toCollectionModel(domainService.getDomains()));
}
3.3 Page로 구성되어있을 경우
스프링에서는 PagedResourcesAssembler를 이용해서 PagedModel을 만들어 주면 됩니다.
PagedResourcesAssembler는 Autowired를 통해 주입시켜주고 toModel메소드를 통해서 구현
toModel메소드는 Page와 RepresentationModelAssembler가 들어가기 때문에 각각의 Entity에 대한 Link는 RepresentationModelAssembler 통해 구현하면 됩니다.
@GetMapping("/page/test1")
public ResponseEntity<PagedModel<Domain>> hateoasPageModel(@RequestParam("page") int page) {
return ResponseEntity.ok().body(pageAssembler.toModel(domainService.getPageDomains(page), assembler));
}
참조
https://docs.spring.io/spring-hateoas/docs/1.1.0.RELEASE/reference/html/
https://github.com/spring-projects/spring-hateoas-examples
'Back-End > Spring' 카테고리의 다른 글
[Spring Batch] 5.0 버전 적용했더니 실행되지 않는데요? (1) | 2023.05.16 |
---|