示例项目:
JDK 17 SpringBoot 3.1.4 (当前最新版本)
Spring HATEOAS 2.1.2
Kotlin 1.8.22
使用Spring Initializr 创建示例项目,使用Gradle管理项目,语言选择Kotlin,JDK 17,依赖选择Spring Web 、Spring HATEOAS ,其他都会自动引入。
生成的示例项目的build.gradle内容如下:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id 'org.springframework.boot' version '3.1.4'
id 'io.spring.dependency-management' version '1.1.3'
id 'org.jetbrains.kotlin.jvm' version '1.8.22'
id 'org.jetbrains.kotlin.plugin.spring' version '1.8.22'
}
group = 'cn.myplus.examples'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
implementation 'org.jetbrains.kotlin:kotlin-reflect'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.withType(KotlinCompile) {
kotlinOptions {
freeCompilerArgs += '-Xjsr305=strict'
jvmTarget = '17'
}
}
tasks.named('test') {
useJUnitPlatform()
}
上面代码中引入了Log4j2做为日志输出框架,所以把spring-boot-starter-logging 做全局排除了。
使用Ideaj 打开项目后,会自动进行load gradle 项目,并下载依赖包。如果下载比较慢,可以参考Gradle 国内加速
我们使用User对象做为示例,只有4个属性,id、name、loginName是必填属性,放到构造方法中,phone 是非必填属性,可以放到类中。在本示例项目没有做Controller之外的代码编写,不涉及保存数据库等操作。
# User.kt
package cn.myplus.examples.springboothateoas.user
import java.io.Serializable
/**
* @project springboot-hateoas
* @description 用户信息实体类
* @author libo
* @date 2023-10-03 10:26:32
*/data class User(val id: String, val name: String, val loginName: String) : Serializable{
val phone: String? = null
}
创建UserController.kt的文件,基本的注解与Java代码编写一样。
package cn.myplus.examples.springboothateoas.user
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.RestController
@RestController
class UserController(@Autowired val userService: UserService) {
private val logger: Logger = LoggerFactory.getLogger(UserController::class.java)
}
hateoas风格的api主要是api的返回值中有相关的链接,使用者能够更容易使用。 在获取单个对象的api中,最常见的就是给出自身(self)的访问链接。
希望通过如下的请求地址
GET http://localhost:9999/user/admin
Accept: application/json
返回单个对象(admin用户)的信息
{
"id": "admin",
"name": "系统管理员",
"loginName": "admin",
"phone": null,
"links": [
{
"rel": "self",
"href": "http://localhost:9999/user/admin"
}
]
}
在UserController 中加入获取单个对象的接口
@GetMapping("/user/{id}")
fun getUser(@PathVariable("id") id: String): ResponseEntity<User> {
val user = getUserById(id)
return ResponseEntity<User>(user, HttpStatus.OK)
}
/**
* 获取单个实体,可以在Service类中实现。
*/
fun getUserById(id: String): User {
if (id.isBlank()) {
throw IllegalArgumentException("查询用户,参数错误.")
}
return User("admin", "系统管理员", "admin")
}
访问 http://localhost:9999/user/admin 这个地址,返回值如下:
{
"id": "admin",
"name": "系统管理员",
"loginName": "admin",
"phone": null
}
接口正常返回单个实体对象,但是没有HATEOAS 的内容,接下来我们就要加入HATEOAS的格式的返回值。
package cn.myplus.examples.springboothateoas.user
import org.springframework.hateoas.RepresentationModel
/**
* @project myplus5
* @description 用记信息接口返回类
* @author libo
* @date 2023-10-03 15:32:58
*/
class UserModel : RepresentationModel<UserModel>() {
var id: String = ""
var name: String = ""
var loginName: String = ""
var phone: String? = null
}
这个类继承org.springframework.hateoas.RepresentationModel类,这个类是Spring Hateoas 提供的包装links的基本model类。继承这个类就可以为模型(UserModel)增加link信息了。
@GetMapping("/user/{id}")
fun getUser(@PathVariable("id") id: String): ResponseEntity<UserModel> {
val user = getUserById(id)
val userModel: UserModel = UserModel()
//加入自身链接
userModel.add(
WebMvcLinkBuilder.linkTo(
WebMvcLinkBuilder.methodOn(UserController::class.java).getUser(user.id)
).withSelfRel()
)
// 以下为属性赋值
userModel.id = user.id
userModel.name = user.name
userModel.loginName = user.loginName
userModel.phone = user.phone
return ResponseEntity<UserModel>(userModel, HttpStatus.OK)
}
再次访问 http://localhost:9999/user/admin 这个地址,返回值如下:
{
"id": "admin",
"name": "系统管理员",
"loginName": "admin",
"phone": null,
"links": [
{
"rel": "self",
"href": "http://localhost:9999/user/admin"
}
]
}
已经加入self 的链接,只是在Controller 的返回值中加入了一些代码,其他都没变化。
Spring HATEOAS 还提供了RepresentationModelAssemblerSupport这样的模型转类,只需要继承这个抽象类就可以快速的实现模型与实体的转换操作。
package cn.myplus.examples.springboothateoas.user
import org.springframework.hateoas.CollectionModel
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn
import org.springframework.stereotype.Component
/**
* @project springboot-hateoas
* @description 用户实体与模型转换
* @author libo
* @date 2023-10-03 18:06:58
*/@Component
class UserModelAssembler :
RepresentationModelAssemblerSupport<User, UserModel>(UserController::class.java, UserModel::class.java) {
override fun toModel(entity: User): UserModel {
val userModel: UserModel = instantiateModel(entity)
userModel.add(linkTo(methodOn(UserController::class.java).getUser(entity.id)).withSelfRel())
userModel.id = entity.id
userModel.name = entity.name
userModel.loginName = entity.loginName
userModel.phone = entity.phone
return userModel;
}
override fun toCollectionModel(entities: MutableIterable<User>): CollectionModel<UserModel> {
val userModels: CollectionModel<UserModel> = super.toCollectionModel(entities)
userModels.add(methodOn(UserController::class.java).getUsers(null)?.let { linkTo(it).withSelfRel() })
return userModels
}
}
这个类把UserController和UserModel 做为构造参数,创建一个类,实现toModel方法,就可以实现model转换的操作。
@RestController
class UserController(@Autowired val userService: UserService) {
@Autowired
private lateinit var userModelAssembler: UserModelAssembler
/**
* @return 用户信息.
* @param id 用户id
* @author libo
*/ @GetMapping("/user/{id}")
fun getUser(@PathVariable("id") id: String): ResponseEntity<UserModel> {
val user = getUserById(id)
return ResponseEntity<UserModel>(userModelAssembler.toModel(user), HttpStatus.OK)
}
这样即可以达到代码的复用,Controller类看来也不是那么乱了。
完整代码已放到gitee仓库,github仓库
因为使用Gradle 管理项目,在下Gradle 及依赖的jar还比较慢。可以配置国内加速的办法。
export GRADLE_USER_HOME="/env/repository"
allprojects {
repositories {
maven { url '/env/repository'}
mavenLocal()
maven { name "Alibaba" ; url "https://maven.aliyun.com/nexus/content/groups/public/" }
mavenCentral()
}
buildscript {
repositories {
maven { name "Alibaba" ; url 'https://maven.aliyun.com/nexus/content/groups/public/' }
}
}
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。