Spring MVC
DispatcherServlet, @RestController, request mapping, validation, exception handling
Spring MVC
A Spring MVC a Servlet API-ra épülő, szinkron, request-response alapú webes keretrendszer, amely a DispatcherServlet köré szerveződik.
1. Definíció
A Spring MVC (Model-View-Controller) a Spring Framework webes modulja, amely a jakarta.servlet.http.HttpServlet API-ra épül. A központi elem a DispatcherServlet, amely egy Front Controller mintát valósít meg: minden HTTP kérés egyetlen belépési ponton érkezik, majd a keretrendszer az URL, HTTP metódus és egyéb feltételek alapján a megfelelő handler metódushoz irányítja.
A Spring MVC alapértelmezetten szinkron és blokkoló: minden kérés egy servlet thread-et foglal el a feldolgozás teljes idejére. A Spring Boot automatikusan konfigurálja a beágyazott Tomcat/Jetty/Undertow servert és a DispatcherServlet-et.
HTTP Request → DispatcherServlet → HandlerMapping → HandlerAdapter
→ Controller → Service → Repository → Response
2. Alapfogalmak
DispatcherServlet architektúra
A DispatcherServlet a következő komponenseket koordinálja:
| Komponens | Szerep |
|---|---|
| HandlerMapping | URL → Controller metódus leképezés |
| HandlerAdapter | Controller metódus meghívása |
| HttpMessageConverter | Request/response body konverzió (JSON, XML) |
| ViewResolver | View név → template feloldás (Thymeleaf, JSP) |
| HandlerExceptionResolver | Kivételek kezelése |
| LocaleResolver | Nyelv feloldás |
| MultipartResolver | Fájlfeltöltés kezelés |
Controller annotációk
@Controller // View-t visszaadó controller
@RestController // = @Controller + @ResponseBody (JSON/XML)
@RequestMapping // Osztály vagy metódus szintű URL mapping
@GetMapping // HTTP GET shortcut
@PostMapping // HTTP POST shortcut
@PutMapping // HTTP PUT shortcut
@DeleteMapping // HTTP DELETE shortcut
@PatchMapping // HTTP PATCH shortcut
Paraméter binding
@PathVariable // URL path változó: /users/{id}
@RequestParam // Query paraméter: ?name=John
@RequestBody // HTTP body → Java objektum (deserialization)
@RequestHeader // HTTP header értékek
@CookieValue // Cookie értékek
@ModelAttribute // Form data → Java objektum
@RequestPart // Multipart fájl
Request feldolgozás folyamata
- HTTP kérés érkezik a DispatcherServlet-hez
- HandlerMapping megkeresi a megfelelő controller metódust
- HandlerInterceptor.preHandle() lefut
- HandlerAdapter meghívja a controller metódust
- HttpMessageConverter deserializálja a request body-t
- Controller végrehajtja az üzleti logikát
- HttpMessageConverter serializálja a response body-t
- HandlerInterceptor.postHandle() lefut
- Response visszaküldése a kliensnek
3. Gyakorlati használat
REST API controller
@RestController
@RequestMapping("/api/v1/users")
@Validated
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<UserDto> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return userService.findAll(PageRequest.of(page, size));
}
@GetMapping("/{id}")
public UserDto findById(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserDto create(@Valid @RequestBody CreateUserRequest request) {
return userService.create(request);
}
@PutMapping("/{id}")
public UserDto update(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
return userService.update(id, request);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
userService.delete(id);
}
}
Bean Validation integráció
public record CreateUserRequest(
@NotBlank(message = "Name is required")
@Size(min = 2, max = 100)
String name,
@Email(message = "Invalid email format")
@NotBlank
String email,
@Min(18) @Max(150)
int age
) {}
A @Valid a controller paraméteren aktiválja a validációt. Ha a validáció sikertelen, MethodArgumentNotValidException keletkezik.
Globális exception kezelés
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(EntityNotFoundException ex) {
return new ErrorResponse("NOT_FOUND", ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidation(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.toList();
return new ErrorResponse("VALIDATION_FAILED", errors.toString());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGeneral(Exception ex) {
return new ErrorResponse("INTERNAL_ERROR", "Something went wrong");
}
}
public record ErrorResponse(String code, String message) {}
4. Kód példák
Content negotiation
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping(produces = {MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE})
public List<Product> findAll() {
return productService.findAll();
}
}
Az Accept header alapján a Spring kiválasztja a megfelelő HttpMessageConverter-t.
Egyedi HttpMessageConverter
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(
new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
));
}
}
Async controller (DeferredResult)
@GetMapping("/async/{id}")
public DeferredResult<UserDto> findByIdAsync(@PathVariable Long id) {
DeferredResult<UserDto> result = new DeferredResult<>(5000L);
executorService.submit(() -> {
try {
UserDto user = userService.findById(id);
result.setResult(user);
} catch (Exception e) {
result.setErrorResult(e);
}
});
return result;
}
A DeferredResult felszabadítja a servlet thread-et, míg a háttérben fut a feldolgozás.
ResponseEntity finomhangolás
@GetMapping("/{id}")
public ResponseEntity<UserDto> findById(@PathVariable Long id) {
return userService.findOptional(id)
.map(user -> ResponseEntity.ok()
.header("X-Custom", "value")
.cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
.body(user))
.orElse(ResponseEntity.notFound().build());
}
5. Trade-offok
Spring MVC vs WebFlux
| Szempont | Spring MVC | WebFlux |
|---|---|---|
| I/O modell | Blokkoló (thread-per-request) | Non-blocking (event loop) |
| Thread-ek | ~200 Tomcat thread (alapért.) | Kevés event loop thread |
| Skálázás | Vertikális (több thread) | Horizontális (kevesebb erőforrás) |
| Debugolás | Egyszerű stack trace | Reaktív lánc nehezebb |
| Ökoszisztéma | Teljes (JPA, JDBC, stb.) | Korlátozott (R2DBC, WebClient) |
| Tanulási görbe | Alacsony | Magas |
Mikor Spring MVC
- CRUD alkalmazások hagyományos adatbázissal (JPA/JDBC)
- Egyszerű REST API-k kevés párhuzamos kéréssel
- Csapat nem ismeri a reaktív programozást
- Szinkron third-party könyvtárak használata
Mikor NEM Spring MVC
- 10 000+ egyidejű kapcsolat (WebSocket, SSE)
- Microservice gateway nagy áteresztőképességgel
- Streaming feldolgozás (Kafka, event sourcing)
6. Gyakori hibák
❌ Üzleti logika a controllerben
// ROSSZ: controller tartalmazza az üzleti logikát
@PostMapping
public UserDto create(@RequestBody CreateUserRequest req) {
if (userRepo.existsByEmail(req.email())) {
throw new DuplicateException("Email exists");
}
User user = new User(req.name(), req.email());
user = userRepo.save(user);
emailService.sendWelcome(user);
return UserDto.from(user);
}
// JÓ: service rétegbe delegálás
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserDto create(@Valid @RequestBody CreateUserRequest req) {
return userService.create(req);
}
❌ Hiányzó @Valid annotáció
// ROSSZ: validáció nem fut le
@PostMapping
public UserDto create(@RequestBody CreateUserRequest req) { ... }
// JÓ: @Valid aktiválja a Bean Validation-t
@PostMapping
public UserDto create(@Valid @RequestBody CreateUserRequest req) { ... }
❌ Exception handler nélküli API
Validációs és üzleti hibák stack trace-ként szivárognak ki a klienshez. Mindig használj @RestControllerAdvice-t!
❌ @ResponseStatus elfelejtése
A @PostMapping alapértelmezetten 200 OK-t ad vissza. Létrehozásnál @ResponseStatus(HttpStatus.CREATED) a helyes.
❌ PathVariable típus-mismatch kezeletlen
// /api/users/abc → NumberFormatException → 500
// Kezeld @ExceptionHandler-rel vagy használj regex-et:
@GetMapping("/{id:\\d+}")
public UserDto findById(@PathVariable Long id) { ... }
7. Mélyebb összefüggések
HandlerInterceptor vs Servlet Filter
| Szempont | HandlerInterceptor | Servlet Filter |
|---|---|---|
| Regisztráció | WebMvcConfigurer |
@Component / FilterRegistrationBean |
| Spring context | Elérhető (Spring bean) | Csak ha Spring-managed |
| Végrehajtási pont | Handler (controller) szinten | Servlet szinten (előbb fut) |
| preHandle | Igen | doFilter() előtt |
| postHandle | Igen (response előtt) | Nincs külön |
| afterCompletion | Igen (response után) | Nincs külön |
HttpMessageConverter belső működés
A Spring az Accept és Content-Type header alapján választja ki a converter-t:
- Request:
Content-Type: application/json→MappingJackson2HttpMessageConverter.read() - Response:
Accept: application/json→MappingJackson2HttpMessageConverter.write() - Ha Jackson a classpath-on van, automatikus regisztráció
- XML-hez
jackson-dataformat-xmldependency kell
Controller tanácsok (Advice)
@ControllerAdvice(basePackages = "com.example.api")
public class ApiAdvice {
// Csak az api package controllerjeire vonatkozik
@ModelAttribute
public void addCommonAttributes(Model model) {
model.addAttribute("appVersion", "2.0");
}
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(LocalDate.class,
new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(LocalDate.parse(text));
}
});
}
}
Argument Resolver egyedi típusokhoz
public class CurrentUserArgumentResolver
implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter param) {
return param.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter param,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
return SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
}
}
8. Interjúkérdések
Mi a DispatcherServlet szerepe? Front Controller minta: egyetlen belépési pont, HandlerMapping-gel keresi meg a controller metódust, HandlerAdapter-rel hívja meg.
Mi a különbség a @Controller és @RestController között?
@RestController=@Controller+@ResponseBody. A@ControllerView nevet ad vissza (Thymeleaf), a@RestControllerJSON/XML body-t.Hogyan működik a validáció Spring MVC-ben?
@Valid/@Validatedaktiválja a Bean Validation-t (Hibernate Validator). Hiba eseténMethodArgumentNotValidException. A@RestControllerAdvicekezeli globálisan.Mi a @RestControllerAdvice? Globális exception handler + model attribute + init binder. A
@ControllerAdvice+@ResponseBodykombinációja.Hogyan testreszabod a HTTP status kódot?
@ResponseStatusannotáció,ResponseEntity<T>visszatérési típus, vagyHttpServletResponse.setStatus().Mi a különbség a HandlerInterceptor és a Servlet Filter között? Filter: servlet szinten, Spring context nélkül is fut. Interceptor: Spring-managed, preHandle/postHandle/afterCompletion lifecycle.
Hogyan kezeled a fájlfeltöltést?
@RequestPart MultipartFile file.MultipartResolverbean szükséges (Spring Boot automatikusan konfigurálja).spring.servlet.multipart.max-file-sizelimitálja.
9. Szószedet
| Fogalom | Jelentés |
|---|---|
| DispatcherServlet | Front Controller, minden HTTP kérés belépési pontja |
| HandlerMapping | URL → controller metódus leképezés |
| HandlerAdapter | Controller metódus meghívásáért felelős |
| HttpMessageConverter | Java objektum ↔ HTTP body konverzió |
| @RestController | @Controller + @ResponseBody, JSON/XML API-khoz |
| @RequestMapping | URL és HTTP metódus leképezés annotáció |
| @Valid | Bean Validation aktiválás controller paraméteren |
| @RestControllerAdvice | Globális exception handler + model attribute |
| ResponseEntity | HTTP status + headers + body finomhangolás |
| HandlerInterceptor | Spring szintű pre/post processing |
| Content negotiation | Accept/Content-Type alapú formátum választás |
| DeferredResult | Aszinkron response (servlet thread felszabadítás) |
10. Gyorsreferencia
REQUEST LIFECYCLE:
Client → Filter → DispatcherServlet → Interceptor.preHandle()
→ HandlerAdapter → Controller → Service → Repository
→ Interceptor.postHandle() → Response
ANNOTÁCIÓK:
@RestController JSON/XML API controller
@RequestMapping("/api") Alap URL prefix
@GetMapping("/{id}") GET + path variable
@PostMapping POST endpoint
@Valid @RequestBody Body validáció
@PathVariable URL szegmens
@RequestParam Query paraméter
@ResponseStatus(201) HTTP status felülírás
EXCEPTION HANDLING:
@RestControllerAdvice Globális handler
@ExceptionHandler(X) Adott kivétel kezelése
ResponseEntity<Error> Egyedi response
KONFIGURÁCIÓ:
WebMvcConfigurer Interceptor, converter, CORS
server.port=8080 Szerver port
spring.jackson.* JSON serialization beállítások
TESZTELÉS:
@WebMvcTest Controller réteg teszt (slice)
MockMvc HTTP kérés szimuláció
@MockBean Service mock
🎮 Játékok
10 kérdés