CodeGym /Courses /Module 5. Spring /Pointcut Configuration and Creating Custom Aspects

Pointcut Configuration and Creating Custom Aspects

Module 5. Spring
Level 3 , Lesson 5
Available

Pointcut — an expression that answers the question "where exactly in the code should the aspect kick in?". Basically, it's a filter that selects the methods you want from the whole application. For example, "all methods in the service layer" or "methods with a specific annotation".

In Spring AOP, Pointcuts are described using AspectJ syntax. Yeah, at first it can look like RSA encryption-level gobbledygook, but it's actually pretty logical. Let's break it down.

Built-in Pointcut Expressions

Here are the most basic expressions you'll need:

Expression Description
execution Targets methods. Used in about 99% of cases
within Specifies a scope (for example, a specific class or package)
args Good for methods with certain arguments
this and target Refers to the proxy object (this) or the actual target object (target)
bean Matches beans with a specific name

Example of a simple Pointcut expression:


@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayerMethods() {
    // This method is just a marker for the Pointcut
}

Let's break down this "spell":

  • execution — we're targeting methods.
  • * — any return type (void, String, int, etc).
  • com.example.service.*.* — any class in the com.example.service package and any method.
  • (..) — any set of parameters.

A bit about Wildcards

Wildcards in Pointcut expressions let us be flexible. Here's a short guide:

  • * — means "any". Example: execution(* com.example..*Controller.*(..)) — any method in any class that has Controller in its name.
  • .. — means "any number of package levels". For example, com.example..service includes all subpackages under com.example.

Example: Digging Deeper into Pointcut

Let's try something a bit more advanced. Suppose we want to add logging for all class methods that start with save.


@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* com.example..*Service.save*(..))")
    public void saveMethodsPointcut() {
        // Pointcut for methods that start with save
    }

    @Before("saveMethodsPointcut()")
    public void beforeSaveAdvice(JoinPoint joinPoint) {
        System.out.println("Calling method: " + joinPoint.getSignature().getName());
    }
}

Here we:

  • define a join point for all methods that start with save in any class within the com.example subpackages whose class name ends with Service.
  • log the method name before such methods are called.

Composing Pointcut Expressions

We already mentioned that sometimes you need to combine multiple Pointcuts. For example, you might want to target only methods from a specific package, and that also start with a certain word.

AspectJ has logical operators for composition:

  • && — logical "AND".
  • || — logical "OR".
  • ! — logical "NOT".

Here is an example:


@Pointcut("within(com.example..*)")
public void withinExamplePackage() {}

@Pointcut("execution(* save*(..))")
public void saveMethods() {}

@Pointcut("withinExamplePackage() && saveMethods()")
public void saveMethodsInExamplePackage() {}

So, saveMethodsInExamplePackage() will trigger only on methods that start with save and live in packages under com.example.


Creating a Custom Aspect

Now let's apply what we've learned in practice. Suppose our app has a service that manages users (UserService). We want to:

  1. Log calls to methods that start with find.
  2. Run extra logic only when the method returns a String.

Implementation


@Aspect
@Component
public class CustomAspect {

    // Log all methods that start with find
    @Pointcut("execution(* com.example.service.UserService.find*(..))")
    public void findMethods() {}

    // Do something before the method is called
    @Before("findMethods()")
    public void logBeforeFind(JoinPoint joinPoint) {
        System.out.println("Calling method: " + joinPoint.getSignature().getName());
    }

    // React to successful method execution that returns a String
    @AfterReturning(
        pointcut = "findMethods()",
        returning = "result"
    )
    public void afterReturningFind(JoinPoint joinPoint, Object result) {
        if (result instanceof String) {
            System.out.println("Method " + joinPoint.getSignature().getName() +
                               " successfully returned value: " + result);
        }
    }
}

What's happening:

  1. With @Pointcut we select only methods in UserService whose names start with find.
  2. We use @Before to log the call.
  3. We use @AfterReturning to additionally handle the return value if it's a string.

Practice: Creating a More Advanced Custom Aspect

Let's up the difficulty. Suppose we need to log all controller methods that are annotated with @GetMapping.


@Aspect
@Component
public class ControllerLoggingAspect {

    // Pointcut for methods annotated with @GetMapping
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getMappingMethods() {}

    // Log before the method executes
    @Before("getMappingMethods()")
    public void logBeforeGetMapping(JoinPoint joinPoint) {
        System.out.println("Controller method call: " + joinPoint.getSignature().getName());
    }
}

We use the @annotation pointcut designator to precisely target the methods we care about (in this case, methods with @GetMapping). You can see how powerful this tool becomes.


Debugging and Common Mistakes

AOP isn't without its own set of gotchas. Here are a few things to keep in mind:

  1. If your aspect isn't working, make sure the aspect class is annotated with @Component and registered in the Spring context.
  2. Check that you've specified package paths correctly in execution. A missing package level or a typo can negate all your efforts.
  3. Watch out for proxies: if you call a class method from within the same class, the aspect might not trigger.

Manually Checking Pointcut Expressions

If you're curious which methods match your Pointcut expression, you can always check manually:


@Before("execution(* com.example.service.*.*(..))")
public void debugPointcut(JoinPoint joinPoint) {
    System.out.println("Hit Pointcut: " + joinPoint.getSignature().getName());
}

You'll be able to see in the logs the list of all methods that match the expression.


You're ready to build aspects that can control your application's business logic! Next, we'll dig even deeper into proxy objects and look at their hidden secrets.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION