Saltar al contenido principal

Capítulo 2: Anotaciones Web y REST

1. Anotaciones de Controlador​

@RestController vs @Controller​

// Devuelve VISTAS (HTML)
@Controller
public class PageController {
@GetMapping("/home")
public String home() {
return "home"; // Busca una plantilla home.html
}
}

// Devuelve DATOS (JSON/XML)
@RestController
public class ApiController {
@GetMapping("/api/data")
public Map<String, String> getData() {
return Map.of("message", "Hello"); // Serializado automáticamente a JSON
}
}

@ResponseBody​

Fuerza el retorno en JSON/XML (usado con @Controller).

@Controller
public class MixedController {

@GetMapping("/page")
public String page() {
return "page"; // Devuelve una vista
}

@GetMapping("/api/info")
@ResponseBody
public Map<String, String> info() {
return Map.of("version", "1.0"); // Devuelve JSON
}
}

2. Anotaciones de Mapeo HTTP​

@RequestMapping​

Anotación básica para mapear una URL a un método. Puede especificar el método HTTP.

@RestController
@RequestMapping("/api/v1/products") // Prefijo para todos los métodos
public class ProductController {

@RequestMapping(method = RequestMethod.GET)
public List<Product> getAll() { ... }

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Product getById(@PathVariable Long id) { ... }
}

@GetMapping​

Atajo para @RequestMapping(method = GET).

@GetMapping
public List<Product> getAll() {
return productService.findAll();
}

@GetMapping("/{id}")
public Product getById(@PathVariable Long id) {
return productService.findById(id);
}

@GetMapping("/search")
public List<Product> search(@RequestParam String name) {
return productService.findByName(name);
}

@PostMapping​

Atajo para @RequestMapping(method = POST).

@PostMapping
public ResponseEntity<Product> create(@Valid @RequestBody ProductDto dto) {
Product created = productService.create(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}

@PutMapping​

Atajo para @RequestMapping(method = PUT). Reemplazo completo de un recurso.

@PutMapping("/{id}")
public Product update(@PathVariable Long id, @Valid @RequestBody ProductDto dto) {
return productService.update(id, dto);
}

@PatchMapping​

Atajo para @RequestMapping(method = PATCH). Actualización parcial de un recurso.

@PatchMapping("/{id}")
public Product partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return productService.partialUpdate(id, updates);
}

@DeleteMapping​

Atajo para @RequestMapping(method = DELETE).

@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
productService.delete(id);
return ResponseEntity.noContent().build();
}

Resumen de los mapeos​

AnotaciónMétodo HTTPUso típico
@GetMappingGETLeer datos
@PostMappingPOSTCrear un recurso
@PutMappingPUTReemplazar un recurso
@PatchMappingPATCHModificar parcialmente
@DeleteMappingDELETEEliminar un recurso

3. Anotaciones de Parámetros​

@PathVariable​

Extrae un valor desde la URL.

// GET /api/users/42
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}

// GET /api/users/42/orders/7
@GetMapping("/api/users/{userId}/orders/{orderId}")
public Order getOrder(@PathVariable Long userId, @PathVariable Long orderId) {
return orderService.findByUserAndId(userId, orderId);
}

// Nombre diferente al parámetro de la URL
@GetMapping("/api/categories/{category-name}")
public Category getCategory(@PathVariable("category-name") String categoryName) {
return categoryService.findByName(categoryName);
}

@RequestParam​

Extrae un parámetro de la query string.

// GET /api/products?category=electronics
@GetMapping("/api/products")
public List<Product> getByCategory(@RequestParam String category) {
return productService.findByCategory(category);
}

// Parámetro opcional con valor por defecto
// GET /api/products?page=0&size=20&sort=name
@GetMapping("/api/products")
public Page<Product> getAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "name") String sort) {
return productService.findAll(PageRequest.of(page, size, Sort.by(sort)));
}

// Parámetro opcional sin valor por defecto
@GetMapping("/api/search")
public List<Product> search(
@RequestParam(required = false) String name,
@RequestParam(required = false) BigDecimal minPrice) {
return productService.search(name, minPrice);
}

@RequestBody​

Deserializa el cuerpo de la solicitud (JSON → Objeto Java).

@PostMapping("/api/users")
public User createUser(@RequestBody UserDto userDto) {
return userService.create(userDto);
}
// Cuerpo de la solicitud (JSON)
{
"name": "Jean Dupont",
"email": "jean@example.com",
"password": "secret123"
}

@RequestHeader​

Extrae un valor de una cabecera HTTP.

