Spring AOP this() PCD context binder doesn't work with aspect instantiation model perthis()

150 Views Asked by At

I'm trying to log which aspect instance is responsible for which proxied object. However, when I'm collecting proxy object context through this() PCD and using perthis() instantiation model I'm getting an error related to the variable name of proxy object that I use in the pointcut expression:

warning no match for this type name: bean [Xlint:invalidAbsoluteTypeName]

Maven dependencies that I use:

<properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>6.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.19</version>
        </dependency>
</dependencies>

This is an aspect that I use to implement that I needed:

@Aspect("perthis(com.sj.aspects.AssociationsAspect.exampleBeans())")
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AssociationsAspect {
    public AssociationsAspect(){
        System.out.println("Creating aspect instance!");
    }

    @Pointcut("execution(* com.sj.utilities.Example.*(..))")
    public void exampleBeans(){};

    @Pointcut("execution(* com.sj.utilities.Example.*(..)) && this(bean)")
    public void exampleOperations(Object bean){};

    @Before(value = "exampleOperations(bean)", argNames = "jp,bean")
    public void beforeExampleMethodsExecution(JoinPoint jp, Object bean){
        System.out.println(
                "JoinPoint: " + jp.getStaticPart() +
                        "\n\taspect: " + this +
                        "\n\tobject: " + bean
        );
    }
}

I tried to change bean as variable name to concrete type, but from documentation it will give different from binding result:

Any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:

  this(com.xyz.service.AccountService)

As well as it will be changing exiting error to another:

error at ::0 formal unbound in pointcut 

Funny enough, if you put away ("perthis(com.sj.aspects.AssociationsAspect.exampleBeans())") then everything will work fine and Spring container will create a separate aspect instance for every proxy object in it. However that is not desirable and most likely a bug, because through @Scope() annotation I only say that there can be multiple instances of same aspect and that Spring container will need to create new instance when I told it to do, but not that it need to create them when it wants to.

The final solution to which I came was to use JoinPoint reflection API instead of collecting context through this() PCD. And it works fine, however I have preconceptions related to how @Scope() works without perthis() instantiation model and with it.

In the end I want to know, 'Is there a solution for collecting proxy object context with this() PCD and using perthis() instantiation model at the same time?'. As well as what are mistakes in the aspect that I described earlier, that give such an error.

2

There are 2 best solutions below

5
On

This answer is incorrect in this particular context, but maybe valuable for other readers, because it describes a frequently made mistake by readers of the Spring manual who do not realise that the code snippets in the manual refer to pointcut methods with fully qualified name rather than to target methods.


I am assuming that you got the syntax example for perthis() from this part of the Spring manual. Unfortunately, the example is somewhat misleading, because it uses fully qualified pointcut method names, expecting the user to have read another manual section describing how to use shared pointcut definitions from a separate class, which is a rather exotic use case.

Inside perthis(), when not referencing a poincut method name but defining an inline pointcut targeting a specific application method, you need to specify a full pointcut, not just a method name without even a return type. Examples for valid clauses would be:

  • perthis(myPointcut()), if myPointcut is specified as a separate, named pointcut
  • perthis(this(org.acme.MyClass))
  • perthis(execution(* doCommand(..))
  • perthis(execution(* doCommand(..)) && this(org.acme.MyClass))

See also the AspectJ documentation here and there. Unfortunately, documentation is sparse.

In your particular case, you might want:

perthis(execution(* com.sj.utilities.Example.*(..)))

But of course, referring to an external pointcut like

perthis(exampleBeans())

for pointcuts in the same class or super class or

perthis(com.sj.aspects.AssociationsAspect.exampleBeans())

for poinctuts in a shared pointcut class is also OK.


Update: I created Spring pull request # 29998 in order to improve the documentation, because I also misunderstood the brief examples out of context there.

0
On

Adding another answer, leaving the previous one there for reference, even though it is false in this case, because the OP did not make the common mistake to refer to method calls in a wrong way, but actually his perthis clause as such is correct, referring to a pointcut with a fully qualified name.


Thanks for your GitHub sample project.

The problem seems to be a limitation of Spring AOP for your particular use case. Maybe you can open an issue and ask if this restriction can somehow be lifted configuratively. I guess it might be a chicken-vs-egg problem during bootstrapping, i.e. what is instantiated first and available for context binding.

The problem is also not limited to perthis instantiation, but also happens for pertarget in combination with advice pointcuts using this(myProxy) or target(myProxyTarget).

If you, as you found out already, simply use jp.getThis() or jp.getTarget() instead of binding the objects to an advice method parameter directly, you are fine. If you use singleton instantiation, you can use this(myProxy) or target(myProxyTarget) without problems, too. According to your example, you found out about that already, I am just summarising for other readers.

BTW: Good digging, nice research for this edge case.

P.S.: If you would use native AspectJ in your Spring application instead of Spring AOP, probably it would just work the way you want.


With regard to this(com.xyz.service.AccountService) leading to a subsequent error at ::0 formal unbound in pointcut, there you are actually making a mistake. When not referring to a method parameter name but to a class, you cannot bind the parameter and need to remove Object bean from your pointcut and from the advice method using it.