Search code examples
javanullpointerexceptionaopfunctional-interface

Why NullPointException raised when AOP @pointcut cut into FunctionalInterface?


Today I encountered a NullPointExcepiton When I tried to use an AOP Logger in java to print logs of a class which contains a BiPredicate(FuncionalInterface).

Problem:

Is there any wrong usage of FunctionalInterface with AOP?

When I set the pointCut to BiPredicateExample.java, the exception will be raised by biPredicateExample.cmp(FuntionalInterface), while the method in this class works fun(biPredicateExample.cmp1()).

The NullPointExcepiton can be reproduced using following codes:

Github repository link here

com/test/BiPredicateExample.java

import org.springframework.stereotype.Component;
import java.util.function.BiPredicate;

@Component
public class BiPredicateExample {

    public BiPredicate<Integer,Integer> cmp = (x,y) -> (x>y);

    public boolean cmp1(Integer x, Integer y){
        return x>y;
    }
}

com/logger/BiPredicateExample.java

package com.logger;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAspect {

    /**
     * BiPredicateExample works fine.
     */
//    @Pointcut("execution(* com.empty.*.*(..) )")

    /**
     *  It'll raise NullPointException When this PointCut cut
     *  into the FunctionalInterface.
     */
    @Pointcut("execution(* com.test.*.*(..) )")
    public void logPointCut(){}

    @Before("logPointCut()")
    public void printBeforeMethodInvoke(JoinPoint joinPoint){

    }

}

com/Application.java

package com;

import com.test.BiPredicateExample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class Application implements CommandLineRunner{
    @Autowired
    BiPredicateExample biPredicateExample;

    public static void main(String[]args){
        SpringApplication.run(Application.class,args);
    }

    @Override
    public void run(String... strings) throws Exception {

        boolean w=biPredicateExample.cmp1(10,2);
        System.out.println(w);

        boolean f=biPredicateExample.cmp.test(10,2);
        System.out.println(f);
    }
}

Thanks!

my 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>groupId</groupId>
    <artifactId>AOP_FunctionalInterface_Test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.0.3.RELEASE</version>
        </dependency>
    </dependencies>>


</project>

Solution

  • The problem is that the member variable cmp only has a meaningful value in the original class, but as soon as you use Spring AOP, you are actually interacting with a dynamic proxy object. JDK dynamic proxies, however, only proxy public method calls (CGLIB ones also protected and package-scoped methods). There is no proxying mechanism for member variables, you need to make sure that you do not directly access internal state via member access from outside but e.g. via getter method.

    I.e. in this case you change biPredicateExample.cmp into biPredicateExample.getCmp(), that's all:

    package com.test;
    
    import org.springframework.stereotype.Component;
    
    import java.util.function.BiPredicate;
    
    @Component
    public class BiPredicateExample {
      private BiPredicate<Integer, Integer> cmp = (x, y) -> (x > y);
    
      public boolean cmp1(Integer x, Integer y) {
        return x > y;
      }
    
      public BiPredicate<Integer, Integer> getCmp() {
        return cmp;
      }
    }
    
    package com;
    
    import com.test.BiPredicateExample;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class Application implements CommandLineRunner {
      @Autowired
      BiPredicateExample biPredicateExample;
    
      public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
      }
    
      @Override
      public void run(String... strings) throws Exception {
        System.out.println(biPredicateExample.cmp1(10, 2));
        System.out.println(biPredicateExample.getCmp().test(10, 2));
      }
    }
    
    package com.logger;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class LogAspect {
      @Pointcut("execution(* com.test..*(..) )")
      public void logPointCut() {}
    
      @Before("logPointCut()")
      public void printBeforeMethodInvoke(JoinPoint joinPoint) {
        System.out.println(joinPoint);
      }
    }
    

    Then the console log changes into:

      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.0.3.RELEASE)
    
    2018-06-16 14:15:10.040  INFO 12456 --- [           main] com.Application                          : Starting Application on Xander-Ultrabook with PID 12456 (C:\Users\alexa\Documents\java-src\AOP_FunctionalInterface_Test\target\classes started by alexa in C:\Users\alexa\Documents\java-src\AOP_FunctionalInterface_Test)
    2018-06-16 14:15:10.056  INFO 12456 --- [           main] com.Application                          : No active profile set, falling back to default profiles: default
    2018-06-16 14:15:10.118  INFO 12456 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3c87521: startup date [Sat Jun 16 14:15:10 ICT 2018]; root of context hierarchy
    2018-06-16 14:15:11.437  INFO 12456 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
    2018-06-16 14:15:11.452  INFO 12456 --- [           main] com.Application                          : Started Application in 1.724 seconds (JVM running for 2.524)
    execution(boolean com.test.BiPredicateExample.cmp1(Integer,Integer))
    true
    execution(BiPredicate com.test.BiPredicateExample.getCmp())
    true
    2018-06-16 14:15:11.452  INFO 12456 --- [       Thread-5] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3c87521: startup date [Sat Jun 16 14:15:10 ICT 2018]; root of context hierarchy
    2018-06-16 14:15:11.531  INFO 12456 --- [       Thread-5] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
    
    Process finished with exit code 0