@GetMapping("/api/profile")
public User getProfile(@RequestHeader("Authorization") String authHeader) {
String token = authHeader.replace("Bearer ", "");
return userService.findByToken(token);
}

@GetMapping("/api/info")
public Map<String, String> info(
@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value = "Accept-Language", defaultValue = "fr") String lang) {
return Map.of("userAgent", userAgent, "language", lang);
}

@CookieValue​

Extrae un valor de una cookie.

@GetMapping("/api/preferences")
public Preferences getPrefs(@CookieValue(value = "theme", defaultValue = "light") String theme) {
return new Preferences(theme);
}

@ModelAttribute​

Vincula los parámetros de formulario a un objeto (usado principalmente con @Controller).

@PostMapping("/register")
public String register(@ModelAttribute RegistrationForm form) {
userService.register(form);
return "redirect:/login";
}

4. Anotaciones de Respuesta​

@ResponseStatus​

Define el código HTTP de la respuesta.

@PostMapping
@ResponseStatus(HttpStatus.CREATED) // 201
public Product create(@RequestBody ProductDto dto) {
return productService.create(dto);
}

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT) // 204
public void delete(@PathVariable Long id) {
productService.delete(id);
}

Uso con excepciones:​

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}

5. Anotaciones de Manejo de Errores​

@ExceptionHandler​

Gestiona las excepciones a nivel de controlador.

@RestController
public class UserController {

@GetMapping("/api/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id); // Puede lanzar ResourceNotFoundException
}

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, String> handleNotFound(ResourceNotFoundException ex) {
return Map.of("error", ex.getMessage());
}
}

@ControllerAdvice / @RestControllerAdvice​

Manejo global de excepciones para todos los controladores.

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException 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_ERROR", errors.toString());
}

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGeneral(Exception ex) {
return new ErrorResponse("INTERNAL_ERROR", "Ha ocurrido un error");
}
}

6. Anotaciones CORS y Otras​

@CrossOrigin​

Configura el Cross-Origin Resource Sharing (CORS).

@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class ApiController {
// Todos los métodos permiten solicitudes desde localhost:3000
}

@GetMapping("/api/public")
@CrossOrigin(origins = "*", maxAge = 3600)
public String publicEndpoint() {
return "accesible desde cualquier lugar";
}

7. Anotaciones de Validación​

@Valid / @Validated​

Activa la validación de los datos recibidos.

@PostMapping("/api/users")
public User create(@Valid @RequestBody CreateUserDto dto) {
return userService.create(dto);
}
public class CreateUserDto {

@NotNull(message = "El nombre es obligatorio")
@Size(min = 2, max = 50, message = "El nombre debe tener entre 2 y 50 caracteres")
private String name;

@NotBlank(message = "El correo electrónico es obligatorio")
@Email(message = "Formato de correo electrónico inválido")
private String email;

@NotNull
@Size(min = 8, message = "La contraseña debe tener al menos 8 caracteres")
private String password;

@Min(value = 0, message = "La edad debe ser positiva")
@Max(value = 150, message = "La edad debe ser menor a 150")
private Integer age;

@Pattern(regexp = "^\\+?[0-9]{10,15}$", message = "Número de teléfono inválido")
private String phone;
}

Anotaciones de validación disponibles​

AnotaciónDescripción
@NotNullNo puede ser null
@NotEmptyNo null y no vacío (String, Collection)
@NotBlankNo null, no vacío, no solo espacios
@Size(min, max)Tamaño entre min y max
@Min(value)Valor mínimo (números)
@Max(value)Valor máximo (números)
@EmailFormato de correo electrónico válido
@Pattern(regexp)Coincide con la expresión regular
@PositiveNúmero estrictamente positivo
@PositiveOrZeroNúmero positivo o cero
@NegativeNúmero estrictamente negativo
@PastFecha en el pasado
@FutureFecha en el futuro
@DecimalMinValor decimal mínimo
@DecimalMaxValor decimal máximo

Resumen Rápido​

AnotaciónRol
@RestControllerControlador REST (JSON)
@RequestMappingMapeo de URL base
@GetMappingGET
@PostMappingPOST
@PutMappingPUT
@PatchMappingPATCH
@DeleteMappingDELETE
@PathVariableParámetro en la URL
@RequestParamParámetro query string
@RequestBodyCuerpo JSON → Objeto
@RequestHeaderCabecera HTTP
@ResponseStatusCódigo de respuesta HTTP
@ExceptionHandlerManejo de excepción local
@RestControllerAdviceManejo de excepción global
@CrossOriginConfiguración CORS
@ValidActiva la validación

➡️ Capítulo 3: Anotaciones Data y JPA


⬅️ Volver al Módulo 17