Spring 應用中的 JSON API

REST,Spring
Remote
0
05:34 PM · Dec 01 ,2025

1. 概述

在本文中,我們將開始探索 JSON-API 規範,以及如何將其集成到 Spring 支持的 REST API 中。

我們將使用 Katharsis 在 Java 中實現 JSON-API – 並且我們將設置一個使用 Katharsis 的 Spring 應用程序,因此我們只需要一個 Spring 應用程序。

2. Maven

首先,讓我們來看一下我們的 Maven 配置——我們需要將以下依賴項添加到我們的 pom.xml

<dependency>
    <groupId>io.katharsis</groupId>
    <artifactId>katharsis-spring</artifactId>
    <version>3.0.2</version>
</dependency>

3. 用户資源

接下來,讓我們來查看我們的用户資源:

@JsonApiResource(type = "users")
public class User {

    @JsonApiId
    private Long id;

    private String name;

    private String email;
}

請注意:

  • @JsonApiResource 註解用於定義我們的資源 User
  • @JsonApiId 註解用於定義資源標識符

並且非常簡要地説 – 這裏的持久化將是一個 Spring Data 存儲庫:

public interface UserRepository extends JpaRepository<User, Long> {}

4. 資源倉庫

接下來,我們討論一下我們的資源倉庫——每個資源都應該有一個 ResourceRepositoryV2 以發佈其可用的API操作:

@Component
public class UserResourceRepository implements ResourceRepositoryV2<User, Long> {

    @Autowired
    private UserRepository userRepository;

    @Override
    public User findOne(Long id, QuerySpec querySpec) {
        Optional<User> user = userRepository.findById(id); 
        return user.isPresent()? user.get() : null;
    }

    @Override
    public ResourceList<User> findAll(QuerySpec querySpec) {
        return querySpec.apply(userRepository.findAll());
    }

    @Override
    public ResourceList<User> findAll(Iterable<Long> ids, QuerySpec querySpec) {
        return querySpec.apply(userRepository.findAllById(ids));
    }

    @Override
    public <S extends User> S save(S entity) {
        return userRepository.save(entity);
    }

    @Override
    public void delete(Long id) {
        userRepository.deleteById(id);
    }

    @Override
    public Class<User> getResourceClass() {
        return User.class;
    }

    @Override
    public <S extends User> S create(S entity) {
        return save(entity);
    }
}

請注意,這當然與Spring控制器非常相似。

5. Katharsis 配置

由於我們使用了 katharsis-spring,我們只需要在我們的 Spring Boot 應用程序中導入 KatharsisConfigV3:

@Import(KatharsisConfigV3.class)

並且在我們的 application.properties 中配置 Katharsis 參數:

katharsis.domainName=http://localhost:8080
katharsis.pathPrefix=/

有了這些 – 我們現在可以開始消費 API;例如:

  • GET “http://localhost:8080/users“: 獲取所有用户.
  • POST “http://localhost:8080/users“: 添加新用户,以及更多.

6. Relationships

接下來,我們討論一下如何在 JSON API 中處理實體之間的關係。

6.1. Role Resource

首先,我們介紹一個新資源——Role:

@JsonApiResource(type = "roles")
public class Role {

    @JsonApiId
    private Long id;

    private String name;

    @JsonApiRelation
    private Set<User> users;
}

然後設置User和Role之間的多對多關係:

@JsonApiRelation(serialize=SerializeType.EAGER)
private Set<Role> roles;

6.2. Role Resource Repository

快速説明一下——我們的Role資源倉庫:

@Component
public class RoleResourceRepository implements ResourceRepositoryV2<Role, Long> {

    @Autowired
    private RoleRepository roleRepository;

    @Override
    public Role findOne(Long id, QuerySpec querySpec) {
        Optional<Role> role = roleRepository.findById(id); 
        return role.isPresent()? role.get() : null;
    }

    @Override
    public ResourceList<Role> findAll(QuerySpec querySpec) {
        return querySpec.apply(roleRepository.findAll());
    }

    @Override
    public ResourceList<Role> findAll(Iterable<Long> ids, QuerySpec querySpec) {
        return querySpec.apply(roleRepository.findAllById(ids));
    }

    @Override
    public <S extends Role> S save(S entity) {
        return roleRepository.save(entity);
    }

    @Override
    public void delete(Long id) {
        roleRepository.deleteById(id);
    }

    @Override
    public Class<Role> getResourceClass() {
        return Role.class;
    }

    @Override
    public <S extends Role> S create(S entity) {
        return save(entity);
    }
}

