1、快速开始
Javalin 快速开始
https://javalin.io/documentation#getting-started
添加依赖项:
如果您想要将 Javalin 与测试工具 Jackson 和 Logback 一起使用,则可以使用 artifact id javalin-bundle 代替javalin。
开始编码:
import io.javalin.Javalin;
public class HelloWorld {
public static void main(String[] args) {
var app = Javalin.create(/*config*/)
.get("/", ctx -> ctx.result("Hello World"))
.start(7070);
}
}
处理程序
Javalin 有三种主要的处理程序类型:前置处理程序、端点处理程序和后置处理程序。(此外还有异常处理程序和错误处理程序,但我们稍后会讲到它们)。前置处理程序、端点处理程序和后置处理程序都需要三个部分:
- 动词,以下之一:before,,,,,,(…,,,,)get,post,put,patch,delete,after head,options,trace,connect
- 路径,例如:/,,/hello-world/hello/{name}
- 处理程序实现,例如ctx -> { … }、MyClass implements Handler等
该接口的返回类型为 void。您可以使用、 或 之Handler类的方法来设置将返回给用户的响应。ctx.result(result)ctx.json(obj)ctx.future(future)
如果您为同一路径添加多个 before/after 处理程序,它们将按照添加的顺序执行。这对于添加身份验证、缓存、日志记录等功能非常有用。
处理程序之前
每次请求(包括静态文件)之前都会匹配 Before-handler。
您可能知道其他库中的 before-handler 是过滤器、拦截器或中间件。
app.before(ctx -> {
// runs before all requests
});
app.before(“/path/*”, ctx -> {
// runs before request to /path/*
});
在某些情况下,你可能希望仅当请求被匹配(而不是 404)时才运行 before-handler。在这种情况下,你可以使用以下app.beforeMatched方法:
app.beforeMatched(ctx -> {
// runs before all matched requests (including static files)
});
端点处理程序
端点处理程序是主要的处理程序类型,它定义了您的 API。您可以添加 GET 处理程序来向客户端提供数据,或者添加 POST 处理程序来接收数据。常用方法直接在Javalin类上支持(GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS),不常用操作(TRACE、CONNECT)则通过 来支持Javalin#addHandler。
端点处理程序按照其定义的顺序进行匹配。
您可能知道端点处理程序是来自其他库的路由或中间件。
app.get(“/output”, ctx -> {
// some code
ctx.json(object);
});
app.post(“/input”, ctx -> {
// some code
ctx.status(201);
});
处理程序路径可以包含路径参数。这些可通过以下方式获取ctx.pathParam(“key”):
app.get(“/hello/{name}”, ctx -> { // the {} syntax does not allow slashes (‘/‘) as part of the parameter
ctx.result(“Hello: “ + ctx.pathParam(“name”));
});
app.get(“/hello/
ctx.result(“Hello: “ + ctx.pathParam(“name”));
});
处理程序路径还可以包含通配符参数:
app.get(“/path/*”, ctx -> { // will match anything starting with /path/
ctx.result(“You are here because “ + ctx.path() + “ matches “ + ctx.matchedPath());
});
但是,您无法提取通配符的值。如果需要此行为,请使用斜线 (
处理程序之后
每次请求后都会运行后续处理程序(即使发生异常)
您可能知道后处理程序是来自其他库的过滤器、拦截器或中间件。
app.after(ctx -> {
// run after all requests
});
app.after(“/path/*”, ctx -> {
// runs after request to /path/*
});
在某些情况下,你可能希望仅当请求被匹配(而不是 404)时才运行后续处理程序。在这种情况下,你可以使用以下app.afterMatched方法:
app.afterMatched(ctx -> {
// runs after all matched requests (including static files)
});
语境
该Context对象提供了处理 http 请求所需的一切。它包含底层的 servlet 请求和 servlet 响应,以及一系列 getter 和 setter 方法。
// Request methods
body() // request body as string
bodyAsBytes() // request body as array of bytes
bodyAsClass(clazz) // request body as specified class (deserialized from JSON)
bodyStreamAsClass(clazz) // request body as specified class (memory optimized version of above)
bodyValidator(clazz) // request body as validator typed as specified class
bodyInputStream() // the underyling input stream of the request
uploadedFile(“name”) // uploaded file by name
uploadedFiles(“name”) // all uploaded files by name
uploadedFiles() // all uploaded files as list
uploadedFileMap() // all uploaded files as a “names by files” map
formParam(“name”) // form parameter by name, as string
formParamAsClass(“name”, clazz) // form parameter by name, as validator typed as specified class
formParams(“name”) // list of form parameters by name
formParamMap() // map of all form parameters
pathParam(“name”) // path parameter by name as string
pathParamAsClass(“name”, clazz) // path parameter as validator typed as specified class
pathParamMap() // map of all path parameters
basicAuthCredentials() // basic auth credentials (or null if not set)
attribute(“name”, value) // set an attribute on the request
attribute(“name”) // get an attribute on the request
attributeOrCompute(“name”, ctx -> {}) // get an attribute or compute it based on the context if absent
attributeMap() // map of all attributes on the request
contentLength() // content length of the request body
contentType() // request content type
cookie(“name”) // request cookie by name
cookieMap() // map of all request cookies
header(“name”) // request header by name (can be used with Header.HEADERNAME)
headerAsClass(“name”, clazz) // request header by name, as validator typed as specified class
headerMap() // map of all request headers
host() // host as string
ip() // ip as string
isMultipart() // true if the request is multipart
isMultipartFormData() // true if the request is multipart/formdata
method() // request methods (GET, POST, etc)
path() // request path
port() // request port
protocol() // request protocol
queryParam(“name”) // query param by name as string
queryParamAsClass(“name”, clazz) // query param by name, as validator typed as specified class
queryParamsAsClass(“name”, clazz) // query param list by name, as validator typed as list of specified class
queryParams(“name”) // list of query parameters by name
queryParamMap() // map of all query parameters
queryString() // full query string
scheme() // request scheme
sessionAttribute(“name”, value) // set a session attribute
sessionAttribute(“name”) // get a session attribute
consumeSessionAttribute(“name”) // get a session attribute, and set value to null
cachedSessionAttribute(“name”, value) // set a session attribute, and cache the value as a request attribute
cachedSessionAttribute(“name”) // get a session attribute, and cache the value as a request attribute
cachedSessionAttributeOrCompute(…) // same as above, but compute and set if value is absent
sessionAttributeMap() // map of all session attributes
url() // request url
fullUrl() // request url + query string
contextPath() // request context path
userAgent() // request user agent
req() // get the underlying HttpServletRequest
// Response methods
result(“result”) // set result stream to specified string (overwrites any previously set result)
result(byteArray) // set result stream to specified byte array (overwrites any previously set result)
result(inputStream) // set result stream to specified input stream (overwrites any previously set result)
future(futureSupplier) // set the result to be a future, see async section (overwrites any previously set result)
writeSeekableStream(inputStream) // write content immediately as seekable stream (useful for audio and video)
result() // get current result stream as string (if possible), and reset result stream
resultInputStream() // get current result stream
contentType(“type”) // set the response content type
header(“name”, “value”) // set response header by name (can be used with Header.HEADERNAME)
redirect(“/path”, code) // redirect to the given path with the given status code
status(code) // set the response status code
status() // get the response status code
cookie(“name”, “value”, maxAge) // set response cookie by name, with value and max-age (optional).
cookie(cookie) // set cookie using javalin Cookie class
removeCookie(“name”, “/path”) // removes cookie by name and path (optional)
json(obj) // calls result(jsonString), and also sets content type to json
jsonStream(obj) // calls result(jsonStream), and also sets content type to json
html(“html”) // calls result(string), and also sets content type to html
render(“/template.tmpl”, model) // calls html(renderedTemplate)
res() // get the underlying HttpServletResponse
// Other methods
async(runnable) // lifts request out of Jetty’s ThreadPool, and moves it to Javalin’s AsyncThreadPool
async(asyncConfig, runnable) // same as above, but with additonal config
handlerType() // handler type of the current handler (BEFORE, AFTER, GET, etc)
appData(typedKey) // get data from the Javalin instance (see app data section below)
with(pluginClass) // get context plugin by class, see plugin section below
matchedPath() // get the path that was used to match this request (ex, “/hello/{name}”)
endpointHandlerPath() // get the path of the endpoint handler that was used to match this request
cookieStore() // see cookie store section below
skipRemainingHandlers() // skip all remaining handlers for this request
应用数据
应用数据可以通过 注册到 Javalin 实例上Javalin#create,然后通过appData(…)中的方法访问Context。您需要为您的数据创建一个类型化的键,然后将其注册到 Javalin 实例上。
// register a custom attribute
var myKey = new Key
var app = Javalin.create(config -> {
config.appData(myKey, myValue);
});
// access a custom attribute
var myValue = ctx.appData(myKey); // var will be inferred to MyValue
// call a custom method on a custom attribute
ctx.appData(myKey).myMethod();
将密钥存储为静态字段会很有帮助,但您也可以在每次需要时重新创建密钥。
饼干店
该类CookieStore提供了一种在处理程序、请求甚至服务器之间共享信息的便捷方法:
ctx.cookieStore().set(key, value); // store any type of value
ctx.cookieStore().get(key); // read any type of value
ctx.cookieStore().clear(); // clear the cookie-store
cookieStore 的工作原理如下:
与传入请求匹配的第一个处理程序将使用当前存储在 cookie 中的数据(如果有)填充 cookie-store-map。
现在,该映射可以用作同一请求周期中处理程序之间的状态,其方式与ctx.attribute()
在请求周期结束时,cookie-store-map 会被序列化、base64 编码,并以 cookie 的形式写入响应中。这允许你在请求和服务器之间共享该 map(如果你在负载均衡器后运行多个服务器)。
例子:
serverOneApp.post(“/cookie-storer”, ctx -> {
ctx.cookieStore().set(“string”, “Hello world!”);
ctx.cookieStore().set(“i”, 42);
ctx.cookieStore().set(“list”, Arrays.asList(“One”, “Two”, “Three”));
});
serverTwoApp.get(“/cookie-reader”, ctx -> { // runs on a different server than serverOneApp
String string = ctx.cookieStore().get(“string”)
int i = ctx.cookieStore().get(“i”)
List
});
由于客户端存储了 cookie,因此get对 的请求将能够检索在对serverTwoApp 中传递的信息。postserverOneApp
请注意,cookie 的最大大小为 4kb。
WebSockets
Javalin 处理 WebSocket 的方式非常直观。你可以使用路径声明一个端点,并在 lambda 表达式中配置不同的事件处理程序:
app.ws(“/websocket/{path}”, ws -> {
ws.onConnect(ctx -> System.out.println(“Connected”));
});
总共支持五种事件:
ws.onConnect(WsConnectContext)
ws.onError(WsErrorContext)
ws.onClose(WsCloseContext)
ws.onMessage(WsMessageContext)
ws.onBinaryMessage(WsBinaryMessageContext)
不同的上下文会WsContext暴露不同的内容,例如, WsMessageContext会有一个方法.message()返回客户端发送的消息。不同上下文之间的差异很小,完整的概述可以在WsContext部分查看。
您可以在常见问题解答 - 并发中了解 Javalin 如何处理 WebSocket 并发。
WsBefore
这app.wsBefore会添加一个在 WebSocket 处理程序之前运行的处理程序。每个 WebSocket 端点可以拥有任意数量的前置处理程序,并且所有事件均受支持。
app.wsBefore(ws -> {
// runs before all WebSocket requests
});
app.wsBefore(“/path/*”, ws -> {
// runs before websocket requests to /path/*
});
WsEndpoint
WebSocket 端点用 声明app.ws(path, handler)。WebSocket 处理程序需要唯一的路径。
app.ws(“/websocket/{path}”, ws -> {
ws.onConnect(ctx -> System.out.println(“Connected”));
ws.onMessage(ctx -> {
User user = ctx.messageAsClass(User.class); // convert from json
ctx.send(user); // convert to json and send back
});
ws.onBinaryMessage(ctx -> System.out.println(“Message”))
ws.onClose(ctx -> System.out.println(“Closed”));
ws.onError(ctx -> System.out.println(“Errored”));
});
后
这app.wsAfter会添加一个在 WebSocket 处理程序之后运行的处理程序。每个 WebSocket 端点可以拥有任意数量的后续处理程序,并且所有事件均受支持。
app.wsAfter(ws -> {
// runs after all WebSocket requests
});
app.wsAfter(“/path/*”, ws -> {
// runs after websocket requests to /path/*
});
WsContext
该WsContext对象提供了处理 websocket 请求所需的一切。它包含底层的 websocket 会话和 servlet 请求,以及用于向客户端发送消息的便捷方法。
// Session methods
send(obj) // serialize object to json string and send it to client
send(“message”) // send string to client
send(byteBuffer) // send bytes to client
sendAsClass(obj, clazz) // serialize object to json string and send it to client
// Upgrade Context methods (getters)
matchedPath() // get the path that was used to match this request (ex, “/hello/{name}”)
host() // host as string
queryParam(“name”) // query param by name as string
queryParamAsClass(“name”, clazz) // query param parameter by name, as validator typed as specified class
queryParams(“name”) // list of query parameters by name
queryParamMap() // map of all query parameters
queryString() // full query string
pathParam(“name”) // path parameter by name as string
pathParamAsClass(“name”, clazz) // path parameter as validator typed as specified class
pathParamMap() // map of all path parameters
header(“name”) // request header by name (can be used with Header.HEADERNAME)
headerAsClass(“name”, clazz) // request header by name, as validator typed as specified class
headerMap() // map of all request headers
cookie(“name”) // request cookie by name
cookieMap() // map of all request cookies
attribute(“name”, value) // set an attribute on the request
attribute(“name”) // get an attribute on the request
attributeMap() // map of all attributes on the request
sessionAttribute(“name”) // get a session attribute
sessionAttributeMap() // map of all session attributes
sendPing() // send a ping to the client
sendPing(bytes) // send a ping with data to the client
enableAutomaticPings() // enable automatic pinging to avoid timeouts
enableAutomaticPings(1, HOURS, bytes) // enable automatic pinging with custom interval and/or data
disableAutomaticPings() // disable automatic pinging
closeSession() // close the session
closeSession(closeStatus) // close the session with a CloseStatus
closeSession(400, “reason”) // close the session with a status and reason
WsMessageContext
message() // receive a string message from the client
messageAsClass(clazz) // deserialize message from client
WsBinaryMessageContext
data() // receive a byte array of data from the client
offset() // the offset of the data
length() // the length of the data
WsCloseContext
status() // the int status for why connection was closed
reason() // the string reason for why connection was closed
WsErrorContext
error() // the throwable error that occurred
WsConnectContext
该类WsConnectContext不会向基础类添加任何内容WsContext
处理程序组
apiBuilder()您可以使用和方法对端点进行分组path()。这apiBuilder() 两个方法会创建一个 Javalin 的临时静态实例,以便您可以跳过处理程序前的app.或router.前缀。这相当于调用ApiBuilder.get(app/router, …),转换为app/router.get(…)。它不是保存静态信息的全局单例,因此您可以在多个位置和多个线程中安全地使用它。
您可以使用 导入所有 HTTP 方法import static io.javalin.apibuilder.ApiBuilder.*。
config.router.apiBuilder(() -> {
path(“/users”, () -> {
get(UserController::getAllUsers);
post(UserController::createUser);
path(“/{id}”, () -> {
get(UserController::getUser);
patch(UserController::updateUser);
delete(UserController::deleteUser);
});
ws(“/events”, UserController::webSocketEvents);
});
});
请注意,如果您没有自行添加,则path()路径会以 为前缀。 这意味着和是等价的。/
path(“api”, …)path(“/api”, …)
CrudHandler
这CrudHandler是一个可以在apiBuilder()调用中使用的接口:
config.router.apiBuilder(() -> {
crud(“users/{user-id}”, new UserController());
});
它实现了五种最常见的 crud 操作:
interface CrudHandler {
getAll(ctx)
getOne(ctx, resourceId)
create(ctx)
update(ctx, resourceId)
delete(ctx, resourceId)
}
验证
您可以使用 Javalin 的Validator类来获取查询、表单和路径参数,以及标头和请求正文:
ctx.queryParamAsClass(“paramName”, MyClass.class) // creates a Validator
ctx.formParamAsClass(“paramName”, MyClass.class) // creates a Validator
ctx.pathParamAsClass(“paramName”, MyClass.class) // creates a Validator
ctx.headerAsClass(“headerName”, MyClass.class) // creates a Validator
ctx.bodyValidator(MyClass.class) // creates a Validator
您还可以通过手动创建自己的验证器 Validator.create(clazz, value, fieldName)。
验证器 API
allowNullable() // turn the Validator into a NullableValidator (must be called first)
check(predicate, “error”) // add a check with a ValidationError(“error”) to the Validator
check(predicate, validationError) // add a check with a ValidationError to the Validator (can have args for localization)
get() // return the validated value as the specified type, or throw ValidationException
getOrThrow(exceptionFunction) // return the validated value as the specified type, or throw custom exception
getOrDefault() // return default-value if value is null, else call get()
errors() // get all the errors of the Validator (as map(“fieldName”, List
验证示例
// VALIDATE A SINGLE QUERY PARAMETER WITH A DEFAULT VALUE /////////////////////////////////////////////
Integer myValue = ctx.queryParamAsClass(“value”, Integer.class).getOrDefault(788) // validate value
ctx.result(value) // return validated value to the client
// GET ?value=a would yield HTTP 400 - {“my-qp”:[{“message”:”TYPE_CONVERSION_FAILED”,”args”:{},”value”:”a”}]}
// GET ?value=1 would yield HTTP 200 - 1 (the validated value)
// GET ? would yield HTTP 200 - 788 (the default value)
// VALIDATE TWO DEPENDENT QUERY PARAMETERS ////////////////////////////////////////////////////////////
Instant fromDate = ctx.queryParamAsClass(“from”, Instant.class).get();
Instant toDate = ctx.queryParamAsClass(“to”, Instant.class)
.check(it -> it.isAfter(fromDate), “‘to’ has to be after ‘from’”)
.get();
// VALIDATE A JSON BODY ///////////////////////////////////////////////////////////////////////////////
MyObject myObject = ctx.bodyValidator(MyObject.class)
.check(obj -> obj.myObjectProperty == someValue, “THINGS_MUST_BE_EQUAL”)
.get();
// VALIDATE WITH CUSTOM VALIDATIONERROR ///////////////////////////////////////////////////////////////
ctx.queryParamAsClass(“param”, Integer.class)
.check({ it > 5 }, new ValidationError(“OVER_LIMIT”, Map.of(“limit”, 5)))
.get();
// GET ?param=10 would yield HTTP 400 - {“param”:[{“message”:”OVER_LIMIT”,”args”:{“limit”:5},”value”:10}]}
收集多个错误
Validator
.check(n -> !n.contains(“-“), “ILLEGAL_CHARACTER”)
// Empty map if no errors, otherwise a map with the key “age” and failed check messages in the list.
Map<String, List
// Merges all errors from all validators in the list. Empty map if no errors exist.
Map<String, List
最后编辑:Jeebiz 更新时间:2025-05-04 00:55