1

Application 1 : (Contains AOP code. A Spring boot application)

LogAspect.Java

@Aspect
@Component
public class LogAspect {
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Pointcut("within(@org.springframework.stereotype.Repository *)"
            + " || within(@org.springframework.stereotype.Service *)"
            + " || within(@org.springframework.web.bind.annotation.RestController *)")
    public void springBeanPointcut() {}

    @Pointcut("within(com.akpanda.springmain..*)")
    public void applicationPackagePointcut() {}

    @Around("applicationPackagePointcut() && springBeanPointcut()")
    public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        CodeSignature c = (CodeSignature) joinPoint.getSignature();
        log.info("Enter: {}.{}() with ParameterName[s]= {} and argumentValue[s]= {}",
                joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
                Arrays.toString(c.getParameterNames()), Arrays.toString(joinPoint.getArgs()));
    }
}

pom.xml (Application 1)

<groupId>com.akpanda</groupId>
  <artifactId>aop</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>aop</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>11</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

  </dependencies>

Application 2 : (Another spring boot application) It has a dependency to the above Application 1, added as a maven dependency

pom.xml (Application 2)

<dependency>
    <groupId>com.akpanda</groupId>
    <artifactId>aop</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

Main class of Application 2

@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringmainApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringmainApplication.class, args);
    }
}

Controller class of Application 2

@RestController
public class FeesController {
    @Autowired
    private FeesCalculator feesCalculator;

    @RequestMapping(value = "/fees/caclulate", method = RequestMethod.GET)
    public Fees calculateFees() {
        return feesCalculator.calculateFees();
    }
}

Now when i run Application 2 and hit endpoint

/fees/caclulate

, I expect the aop dependency added in maven to kick in. and the method logAround() to be called whenever any method of Application 2 is called.

If i do not add application 1 as a dependency but simply create class in application 2 containing aspects and advices it works. But in that case I have 10 different projects for which this needs to be duplicated. I want to separate the AOP code as a module (another spring boot application) and use it in many different spring boot projects as a maven dependency.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Is this spring boot or MVC? Spring boot has starter dependency already.. org.springframework.boot spring-boot-starter-aop – Tim Mar 18 '22 at 14:32
  • Also can you try wrapping the @Around like `@Around("execution(* package (..)")..` . – Tim Mar 18 '22 at 14:37
  • Can you just add details about how do you call AOP? Are you calling any method of class inside package? @Around is an advice type, which ensures that an advice can run before and after the method execution. – Aks 1316 Mar 18 '22 at 16:34
  • @Tim Updated the question with proper code snippets and more details. Any help is appreciated. – Locke Lamora Mar 18 '22 at 21:19
  • @Aks1316, Updated the question with more details. AOP is working finr if I have a class inside the same project defining the AOP aspects.. Once i move the AOP code to a different project and add the second project as a dependency on the first one, it stops working. – Locke Lamora Mar 18 '22 at 21:20
  • Why do you have the dependencies for a whole Spring application in your aspect module? Isn't that a bit too much? Every other module that wants to use your aspect library get tons of transitive dependencies. You should isolate the aspect library and use it in both applications. – kriegaex Mar 19 '22 at 04:00
  • @LockeLamora, I think answer given by kriegaex will resolve your issue. He already mentioned 2 points that you need to take care. package scanning and issue with your aspect. – Aks 1316 Mar 19 '22 at 07:47

1 Answers1

2

Like I said in my comment:

Why do you have the dependencies for a whole Spring application in your aspect module? Isn't that a bit too much? Every other module that wants to use your aspect library get tons of transitive dependencies. You should isolate the aspect library and use it in both applications.

But if we ignore this for a moment, there should not be any problem for your aspect to be applied. Maybe your main class in project B is com.akpanda.springmain.SpringmainApplication, but your aspect is in another base package, say com.akpanda.aop.LogAspect. In that case, it will not be picked up by component scan. That is easy enough to fix by adding this to your application class:

@ComponentScan(basePackages = { "com.akpanda.aop", "com.akpanda.springmain" })

Then the aspect will work immediately. But looking at your advice method, there are problems with it:

  1. You are using an @Around advice which returns void, i.e. it only makes sense to apply it to methods which also return void. Your target method however returns Fees. In order to accommodate any return type in an around-advice, change the return type to Object.
  2. The around-advice does not return anything and it also does not proceed() to the original target method, returning its result.

You can fix it like this:

  @Around("applicationPackagePointcut() && springBeanPointcut()")
  public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    CodeSignature signature = (CodeSignature) joinPoint.getSignature();
    log.info("Enter: {}.{}() with ParameterName[s]= {} and argumentValue[s]= {}",
      signature.getDeclaringTypeName(), signature.getName(),
      Arrays.toString(signature.getParameterNames()), Arrays.toString(joinPoint.getArgs()));
    return joinPoint.proceed();
  }

Or alternatively, if your advice only logs and does not modify and method parameters or return values and also does not do any exception handling, simply use a @Before advice instead, then you are back to a void method there is no need to proceed() or declare Throwable:

  @Before("applicationPackagePointcut() && springBeanPointcut()")
  public void logBefore(JoinPoint joinPoint) {
    CodeSignature signature = (CodeSignature) joinPoint.getSignature();
    log.info("Enter: {}.{}() with ParameterName[s]= {} and argumentValue[s]= {}",
      signature.getDeclaringTypeName(), signature.getName(),
      Arrays.toString(signature.getParameterNames()), Arrays.toString(joinPoint.getArgs()));
  }

BTW, you could also write your pointcut a little simpler using @within instead of within:

  @Pointcut(
    "@within(org.springframework.stereotype.Repository)" +
    " || @within(org.springframework.stereotype.Service)" +
    " || @within(org.springframework.web.bind.annotation.RestController)"
  )
  public void springBeanPointcut() {}
kriegaex
  • 63,017
  • 15
  • 111
  • 202