Pointcut — es una expresión que responde a la pregunta "¿dónde exactamente en el código debe activarse el aspecto?". En esencia, es un filtro que elige los métodos necesarios de toda la aplicación. Por ejemplo, "todos los métodos en la capa de servicio" o "métodos con cierta anotación".
En Spring AOP para describir Pointcuts se usa la sintaxis de AspectJ. Sí, al principio puede parecer como código con cifrado RSA, pero en realidad todo tiene sentido. Vamos a desglosarlo.
Expresiones Pointcut integradas
Aquí están las expresiones más básicas que vas a necesitar:
| Expresión | Descripción |
|---|---|
execution |
Sirve para métodos. Se usa en el 99% de los casos |
within |
Indica el área (por ejemplo, una clase o paquete concreto) |
args |
Útil para métodos con ciertos argumentos |
this y target |
Se refieren al objeto proxy (this) o al objeto objetivo real (target) |
bean |
Útil para beans con un nombre específico |
Ejemplo de una expresión Pointcut simple:
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayerMethods() {
// Este método es solo un marcador para el Pointcut
}
Vamos a desglosar este "hechizo":
execution— nos enfocamos en métodos.*— cualquier tipo de retorno (void,String,int, etc).com.example.service.*.*— cualquier clase en el paquetecom.example.servicey cualquier método.(..)— cualquier conjunto de parámetros.
Un poco sobre Wildcards
Los Wildcards (comodines) en las expresiones Pointcut nos permiten ser flexibles. Aquí tienes una guía rápida:
*— significa "cualquiera". Ejemplo:execution(* com.example..*Controller.*(..))— cualquier método en cualquier clase que contengaControlleren el nombre...— significa "cualquier nivel de anidamiento". Por ejemplo,com.example..serviceincluye todos los subpaquetes a partir decom.example.
Ejemplo: profundizando en Pointcut
Probemos algo un poco más avanzado. Imagina que quieres añadir logging para todos los métodos de una clase que empiecen por save.
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example..*Service.save*(..))")
public void saveMethodsPointcut() {
// Pointcut para métodos que empiezan con save
}
@Before("saveMethodsPointcut()")
public void beforeSaveAdvice(JoinPoint joinPoint) {
System.out.println("Llamada al método: " + joinPoint.getSignature().getName());
}
}
Aquí nosotros:
- definimos el punto de corte para todos los métodos que empiezan con
saveen cualquier clase dentro de los subpaquetes decom.examplecuyo nombre termina enService. - Antes de la llamada a esos métodos registramos el nombre del método.
Composición de expresiones Pointcut
Ya mencionamos que a veces necesitas combinar varios Pointcuts. Por ejemplo, quieres afectar solo a métodos de un paquete determinado y que además empiecen con cierta palabra.
En AspectJ hay operadores lógicos para composiciones:
&&— lógico "Y".||— lógico "O".!— lógico "NO".
Ejemplo:
@Pointcut("within(com.example..*)")
public void withinExamplePackage() {}
@Pointcut("execution(* save*(..))")
public void saveMethods() {}
@Pointcut("withinExamplePackage() && saveMethods()")
public void saveMethodsInExamplePackage() {}
Así, saveMethodsInExamplePackage() se activará solo en métodos que empiecen por save y que estén en paquetes com.example.
Creación de un aspecto personalizado
Ahora apliquemos lo aprendido en la práctica. Supongamos que en nuestra aplicación hay un servicio encargado de gestionar usuarios (UserService). Queremos:
- Registrar llamadas a métodos que empiecen por
find. - Ejecutar lógica adicional solo cuando el método devuelva un
String.
Implementación
@Aspect
@Component
public class CustomAspect {
// Registro de todos los métodos que empiezan con find
@Pointcut("execution(* com.example.service.UserService.find*(..))")
public void findMethods() {}
// Acción antes de la llamada al método
@Before("findMethods()")
public void logBeforeFind(JoinPoint joinPoint) {
System.out.println("Llamada al método: " + joinPoint.getSignature().getName());
}
// Reacción tras la ejecución exitosa del método que devuelve String
@AfterReturning(
pointcut = "findMethods()",
returning = "result"
)
public void afterReturningFind(JoinPoint joinPoint, Object result) {
if (result instanceof String) {
System.out.println("El método " + joinPoint.getSignature().getName() +
" devolvió correctamente el valor: " + result);
}
}
}
Qué ocurre:
- Con
@Pointcutseleccionamos solo los métodos en la claseUserServicecuyos nombres empiezan porfind. - Usamos
@Beforepara hacer logging de la llamada. - Usamos
@AfterReturningpara procesar el valor retornado si es una cadena.
Práctica: creación de un aspecto personalizado más complejo
Complicamos un poco la tarea. Supongamos que necesitamos registrar todos los métodos de los controladores que estén anotados con @GetMapping.
@Aspect
@Component
public class ControllerLoggingAspect {
// Pointcut para métodos anotados con @GetMapping
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public void getMappingMethods() {}
// Registro antes de ejecutar el método
@Before("getMappingMethods()")
public void logBeforeGetMapping(JoinPoint joinPoint) {
System.out.println("Llamada al método del controlador: " + joinPoint.getSignature().getName());
}
}
Usamos la anotación @annotation para identificar exactamente los métodos objetivo (en este caso, métodos con @GetMapping). ¿Ves lo potente que se vuelve la herramienta?
Depuración y errores típicos
AOP no está libre de sus propios rompecabezas. Aquí tienes algunas cosas a tener en cuenta:
- Si tu aspecto no funciona, asegúrate de que la clase del aspecto está anotada con
@Componenty registrada en el contexto de Spring. - Comprueba que indicaste correctamente la ruta de paquetes en
execution. Un nivel de anidamiento omitido o una errata puede arruinarlo todo. - Fíjate en los proxies: si llamas a un método de la clase desde la misma clase, el aspecto puede no activarse.
Comprobación manual de expresiones Pointcut
Si te interesa saber qué métodos exactamente coinciden con tu expresión Pointcut, siempre puedes comprobarlo manualmente:
@Before("execution(* com.example.service.*.*(..))")
public void debugPointcut(JoinPoint joinPoint) {
System.out.println("Entró en el Pointcut: " + joinPoint.getSignature().getName());
}
Verás en los logs la lista de métodos que coinciden con la expresión.
¡Estás listo para crear aspectos que puedan controlar la lógica de negocio de tu aplicación! En el siguiente paso profundizaremos aún más en los objetos proxy y descubriremos sus secretos ocultos.
GO TO FULL VERSION