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