Issue
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 orde.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.
Answered By - kriegaex
Answer Checked By - David Marino (JavaFixing Volunteer)