使用 Javalin 和 Vue 的简单前端
使用 Javalin 和 Vue 的简单前端
在本教程中,你将学习如何使用 Javalin 和 Vue 创建简单的前端。本教程内容丰富,涵盖单文件组件、路由、错误处理、应用程序布局、访问管理(以及身份验证)以及服务器和客户端之间的状态共享。
本教程与我平常写的教程略有不同,因为它会比较主观。背景部分会稍微介绍一下我是如何构思出本教程中使用的方法的,但如果您只是想学习如何编写简单的前端,可以直接跳到设置部分。
背景
我从事 Web 开发大约十年了。服务器端渲染方面,我一开始用的是纯 HTML(框架集!),然后是 PHP、JSP、JSTL、Twirl(Scala),最后是 Velocity 模板(Java)。前端方面,我用过 jQuery/Zepto,然后是 Knockout、Angular、Meteor,最后是 Vue。样式方面,我用过 Less、SASS(SCSS),最后是 CSS 变量。构建方面,我用过 Bower、NPM、Yarn、Grunt 和 Gulp,总共配置过一个 Webpack 项目。
前端在过去十年中取得了长足的进步,声明式/反应式视图库比过去的 DOM 操作混乱要好用得多。
但现代前端架构通常也极其复杂(且不必要)。我不会对此大肆抨击,因为我不会动摇任何一方的立场。本教程将展示一种使用纯 Vue 创建简洁现代前端的替代方法,避免了 Vue 通常带来的大部分复杂性。无需 Node、NPM 或 Webpack,无需状态管理、reducer 或 sagas。无需 rehydration、无需 tree shake、无需代码拆分,也无需无休止的传递依赖。无需任何类型的样板代码或管道代码。
只是商业逻辑。
设置
我们的后端将采用 Kotlin 语言,并使用 Maven 进行构建。我们需要引入 Javalin(Web 服务器框架)。
我们还将为前端添加 Vue(视图库):
现在我们已经准备好了所有依赖项,我们需要配置我们的 Web 服务器。
让我们创建/src/main/kotlin/javalinvue/JavalinVueExampleApp.kt:
import io.javalin.Javalin
fun main() {
val app = Javalin.create { config ->
config.staticFiles.enableWebjars()
config.vue.vueInstanceNameInJs = “app” // only required for Vue 3, is defined in layout.html
}.start(7070)
}
我们还需要一个 HTML 文件来加载前端依赖项并初始化 Vue。
让我们创建/src/main/resources/vue/layout.html:
打开http://localhost:7070/users/查看用户列表。点击某个用户时,会显示一个空白页。
我们来解决这个问题/src/main/resources/vue/views/user-profile.vue:
- User ID
- {{user.id}}
- Name
- {{user.name}}
- {{user.email}}
- Birthday
- {{user.userDetails.dateOfBirth}}
- Salary
- {{user.userDetails.salary}}
让我们用以下方式来结束我们的观点/src/main/resources/vue/views/not-found.vue:
Page not found (error 404)
Page not found (error 404)
现在前端和后端都已完成,是时候通过添加访问管理来使事情变得更加复杂了。
访问管理
Javalin 中的访问管理由前置过滤器 (before-filters) 和路由角色 (route-roles) 组合而成。请求是否有效由开发者决定。为了简单起见,我们将使用 basic-auth 来保护我们的应用程序,但您可以使用任何您想要的技术和身份提供者。
首先,我们需要定义角色。应用的某些部分应该对所有人开放(例如用户概览和 404 页面),而其他部分则需要登录后才能访问(例如用户个人资料)。
两个角色就足够了:ANYONE和LOGGED_IN。我们将这些角色添加到端点(包括视图和 API),并创建一个过滤器:
import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.get
import io.javalin.security.RouteRole
import io.javalin.http.Header
import io.javalin.http.Context
import io.javalin.http.HttpStatus
import io.javalin.http.UnauthorizedResponse
import io.javalin.vue.VueComponent
enum class Role : RouteRole { ANYONE, LOGGED_IN }
fun main() {
val app = Javalin.create { config ->
config.staticFiles.enableWebjars()
config.vue.apply {
stateFunction = { ctx -> mapOf("currentUser" to ctx.currentUser()) }
vueInstanceNameInJs = "app"
}
config.router.mount {
it.beforeMatched { ctx ->
if (Role.LOGGED_IN in ctx.routeRoles() && ctx.currentUser() == null) {
ctx.header(Header.WWW_AUTHENTICATE, "Basic")
throw UnauthorizedResponse()
}
}
}.apiBuilder { // frontend routes
get("/", VueComponent("hello-world"), Role.ANYONE)
get("/users", VueComponent("user-overview"), Role.ANYONE)
get("/users/{user-id}", VueComponent("user-profile"), Role.LOGGED_IN)
}.apiBuilder { // api routes
get("/api/users", UserController::getAll, Role.ANYONE)
get("/api/users/{user-id}", UserController::getOne, Role.LOGGED_IN)
}
}.apply {
error(HttpStatus.NOT_FOUND, "html", VueComponent("not-found"))
}.start(7070)
}
private fun Context.currentUser() = this.basicAuthCredentials()?.username
这只是一个例子,我们的身份验证并不完全安全。只要用户在 basic-auth 用户名字段中输入 任何内容,我们就会登录用户。我们完全忽略密码。
服务器端状态
现在我们可以登录用户了,如果客户端能够知道当前用户,那就太好了。我们的服务器也知道,所以我们需要以某种方式传递这些信息。这可以通过设置 JavalinVue 状态函数来解决:
config.vue.stateFunction = { ctx -> mapOf(“currentUser” to currentUser(ctx)) }
这行代码设置了一个将为每个 运行的函数VueComponent,因此所有组件现在都可以访问当前用户(如果有)。由于 basic-auth 是按目录运行的,因此框架将仅显示当前用户http://localhost:7070/users/(用户概览)及其子路径(各个配置文件)。让我们将其添加到app-frame.vue:
JavalinVueExampleApp.kt包含服务器配置(路由、错误处理程序、访问管理)
UserController.kt包含虚假用户列表,以及获取它们的方法(getAll、getOne)
layout.html加载前端依赖项并初始化 Vue
app-frame.vue有一个标题和一些包含在所有组件中的全局样式
user-overview.vue显示用户列表
user-profile.vue显示一个用户的附加详细信息(需要登录)
not-found.vue显示 404 错误页面
pom.xml包含我们所有的依赖项
由于我们的前端依赖项是预先打包的 WebJars,因此我们不需要 NPM,也不需要手动管理任何前端库。项目结构非常清晰:
javalinvue-example
├───src
│ └─── main
│ └───kotlin
│ ├───javalinvue
│ │ ├───UserController.kt
│ │ └───Main.kt
│ └───resources
│ ├───components
│ │ └───app-frame.vue
│ ├───components
│ │ ├───not-found.vue
│ │ ├───user-overview.vue
│ │ └───user-profile.vue
│ └───layout.html
└───pom.xml
差不多就是这样了。后记会稍微讨论一下这项技术,但和背景部分一样,如果你不太感兴趣,可以跳过。感谢阅读!
后记
本教程中描述的架构具备现代前端的诸多优势。我们拥有完整的客户端渲染,无需 DOM 操作,并且能够充分使用 Vue(我们甚至拥有单文件组件)。不过,我们没有任何客户端路由。每个页面(每个服务器端路由)都有一个 Vue 实例,这使得状态管理更加轻松。每个视图负责其自身的状态,仅此而已。每次用户浏览网站时,应用都是从头构建的。共享状态(登录用户、当前主题等)可以在服务器端设置,并包含在所有视图中。
我们错过了一些好东西。我们无法热重载组件内容或注入新样式,必须手动刷新页面才能看到变化。不过,更改会立即生效,因此刷新通常需要约 10 毫秒。
我在生产环境中运行了几个采用此设置的应用。最大的一个是一个客户门户,包含 30 个组件和 25 个视图(页面),所有组件的总大小只有 20kb(GZIP 压缩前为 100kb)。它已经运行了一年,我一直在等待问题和缺陷的出现,但至今仍未发现任何问题。
更新,2022 年 10 月:该应用已运行四年,没有任何问题。现在它正在使用JavalinVue.optimizeDependenciesJavalin 4+ 默认启用的配置选项。这使得平均请求大小(gzip 压缩后)降至约 8kb。
性能相当不错。应用加载速度很快,而且没有闪烁。下面是 Chrome 审核结果,针对我之前提到的 30 个组件 + 25 个视图的应用:
应用性能
优点和缺点
优点
最低要求 - 后端开发人员只需阅读 Vue 文档即可在几分钟内上手。无需了解当前的 JavaScript 生态系统。
没有样板
单文件组件
没有复杂的构建管道或传递依赖
无需状态管理
表现良好
可以轻松地从文档中复制粘贴(Vue 文档在其示例中使用 CDN,而大多数项目使用 Webpack)
服务器端路由
缺点
无法运行组件的单元测试
无需转译(在旧浏览器中,如果不破坏代码,则无法使用最新的 JS 功能)
没有热重载或样式注入
你错过了很多 JS 生态系统(也可以被认为是优点)
您的前端需要一个 Kotlin/Java 服务器
服务器和客户端紧密耦合
最后编辑:Jeebiz 更新时间:2025-05-04 00:55