Search code examples
javaspringspring-bootmavenspring-aop

Spring @configurable NullPointerException, @autowired service is null


I'm trying to use @configurable in spring to use a @autowired service in a non bean class I create.
It doesn't want to work anymore whatever I try.
Can someone tell me what I'm doing wrong? (I did some research but I'm totally clueless now)
Here is a very basic code example I made :

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 https://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>2.7.5</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo2</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Configuration ComponentScan class

package com.example.demo2;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;

@Configuration
@ComponentScan
@EnableSpringConfigured
public class AspectJConfig
{
    
}

@SpringBootApplication class

package com.example.demo2;

import javax.annotation.PostConstruct;

//import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class Demo2Application
{
    //@Autowired
    //private HelloWorldService helloWorldService;
    
    public static void main(String[] args)
    {
        SpringApplication.run(Demo2Application.class, args);
    }
    
    @PostConstruct
    public void doSomethingIProbablyShouldNotBeDoing()
    {
        //helloWorldService.sayHello();
        HelloWorldClient client = new HelloWorldClient();
        client.sayHello();
    }
    
}

class with @Configurable and @Autowired service

package com.example.demo2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class HelloWorldClient
{
    @Autowired
    private HelloWorldService service;
    
    public void sayHello()
    {
        // Used injected instance of service
        service.sayHello();
    }
}

@Service class

package com.example.demo2;

import org.springframework.stereotype.Service;

@Service
public class HelloWorldService
{
    public void sayHello()
    {
        System.out.println("Hello world!");
    }
}

Also here is a link to my previous post on that subject. I did received an answer to my question that was working. But for whatever reason it doesn't work anymore on my side.
Spring @configurable NullPointerException


Solution

  • @Configurable should be used in connection with native AspectJ, not with Spring AOP, see here. Because the annotation is meant to be used with POJOs rather than Spring beans, this makes sense.

    In my answer to your first question, we used AspectJ Maven Plugin to do compile-time weaving (CTW). Either you need to do the same here or configure load-time weaving (LTW) instead.

    When using LTW, in order for org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect to work, which is responsible for achieving the POJO dependency injection, you need either of

    • -javaagent:/path/to/aspectjweaver.jar on the JVM command line or
    • -javaagent:/path/to/spring-instrument.jar on the JVM command line and @EnableLoadTimeWeaving in your Spring configuration or
    • de.invesdwin:invesdwin-instrument in the dependency list plus a code snippet initialising the weaver in your application. On more recent Java versions, you probably also need --add-opens java.base/java.lang=ALL-UNNAMED on the JVM command line to enable invesdwin-instrument to work correctly, using reflection.

    You also need spring-aspects for the AnnotationBeanConfigurerAspect to be found. Optionally, you might want to add your own META-INF/aop.xml (or org/aspectj/aop.xml) file in the resources directory, if you want to configure certain weaving options or deactivate unneeded aspects from spring-aspects, if the corresponding warning messages on the console get on your nerves.

    I know that this is not trivial, which is why I think that the CTW approach is easier to set up. But if you want to apply aspects dynamically via LTW, you need to use one of the approaches mentioned above. It is not rocket science, just a bit complicated when doing it the first time. But like Miyamoto Musashi said: "It may seem difficult at first, but all things are difficult at first."


    Update: Here is an MCVE for you, i.e. a full, minimal example:

    https://github.com/kriegaex/SO_AJ_SpringMavenConfigurableNPE_74184130

    Feel free to play with it and remove the code using invesdwin-instrument and the corresponding dependency, if you want to use -javaagent and Spring on-board means. I actually recommend that.