將Spring實戰第5版中Spring HATEOAS部分代碼遷移到Spring HATEOAS 1.0

       最近在閱讀Spring實戰第五版中文版,書中第6章關於Spring HATEOAS部分代碼使用的是Spring HATEOAS 0.25的版本,而最新的Spring HATEOAS 1.0對舊版的API作了升級,致使在使用新版Spring Boot(截至文章發佈日最新的Spring Boot版本爲2.2.4)加載的Spring HATEOAS 1.0.3沒法正常運行書中代碼,因此我決定在此對書中的代碼進行遷移升級。git

在線閱讀書中這一部分:https://potoyang.gitbook.io/spring-in-action-v5/di-6-zhang-chuang-jian-rest-fu-wu/6.2-qi-yong-chao-mei-tigithub

 

Spring HATEOAS 1.0 版本的變化

封裝結構的最大變化是經過引入超媒體類型註冊API來實現的,以支持Spring HATEOAS中的其餘媒體類型。這致使客戶端API和服務器API(分別命名的包)以及包中的媒體類型實現的明確分離 mediatypespring

最大的變化就是將原來的資源表示爲模型,具體變化以下。json

ResourceSupportResourceResourcesPagedResources組類歷來沒有真正感覺到適當命名。畢竟,這些類型實際上並不表示資源,而是表示模型,能夠經過超媒體信息和提供的內容來豐富它們。這是新名稱映射到舊名稱的方式:api

  • ResourceSupport 就是如今 RepresentationModel服務器

  • Resource 就是如今 EntityModelmvc

  • Resources 就是如今 CollectionModelapp

  • PagedResources 就是如今 PagedModelide

所以,ResourceAssembler已被重命名爲RepresentationModelAssembler和及其方法toResource(…)分別toResources(…)被重命名爲toModel(…)toCollectionModel(…)名稱更改也反映在中包含的類中TypeReferencesui

  • RepresentationModel.getLinks()如今公開了一個Links實例(經過List<Link>),該實例公開了其餘API,以Links使用各類策略來鏈接和合並不一樣的實例。一樣,它已經變成了自綁定的泛型類型,以容許向實例添加連接的方法返回實例自己。

  • LinkDiscovererAPI已移動到client包。

  • LinkBuilderEntityLinksAPI已經被移到了server包。

  • ControllerLinkBuilder已移入server.mvc,不推薦使用替換WebMvcLinkBuilder

  • RelProvider已重命名爲LinkRelationProvider並返回LinkRelation實例,而不是String

  • VndError已移至mediatype.vnderror套件。

另外注意 ResourceProcessor 接口被 RepresentationModelProcessor 取代

更多變化請參考Spring HATEOAS文檔:https://spring.io/projects/spring-hateoas

代碼遷移升級

書中程序清單6.4 爲資源添加超連接

    @GetMapping("/recent")
    public CollectionModel<EntityModel<Taco>> recentTacos() {
        PageRequest page = PageRequest.of(
                0, 12, Sort.by("createdAt").descending());

        List<Taco> tacos = tacoRepo.findAll(page).getContent();
        CollectionModel<EntityModel<Taco>> recentResources = CollectionModel.wrap(tacos);

        recentResources.add(
                new Link("http://localhost:8080/design/recent", "recents"));
        return recentResources;
    }

消除URL硬編碼

    @GetMapping("/recent")
    public CollectionModel<EntityModel<Taco>> recentTacos() {
        PageRequest page = PageRequest.of(
                0, 12, Sort.by("createdAt").descending());

        List<Taco> tacos = tacoRepo.findAll(page).getContent();
        CollectionModel<EntityModel<Taco>> recentResources = CollectionModel.wrap(tacos);

        recentResources.add(
                linkTo(methodOn(DesignTacoController.class).recentTacos()).withRel("recents"));
        return recentResources;
    }
程序清單6.5 可以攜帶領域數據和超連接列表taco資源
public class TacoResource extends RepresentationModel<TacoResource> {

    @Getter
    private String name;

    @Getter
    private Date createdAt;

    @Getter
    private List<Ingredient> ingredients;

    public TacoResource(Taco taco) {
        this.name = taco.getName();
        this.createdAt = taco.getCreatedAt();
        this.ingredients = taco.getIngredients();
    }
}
程序清單6.6 裝配taco資源的資源裝配器
public class TacoResourceAssembler extends RepresentationModelAssemblerSupport<Taco, TacoResource> {
    /**
     * Creates a new {@link RepresentationModelAssemblerSupport} using the given controller class and resource type.
     *
     * @param controllerClass DesignTacoController {@literal DesignTacoController}.
     * @param resourceType    TacoResource {@literal TacoResource}.
     */
    public TacoResourceAssembler(Class<?> controllerClass, Class<TacoResource> resourceType) {
        super(controllerClass, resourceType);
    }

    @Override
    protected TacoResource instantiateModel(Taco taco) {
        return new TacoResource(taco);
    }


    @Override
    public TacoResource toModel(Taco entity) {
        return createModelWithId(entity.getId(), entity);
    }
}

以後對recentTacos()的調整

