springspring-aop

Spring AOP @Before and @After Not Triggering for Method Execution in Simple Java Application


Java Classes

1. Example17.java

package com.example.controller;

import com.example.config.ProjectConfig;
import com.example.service.PaymentServiceImpl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Example17 {
    public static void main(String[] args) {
        var context = new AnnotationConfigApplicationContext(ProjectConfig.class);

        PaymentServiceImpl paymentService = context.getBean(PaymentServiceImpl.class);

        paymentService.makePayment();
    }
}

2. MyAspect.java

package com.example.aspects;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    @Before("execution(* com.example.service.PaymentServiceImpl.makePayment())")
    public void printBefore() {
        System.out.println("Payment Started...");
    }

    @After("execution(* com.example.service.PaymentServiceImpl.makePayment())")
    public void printAfter() {
        System.out.println("Payment done....");
    }
}

3. ProjectConfig.java

package com.example.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = {"com.example.service", "com.example.aspects"})
@EnableAspectJAutoProxy
public class ProjectConfig {
}

4. PaymentServiceImpl.java

package com.example.service;

import org.springframework.stereotype.Component;

@Component
public class PaymentServiceImpl implements PaymentService {

    @Override
    public void makePayment() {
        System.out.println("Amount Debited");
        System.out.println("Amount credited to another account");
    }
}

POM File

pom.xml

<?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.springwithshubham</groupId>
    <artifactId>example-17</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>19</maven.compiler.source>
        <maven.compiler.target>19</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Spring Context for ApplicationContext and Component Scanning -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.1.10</version>
        </dependency>

        <!-- Spring Aspects for AOP support -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.1.10</version>
        </dependency>

    </dependencies>
</project>

Description

I am trying to implement aspect-oriented programming (AOP) using Spring AOP annotations @Before and @After. My expectation is that the output should include the AOP messages before and after the makePayment method execution. However, the actual output only prints the method's internal messages, ignoring the AOP advice.

Expected Output:

Payment Started...
Amount Debited
Amount credited to another account
Payment done....

Actual Output:

Amount Debited
Amount credited to another account

Could you please help me figure out why the AOP advice isn't being applied as expected?



Solution

  • Your sample code does not run, as you claimed, but yields this error with the given configuration:

    Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.service.PaymentServiceImpl' available
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1252)
        at com.example.controller.Example17.main(Example17.java:10)
    

    This is due to the fact that you run the plain Spring container in default AOP proxying mode, i.e. it tries to use JDK interface proxies. You have hidden interface class PaymentService from us, which I guess looks like this:

    package com.example.service;
    
    public interface PaymentService {
      void makePayment();
    }
    

    Option 1 (recommended)

    Simply use the interface when instantiating the bean:

    PaymentService paymentService = context.getBean(PaymentService.class);
    

    Option 2

    Alternatively, you can change the proxying mode as follows in your configuration:

    @EnableAspectJAutoProxy(proxyTargetClass = true)
    

    Both solutions will yield the behaviour you want. But actually, programming against interfaces (option 1) is cleaner.