需要注意的是,這個單一資源倉庫不處理關係方面——這需要一個單獨的倉庫。

6.3. Relationship Repository

為了處理User和Role之間的多對多關係,我們需要創建一個新的倉庫風格:

@Component
public class UserToRoleRelationshipRepository implements RelationshipRepositoryV2<User, Long, Role, Long> {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Override
    public void setRelation(User User, Long roleId, String fieldName) {}

    @Override
    public void setRelations(User user, Iterable<Long> roleIds, String fieldName) {
        Set<Role> roles = new HashSet<Role>();
        roles.addAll(roleRepository.findAllById(roleIds));
        user.setRoles(roles);
        userRepository.save(user);
    }

    @Override
    public void addRelations(User user, Iterable<Long> roleIds, String fieldName) {
        Set<Role> roles = user.getRoles();
        roles.addAll(roleRepository.findAllById(roleIds));
        user.setRoles(roles);
        userRepository.save(user);
    }

    @Override
    public void removeRelations(User user, Iterable<Long> roleIds, String fieldName) {
        Set<Role> roles = user.getRoles();
        roles.removeAll(roleRepository.findAllById(roleIds));
        user.setRoles(roles);
        userRepository.save(user);
    }

    @Override
    public Role findOneTarget(Long sourceId, String fieldName, QuerySpec querySpec) {
        return null;
    }

    @Override
    public ResourceList<Role> findManyTargets(Long sourceId, String fieldName, QuerySpec querySpec) {
        final Optional<User> userOptional = userRepository.findById(sourceId);
        User user = userOptional.isPresent() ? userOptional.get() : new User();
        return  querySpec.apply(user.getRoles());
    }

    @Override
    public Class<User> getSourceResourceClass() {
        return User.class;
    }

    @Override
    public Class<Role> getTargetResourceClass() {
        return Role.class;
    }
}

我們忽略了這裏的一些單方法,在關係倉庫中。

7. 測試

最後,讓我們分析一些請求並真正瞭解 JSON-API 輸出是什麼樣的。

我們將開始檢索單個用户資源(id = 2):

GET http://localhost:8080/users/2

{
    "data":{
        "type":"users",
        "id":"2",
        "attributes":{
            "email":"[email protected]",
            "username":"tom"
        },
        "relationships":{
            "roles":{
                "links":{
                    "self":"http://localhost:8080/users/2/relationships/roles",
                    "related":"http://localhost:8080/users/2/roles"
                }
            }
        },
        "links":{
            "self":"http://localhost:8080/users/2"
        }
    },
    "included":[
        {
            "type":"roles",
            "id":"1",
            "attributes":{
                "name":"ROLE_USER"
            },
            "relationships":{
                "users":{
                    "links":{
                        "self":"http://localhost:8080/roles/1/relationships/users",
                        "related":"http://localhost:8080/roles/1/users"
                    }
                }
            },
            "links":{
                "self":"http://localhost:8080/roles/1"
            }
        }
    ]
}

要點:

  • 資源的 主要屬性 位於 data.attributes
  • 資源的 主要關係 位於 data.relationships
  • 由於我們使用了 @JsonApiRelation(serialize=SerializeType.EAGER)roles 關係進行了配置,因此該關係包含在 JSON 中,並且位於節點 included

接下來,讓我們獲取包含角色的集合資源:

GET http://localhost:8080/roles

{
    "data":[
        {
            "type":"roles",
            "id":"1",
            "attributes":{
                "name":"ROLE_USER"
            },
            "relationships":{
                "users":{
                    "links":{
                        "self":"http://localhost:8080/roles/1/relationships/users",
                        "related":"http://localhost:8080/roles/1/users"
                    }
                }
            },
            "links":{
                "self":"http://localhost:8080/roles/1"
            }
        },
        {
            "type":"roles",
            "id":"2",
            "attributes":{
                "name":"ROLE_ADMIN"
            },
            "relationships":{
                "users":{
                    "links":{
                        "self":"http://localhost:8080/roles/2/relationships/users",
                        "related":"http://localhost:8080/roles/2/users"
                    }
                }
            },
            "links":{
                "self":"http://localhost:8080/roles/2"
            }
        }
    ],
    "included":[

    ]
}

快速總結一下,我們獲取了系統中所有角色——作為一個數組,位於 data 節點中

8. 結論

JSON-API 是一項絕佳規範——它終於為我們API中的JSON添加了結構,並真正實現了超媒體API。

本文探討了一種在Spring應用中設置它的方法。但無論這種實現方式如何,我認為該規範本身非常具有前景。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.