@GetMapping("/recentNew")
    public CollectionModel<TacoResource> recentTacos() {
        PageRequest page = PageRequest.of(
                0, 12, Sort.by("createdAt").descending());
        List<Taco> tacos = tacoRepo.findAll(page).getContent();

        CollectionModel<TacoResource> tacoResources =
                new TacoResourceAssembler(DesignTacoController.class, TacoResource.class).toCollectionModel(tacos);

        tacoResources.add(linkTo(methodOn(DesignTacoController.class)
                .recentTacos())
                        .withRel("recents"));
        return tacoResources;
    }

 

建立 IngredientResource 對象

@Data
public class IngredientResource extends RepresentationModel<IngredientResource> {
    public IngredientResource(Ingredient ingredient) {
        this.name = ingredient.getName();
        this.type = ingredient.getType();
    }

    private final String name;
    private final Ingredient.Type type;
}
IngredientResourceAssembler 對象
public class IngredientResourceAssembler extends RepresentationModelAssemblerSupport<Ingredient, IngredientResource> {
    /**
     * Creates a new {@link RepresentationModelAssemblerSupport} using the given controller class and resource type.
     *
     * @param controllerClass IngredientController {@literal IngredientController}.
     * @param resourceType    IngredientResource {@literal IngredientResource}.
     */
    public IngredientResourceAssembler(Class<?> controllerClass, Class<IngredientResource> resourceType) {
        super(controllerClass, resourceType);
    }

    @Override
    protected IngredientResource instantiateModel(Ingredient entity) {
        return new IngredientResource(entity);
    }

    @Override
    public IngredientResource toModel(Ingredient entity) {
        return createModelWithId(entity.getId(), entity);
    }
}

 

對 TacoResource 對象的修改

public class TacoResource extends RepresentationModel<TacoResource> {
    private static final IngredientResourceAssembler
            ingredientAssembler = new IngredientResourceAssembler(IngredientController.class, IngredientResource.class);

    @Getter
    private String name;

    @Getter
    private Date createdAt;

    @Getter
    private CollectionModel<IngredientResource> ingredients;

    public TacoResource(Taco taco) {
        this.name = taco.getName();
        this.createdAt = taco.getCreatedAt();
        this.ingredients = ingredientAssembler.toCollectionModel(taco.getIngredients());

    }
}

 程序清單6.7

@RepositoryRestController
public class RecentTacosController {
    private TacoRepository tacoRepo;

    public RecentTacosController(TacoRepository tacoRepo) {
        this.tacoRepo = tacoRepo;
    }

    /**
     * 雖然@GetMapping映射到了「/tacos/recent」路徑,可是類級別的@Repository RestController註解會確保這個路徑添加
     * Spring Data REST的基礎路徑做爲前綴。按照咱們的配置,recentTacos()方法將會處理針對「/api/tacos/recent」的GET請求。
     * */
    @GetMapping(path="/tacos/recent", produces="application/hal+json")
    public ResponseEntity<CollectionModel<TacoResource>> recentTacos() {
        PageRequest page = PageRequest.of(
                0, 12, Sort.by("createdAt").descending());
        List<Taco> tacos = tacoRepo.findAll(page).getContent();

        CollectionModel<TacoResource> tacoResources =
                new TacoResourceAssembler(DesignTacoController.class, TacoResource.class).toCollectionModel(tacos);

        tacoResources.add(
                linkTo(methodOn(RecentTacosController.class).recentTacos())
                        .withRel("recents"));
        return new ResponseEntity<>(tacoResources, HttpStatus.OK);
    }

}

 

程序清單6.8 爲Spring Data REST端點添加自定義的連接
    @Bean
    public RepresentationModelProcessor<PagedModel<EntityModel<Taco>>> tacoProcessor(EntityLinks links) {

        return new RepresentationModelProcessor<PagedModel<EntityModel<Taco>>>() {
            @Override
            public PagedModel<EntityModel<Taco>> process(PagedModel<EntityModel<Taco>> resource) {
                resource.add(
                        links.linkFor(Taco.class)
                                .slash("recent")
                                .withRel("recents"));
                return resource;
            }
        };
    }

 

另外一種寫法

若是你以爲寫使用資源裝配器有點麻煩,那麼你還能夠採用這種方法。

    @GetMapping("/employees")
    public ResponseEntity<CollectionModel<EntityModel<Taco>>> findAll() {
        PageRequest page = PageRequest.of(
                0, 12, Sort.by("createdAt").descending());
        List<EntityModel<Taco>> employees = StreamSupport.stream(tacoRepo.findAll(page).spliterator(), false)
                .map(employee -> new EntityModel<>(employee,
                        linkTo(methodOn(DesignTacoController.class).findOne(employee.getId())).withSelfRel(),
                        linkTo(methodOn(DesignTacoController.class).findAll()).withRel("employees")))
                .collect(Collectors.toList());

        return ResponseEntity.ok(
                new CollectionModel<>(employees,
                        linkTo(methodOn(DesignTacoController.class).findAll()).withSelfRel()));
    }
@GetMapping("/employees/{id}")
    public ResponseEntity<EntityModel<Taco>> findOne(@PathVariable long id) {

        return tacoRepo.findById(id)
                .map(employee -> new EntityModel<>(employee,
                        linkTo(methodOn(DesignTacoController.class).findOne(employee.getId())).withSelfRel(), //
                        linkTo(methodOn(DesignTacoController.class).findAll()).withRel("employees"))) //
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

參考來源:https://github.com/spring-projects/spring-hateoas-examples/tree/master/simplified

 

END

相關文章
相關標籤/搜索