'JMH Benchmark get NullPointerException with Autowired field in Spring(with maven) project

I try to benchmark some of the methods of my Spring (with maven) project. I need to use @Autowired and @Inject on several fields in my project. While I run my project, it works well. But JMH always gets NullPointerException with @Autowired/@Inject fields.

public class Resources {

    private List<Migratable> resources;

    @Autowired
    public void setResources(List<Migratable> migratables) {
        this.resources = migratables;
    }

    public Collection<Migratable> getResources() {
        return resources;
    }
}

My Benchmark class

@State(Scope.Thread)
public class MyBenchmark {

    @State(Scope.Thread)
    public static class BenchmarkState {

        Resources res;

        @Setup
        public void prepare() {
            res = new Resources();
        }
    }

    @Benchmark
    public void testBenchmark(BenchmarkState state, Blackhole blackhole) {
        blackhole.consume(state.res.getResources());
    }
}

When I run my benchmark, it get NullPointerException at Resources.getResources() More specifically at resources.
It cannot Autowire setResources(). But if I run my project(exclude benchmark), it works fine.
How can I get rid of this NullPointerException with Autowired field while benchmarking?



Solution 1:[1]

Here is an example of how to run Spring-based benchmarks: https://github.com/stsypanov/spring-boot-benchmark.

Basically what you need is to store a reference to your application context as a field of benchmarks class, initialize the context in @Setup method and close it in @TearDown. Something like this:

@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(value = Mode.AverageTime)
public class ProjectionVsDtoBenchmark {

  private ManyFieldsRepository repository;

  private ConfigurableApplicationContext context;

  @Setup
  public void init() {
    context = SpringApplication.run(Application.class);
    context.registerShutdownHook();

    repository = context.getBean(ManyFieldsRepository.class);
  }

  @TearDown
  public void closeContext(){
    context.close();
  }
}

The logic that you are going to measure must be encapsulated in a method of Spring component called from @Benchmark annotated method. Remember general rules of benchmarking to make sure your measurements are correct, e.g. use Blackhole or return value from the method to prevent compiler from DCE.

Solution 2:[2]

Try to use

@RunWith(SpringJUnit4ClassRunner.class) and @ContextConfiguration(locations = {...}) on the test class. This should initialize Spring TestContext Framework and let you autowire dependencies.

If this doesn't work, then you have to start Spring ApplicationContext explicitly as a part of you @Setup annotated method, using either of

ClassPathXmlApplicationContext, FileSystemXmlApplicationContext or WebXmlApplicationContext and resolve beans from that context:

ApplicationContext context = new ChosenApplicationContext("path_to_your_context_location");
res = context.getBean(Resources.class);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2