~~NOCACHE~~
~~REVEAL theme=simple&disableLayout=0&transition=none&controls=1&show_progress_bar=1&build_all_lists=0&show_image_borders=0&horizontal_slide_level=2&enlarge_vertical_slide_headers=0&show_slide_details=1&open_in_new_window=1&size=1024x768~~
======= Javalin - Lekki Framework Webowy dla Javy ======
===== Co to jest Javalin? =====
* **Javalin** to lekki framework do tworzenia aplikacji webowych i REST API w Javie
* Oparty na serwerze **Jetty** (serwer HTTP i servletów)
* Przeznaczony dla programistów, którzy chcą szybko tworzyć aplikacje bez dużej ilości boilerplate'u
* Idealne do:
* REST API
* Mikroserwisów
* Prototypów
* Małych i średnich aplikacji webowych
* "Czysty" kod Java, bez adnotacji i refleksji
* Następca Spark Java, ale z nowoczesnym designem i lepszą wydajnością
* Obecna wersja: **7.x** (znaczące zmiany od wersji 6.x)
* W Javalin 7 trasy definiujemy przez ''config.routes'' podczas ''Javalin.create(...)''
* https://javalin.io/
===== Dodanie Javalin do projektu Maven =====
Plik ''pom.xml'':
io.javalin
javalin
7.2.2
io.javalin
javalin-bundle
7.2.2
===== Zawartość i zależności javalin-bundle =====
**javalin-bundle** to "all-in-one" pakiet zawierający:
* ''javalin-core'' - główny framework
* ''jetty-server'' - serwer HTTP
* ''jetty-servlet'' - obsługa servletów
* ''jackson'' - serializacja JSON
* ''slf4j-api'' - API loggowania
**Zaleta:** Nie musisz ręcznie dodawać zależności, wszystko jest już skonfigurowane i kompatybilne
[[https://javalin.io/download#javalin-modules|Inne moduły]]: ''javalin-rendering-*'', ''javalin-ssl'', ''javalin-micrometer''
===== Uruchomienie serwera =====
import io.javalin.Javalin;
public class HelloWorldApp {
public static void main(String[] args) {
Javalin app = Javalin.create(config -> {
config.routes.get("/", ctx -> {
ctx.result("Hello World!");
});
});
// Uruchom na porcie 8080
app.start(8080);
}
}
===== Uchwyty =====
* Podstawowe typy:
* akcje przed: ''before'', ''beforeMatched''
* punktów końcowych ''endpoints'' : ''get'', ''post'', ''put'', ''delete'', ''patch''
* akcje po: ''after'', ''afterMatched''
* Ścieżka:
* ''/'', ''/hello-world'', ''/hello/{name}'', ''/user/*''
* Handler: ''ctx -> { ... }'' - lambda z obiektem ''Context''
* Pobieranie danych z żądania i wysyłanie odpowiedzi przez ''Context''
* Uchwyty do tych samych scieżek wykonywane w kolejności definicji
config.routes.get("/", ctx -> {
ctx.result("Hello World!");
});
===== Routing: Metody HTTP =====
Javalin obsługuje wszystkie standardowe metody HTTP:
* ''GET'' - pobranie danych
* ''POST'' - utworzenie zasobu
* ''PUT'' - aktualizacja zasobu
* ''DELETE'' - usunięcie zasobu
* ''PATCH'' - częściowa aktualizacja
===== Składnia tras =====
Javalin app = Javalin.create(config -> {
// GET - pobierz dane
config.routes.get("/api/users", ctx -> {
ctx.result("List of users");
});
// POST - utwórz zasób
config.routes.post("/api/users", ctx -> {
ctx.status(201); // Created
ctx.result("User created");
});
// PUT - aktualizuj
config.routes.put("/api/users/{id}", ctx -> {
String id = ctx.pathParam("id");
ctx.result("Updated user " + id);
});
// DELETE - usuń
config.routes.delete("/api/users/{id}", ctx -> {
String id = ctx.pathParam("id");
ctx.result("Deleted user " + id);
});
}).start(8080);
===== Priorytety tras =====
* Trasy są przetwarzane w kolejności zdefiniowania
* Bardziej konkretne trasy powinny być zdefiniowane przed ogólnymi
* Wildcard ''*'' dopasowuje dowolny tekst
Javalin app = Javalin.create(config -> {
config.routes.get("/admin/users", ctx -> ctx.result("Admin users")); // Konkretna
config.routes.get("/admin/*", ctx -> ctx.result("Admin section")); // Ogólna
config.routes.get("/*", ctx -> ctx.result("Everything else")); // Najogólniejsza
});
===== Obiekt Context? =====
* ''Context'' to centralny obiekt w Javalin reprezentujący:
* **Żądanie HTTP** od klienta (request)
* **Odpowiedź HTTP** dla klienta (response)
* Każdy handler otrzymuje obiekt ''Context'' jako argument i poprzez niego:
* Pobiera dane z żądania
* Wysyła dane w odpowiedzi
* Handler nic nie zwraca, wszystko idzie przez ''Context''
Javalin app = Javalin.create(config -> {
config.routes.get("/example", ctx -> {
String query = ctx.queryParam("q");
String path = ctx.path();
ctx.status(200);
ctx.result("Response, query=" + query + ", path=" + path);
});
});
===== Kluczowe metody Context =====
* ''path()'' - ścieżka żądania
* ''method()'' - metoda HTTP
* ''pathParam(key)'' - parametr z ścieżki (np. ''/users/{id}'')
* ''queryParam(key)'' - parametr z query string (''/search?q=java'')
* ''body()'' - ciało żądania
* ''result(String)'' - wysłanie odpowiedzi
* ''json(Object)'' - wysłanie JSON
* ''status(int)'' - kod statusu HTTP
* Zobacz: [[https://javalin.io/documentation#context|Dokumentacja Context]]
===== Parametry w ścieżce =====
Javalin app = Javalin.create(config -> {
// Definicja: /users/{id}/posts/{postId}
config.routes.get("/users/{id}/posts/{postId}", ctx -> {
String userId = ctx.pathParam("id");
// Z walidacją typu
int postId = ctx.pathParamAsClass("postId", Integer.class).get();
ctx.result("User: " + userId + ", Post: " + postId);
});
}).start(8080);
// GET /users/123/posts/456 -> User: 123, Post: 456
// GET /users/123/posts/abc -> "Serwer error" (NumberFormatException)
===== Query Parameters =====
Parametry po znaku ''?'' w URL:
Javalin app = Javalin.create(config -> {
config.routes.get("/search", ctx -> {
String query = ctx.queryParam("q");
int limitInt = ctx.queryParamAsClass("limit", Integer.class).getOrDefault(42);
ctx.result("Szukam: " + query + " (limit: " + limitInt + ")");
});
});
// GET /search?q=java&limit=5
// Odpowiedź: Szukam: java (limit: 5)
Wiele wartości tego samego parametru:
// GET /filter?tag=java&tag=web&tag=api
List tags = ctx.queryParams("tag");
// tags = ["java", "web", "api"]
===== Body - Ciało żądania =====
Dane przesłane w ciele żądania (zazwyczaj POST/PUT):
Javalin app = Javalin.create(config -> {
config.routes.post("/echo", ctx -> {
String body = ctx.body();
ctx.result("Echo: " + body);
});
});
// CMD: curl -X POST http://localhost:8080/echo -d "hello"
// POST /echo
// Body: hello
// Odpowiedź: Echo: hello
===== Nagłówki żądania =====
Javalin app = Javalin.create(config -> {
config.routes.get("/headers", ctx -> {
String userAgent = ctx.header("User-Agent");
String contentType = ctx.header("Content-Type");
ctx.result("User-Agent: " + userAgent);
Map headers = ctx.headerMap();
});
});
===== Kody statusu HTTP ======
Javalin app = Javalin.create(config -> {
config.routes.post("/create", ctx -> {
// Utworzenie zasobu
ctx.status(201); // Created
ctx.result("Zasób utworzony");
});
config.routes.get("/notfound", ctx -> {
ctx.status(404); // Not Found
ctx.result("Nie znaleziono");
});
});
// Powszechne kody:
// 200 - OK (GET, POST, PUT zwrócił dane)
// 201 - Created (POST/PUT utworzył zasób)
// 204 - No Content (DELETE, operacja OK bez zawartości)
// 400 - Bad Request (błąd w żądaniu klienta)
// 401 - Unauthorized (brak autoryzacji)
// 404 - Not Found (nie znaleziono)
// 500 - Internal Server Error (błąd serwera)
===== Wysłanie JSON =====
// Record - idealny do przesyłania JSON
record User(int id, String name, String email) {}
Javalin app = Javalin.create(config -> {
config.routes.get("/user", ctx -> {
User user = new User(1, "Alice", "alice@example.com");
ctx.json(user); // Automatyczna konwersja do JSON
});
});
// Odpowiedź: {"id":1,"name":"Alice","email":"alice@example.com"}
===== Rekordy w Java 16+ =====
Rekordy to specjalne klasy wprowadzone w Javie 16, idealne do przechowywania danych:
* Automatycznie generują konstruktor, gettery, ''equals()'', ''hashCode()'' i ''toString()''
* niemutowalne (immutable) - wartości nie mogą być zmieniane po utworzeniu
record Product(int id, String name, double price) {}
Product p = new Product(1, "Laptop", 999.99);
System.out.println(p.name());
===== Serialiacja i deserializacja JSON =====
Rekordy to idealne do przesyłania danych w JSON:
record User(int id, String name, String email) {}
record Product(int id, String title, double price) {}
Javalin app = Javalin.create(config -> {
// Wysłanie
config.routes.get("/user", ctx -> {
User user = new User(1, "Alice", "alice@example.com");
ctx.json(user); // Konwersja Record -> JSON
});
// Otrzymanie
config.routes.post("/user", ctx -> {
User user = ctx.bodyAsClass(User.class); // Konwersja JSON -> Record
System.out.println("User: " + user.name());
ctx.json(user);
});
});
===== Przechwytywanie wyjątków =====
config.routes.exception(ValidationException.class, (e, ctx) -> {
ctx.status(400);
ctx.result("Liczba musi byc liczba calkowita\n" + e.getMessage());
});
config.routes.exception(RuntimeException.class, (e, ctx) -> {
ctx.status(500);
ctx.result("Blad serwera: " + e.getMessage());
});
config.routes.get("/boom", ctx -> {
throw new RuntimeException("DEMO: kontrolowany wyjątek");
});
===== Error Handlers =====
Obsługa błędów HTTP na poziomie aplikacji:
// Wszystkie 404
config.routes.error(404, ctx -> {
ctx.result("Strona nie znaleziona");
});
// Wszystkie 500
config.routes.error(500, ctx -> {
ctx.result("Błąd serwera");
});
===== Serwowanie HTML/CSS/JavaScript ====
Javalin app = Javalin.create(config -> {
config.staticFiles.add("/public"); // Folder z plikami
});
app.start(8080);
// Pliki z src/main/resources/public/:
// - /index.html → http://localhost:8080/index.html
// - /css/style.css → http://localhost:8080/css/style.css
// - /js/app.js → http://localhost:8080/js/app.js
===== Struktura projektu =====
src/main/resources/
└── public/
├── index.html
├── css/
│ └── style.css
└── js/
└── app.js
===== Przykład index.html =====
Moja aplikacja
Witaj w Javalin
Ćwiczenie: [[https://javalin.io/tutorials/html-forms-example|Przykład formularza HTML z Javalin]]
===== Before Handler =====
Wykonywane przed obsługą żądania
// Globalny middleware
Javalin app = Javalin.create(config -> {
config.routes.before(ctx -> {
System.out.println(">>> " + ctx.method() + " " + ctx.path());
// Logging, walidacja, autoryzacja
});
// Przed dopasowaniem trasy
config.routes.beforeMatched("/api/*", ctx -> {
String token = ctx.header("Authorization");
if (token == null) {
ctx.status(401).result("Brak tokenu");
}
});
});
===== After Handler ======
Wykonywane po obsłudze żądania:
Javalin app = Javalin.create(config -> {
config.routes.after(ctx -> {
// run after all requests
});
config.routes.after("/path/*", ctx -> {
// runs after request to /path/*
});
config.routes.afterMatched(
// runs after all matched requests (including static files)
);
});
===== Resource-Oriented Design ====
REST API powinno być zorientowane na **zasoby** (rzeczy), nie na **akcje** (czasowniki):
* Złe: ''/api/getUser'', ''/api/createProduct''
* Dobre: ''/api/users'', ''/api/products''
===== Prawidłowe użycie metod HTTP =====
^ Metoda ^ Akcja ^ Przykład ^
| GET | Pobranie | GET /api/users (pobranie listy) |
| POST | Utworzenie | POST /api/users (utwórz nowego) |
| PUT | Aktualizacja | PUT /api/users/1 (aktualizuj) |
| DELETE | Usunięcie | DELETE /api/users/1 (usuń) |
===== apiBuilder =====
config.routes.apiBuilder(() -> {
path("/users", () -> {
get(UserController::getAllUsers);
post(UserController::createUser);
path("/{id}", () -> {
get(UserController::getUser);
});
ws("/events", UserController::webSocketEvents);
});
});
===== Role =====
Interfejs ''RouteRole'' do definiowania ról użytkowników:
enum Role implements RouteRole { ANYONE, USER_READ, USER_WRITE }
Przypisanie ról do endpointów:
config.routes.get("/public", ctx -> ctx.result("Hello public"), Role.ANYONE);
config.routes.get("/private", ctx -> ctx.result("Hello private"), Role.USER_READ);
Pobieranie ról z kontekstu:
config.routes.before("/private/*", ctx -> {
Set roles = ctx.roles();
if (!roles.contains(Role.USER_READ)) {
ctx.status(403).result("Forbidden");
}
});
====== Materiały ======
* Dokumentacja Javalin 7: https://javalin.io/documentation
* Tutorial: https://javalin.io/tutorials
* Przykłady na GitHub: https://github.com/tipsy/javalin-examples