En aplicaciones Spring Boot, es frecuente manejar solicitudes con información de encabezado común. Para evitar código repetitivo al extraer valores del encabezado y asignarlos a campos específicos de la solicitud, podemos emplear anotaciones personalizadas en combinación con AOP de Spring.
Imaginemos una situación donde existe una clase base de solicitud con un encabezado común que contiene campos como ID de cliente, nombre de usuario y contraseña. El objetivo es poblar automáticamente campos determinados en el objeto de solicitud según los valores del encabezado, usando una anotación para indicar qué campo establecer.
Primero, definimos los modelos de datos:
package com.ejemplo.modelo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class SolicitudEnvio extends SolicitudBase {
private String nombreUsuario;
private String contrasena;
private String idCliente;
}
package com.ejemplo.modelo;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class SolicitudBase {
private CabeceraComun cabeceraComun;
public SolicitudBase() {
this.cabeceraComun = new CabeceraComun();
}
}
package com.ejemplo.modelo;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CabeceraComun {
private String idCliente = "999999";
private String nombreUsuario = "Juan";
private String contrasena = "123456";
}
A continuación, creamos la anotación personalizada que especificará qué propiedad del encabezado común debe asignarse:
package com.ejemplo.anotacion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AgregarParametro {
String nombreParametro() default "nombreUsuario";
}
Luego, desarrollamos el aspecto que interceptará los métodos anotados y realizará la asignación mediante reflexión:
package com.ejemplo.aspecto;
import com.ejemplo.modelo.CabeceraComun;
import com.ejemplo.modelo.SolicitudEnvio;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
@Component
@Aspect
public class ParametroAspecto {
@Pointcut(value = "@annotation(com.ejemplo.anotacion.AgregarParametro)")
public void puntoDeCorte() {
}
@Around("puntoDeCorte() && @annotation(agregarParametro)")
public Object alrededorMetodo(ProceedingJoinPoint pjp, AgregarParametro agregarParametro) throws Throwable {
String nombreParam = agregarParametro.nombreParametro();
Object argumento = pjp.getArgs()[0];
if (argumento instanceof SolicitudEnvio) {
SolicitudEnvio solicitud = (SolicitudEnvio) argumento;
CabeceraComun cabecera = solicitud.getCabeceraComun();
PropertyDescriptor descriptorCabecera = new PropertyDescriptor(nombreParam, CabeceraComun.class);
Method getter = descriptorCabecera.getReadMethod();
Object valorParam = getter.invoke(cabecera);
PropertyDescriptor descriptorSolicitud = new PropertyDescriptor(nombreParam, SolicitudEnvio.class);
Method setter = descriptorSolicitud.getWriteMethod();
setter.invoke(solicitud, valorParam);
}
return pjp.proceed();
}
}
Para probar, implementamos un controlador simple con métodos que utilizan la anotación:
package com.ejemplo.controlador;
import com.ejemplo.anotacion.AgregarParametro;
import com.ejemplo.modelo.SolicitudEnvio;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ControladorPrueba {
@ResponseBody
@RequestMapping("/probarAgregarParam")
@AgregarParametro
public String enviar(SolicitudEnvio solicitud, String cadena) {
return solicitud.toString();
}
@ResponseBody
@RequestMapping("/probarAgregarParam2")
@AgregarParametro(nombreParametro = "contrasena")
public String enviar2(SolicitudEnvio solicitud, String cadena) {
return solicitud.toString();
}
}
Al ejecutar la aplicación y realizar pruebas:
- Accediendo a
/probarAgregarParam, se muestra:SolicitudEnvio(nombreUsuario=Juan, contrasena=null, idCliente=null) - Accediendo a
/probarAgregarParam2, se muestra:SolicitudEnvio(nombreUsuario=null, contrasena=123456, idCliente=null)
El archivo de configuración Maven incluye dependencias esenciales como Spring Boot Starter Web, AOP y Lombok.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ejemplo</groupId>
<artifactId>demo-anotaciones</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>