Alexandru
Alexandru

Reputation: 241

Aspect for class or subclass annotated with

I want to be able to track methods that are anotated with @RequestMapping that are annotated with a certain annotation (for instance @LooseController).

I have two pointcuts: requestMappings() and looseController()

this works well if the method that is annotated with @RequestMapping is in a class that has @LooseController but not if the @LooseController is in a subclass

for instance, when update(id) is called on Controller1 it is not caught by this aspect


Update to include more information:

package de.scrum_master.app;

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

@Aspect
@Component
public class MyAspect {

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    private static void requestMapping() {}

    @Pointcut("@within(de.scrum_master.app.LooseController)")
    private static void looseController() {}

//    @Pointcut("@this(de.scrum_master.app.LooseController)")
//    private static void looseController() {}


    @Before("requestMapping() && looseController()")
    public void myAdvice(JoinPoint thisJoinPoint) {
        System.out.println(thisJoinPoint);
    }

}

package de.scrum_master.app;

import org.springframework.web.bind.annotation.RequestMapping;

//@LooseController
public abstract class PutController {

    @RequestMapping("/{id}")
    public void update(String id) {
    }

}

package de.scrum_master.app;

import java.lang.annotation.Retention;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(RUNTIME)
public @interface LooseController {
}

package de.scrum_master.app;

import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

@Component
@LooseController
@RequestMapping("/something")
public class Controller1 extends PutController {
}

package de.scrum_master.app;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class AspectApplication implements CommandLineRunner {

    @Autowired
    private Controller1 controller1;

    @Autowired
    private ConfigurableApplicationContext context;

    public static void main(String[] args) {
        SpringApplication.run(AspectApplication.class, "--logging.level.root=WARN", "--spring.main.banner-mode=off");
    }

    @Override
    public void run(String... strings) throws Exception {
        controller1.update("test");
        context.close();
    }

}

<?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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>aspects</groupId>
    <artifactId>aspects</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <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>

    </dependencies>

</project>

Upvotes: 1

Views: 3320

Answers (1)

kriegaex
kriegaex

Reputation: 67317

As I said in my comment, I have to speculate:

  • It begins with your exact pointcut (which you do not show)
  • and goes on with the question whether you use Spring AOP or full AspectJ via LTW (load-time weaving).
    • In the former case you do not explain if your target classes are actual Spring beans because Spring AOP can only proxy Spring beans/components.
    • In the latter case you have more options but have to configure your application in a different way.
  • You also do not show your own annotation's implementation, especially not if it has the required runtime retention.

My hypothesis for now is that

  • you use proxy-based Spring AOP and
  • all classes and aspects are @Components or otherwise declared as Spring beans in your configuration.

But in order to show you what happens I will use a stand-alone Java example with AspectJ, not a Spring application. I only put spring-web.jar and sprint-context.jar on my classpath so as to resolve the Spring annotations.

Annotation:

package de.scrum_master.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface LooseController {}

Abstract base class:

package de.scrum_master.app;

import org.springframework.web.bind.annotation.RequestMapping;

public abstract class PutController {
  @RequestMapping("/{id}")
  public void update(String id) {}
}

Subclass + main method:

package de.scrum_master.app;

import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

@Component
@LooseController
@RequestMapping("/something")
public class Controller1 extends PutController {
  public static void main(String[] args) {
    new Controller1().update("foo");
  }
}

The aspect which I guess you might use:

Please note that your own pointcut within(@blabla.LooseController) is syntactically invalid, this is why I changed it to @within(blabla.LooseController).

package de.scrum_master.aspect;

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

@Aspect
@Component
public class MyAspect {
  @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
  private static void requestMapping() {}

  @Pointcut("@within(de.scrum_master.app.LooseController)")
  private static void looseController() {}

  @Before("requestMapping() && looseController()")
  public void myAdvice(JoinPoint thisJoinPoint) {
    System.out.println(thisJoinPoint);
  }
}

Console log when running Controller1.main:

staticinitialization(de.scrum_master.app.Controller1.<clinit>)
call(void de.scrum_master.app.Controller1.update(String))

Now you see the problem: AspectJ can intercept a few joinpoints for the given pointcut, but neither call nor staticinitialization are supported by Spring AOP according to the documentation.

So either you need to switch to AspectJ with LTW or devise another pointcut strategy.

To be continued, I have to interrupt the answer here for a while because I have an appointment, but will add some more info later.


Update: Okay, here is your problem and a solution: @within(de.scrum_master.app.LooseController) looks inside classes with the @LooseController annotation, but the parent class with the annotated method you are trying to intercept does not have that annotation. Thus, @within() is not the right pointcut type for you. What you want to express is that the instance, i.e. the current target object upon which the method is called, belongs to an annotated class. Therefore, you need a @target() pointcut:

@Pointcut("@target(de.scrum_master.app.LooseController)")
private static void looseController() {}

Alternatively you could also use this workaround (a little ugly, but works too):

@Pointcut("target(@de.scrum_master.app.LooseController Object)")
private static void looseController() {}

Now the console log says:

execution(void de.scrum_master.app.PutController.update(String))

This should also work in Spring AOP because execution() is a supported pointcut type there.

Upvotes: 2

Related Questions