0

I have this ApplicationContextProvider class defined along with the MyApplication.java (entry point where the application is run):

package com.company.my.app;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;


@Component
public class ApplicationContextProvider implements ApplicationContextAware {

  private ApplicationContext applicationContext;

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }

  public ApplicationContext getContext() {
    return applicationContext;
  }
}

Have the package restapi with two classes in it (Greeting is just a class to hold data):

package com.company.my.app.restapi;

import com.company.my.app.ApplicationContextProvider;
import io.micrometer.core.instrument.Counter;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class GreetingController {

  private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);

  private static final String template = "Hello, %s!";
  private final AtomicLong counter = new AtomicLong();

  @RequestMapping("/greeting")
  public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) {

    ApplicationContextProvider acp = new ApplicationContextProvider();
    ApplicationContext context = acp.getContext();

    if (context == null) LOG.info("app context is NULL");

    Counter bean = context.getBean(Counter.class);
    bean.increment();

    return new Greeting(counter.incrementAndGet(),
        String.format(template, name));
  }
}

Finally the MyApplication class is:

package com.company.my.app;

import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.binder.MeterBinder;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
public class MyApplication {

  @Bean
  public MeterBinder exampleMeterBinder() {
    return (meterRegistry) -> Counter.builder("my.counter")
        .description("my simple counter")
        .register(meterRegistry);
  }

  @Configuration
  public class CounterConfig {
    @Bean
    public Counter simpleCounter(MeterRegistry registry) {
      return registry.counter("my.counter");
    }
  }

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

When I run the app and call http://localhost:8081/greeting in my browser, it crashes printing app context is NULL. How do I get the application context? I need it to retrieve the simple counter bean.

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
Robin
  • 29
  • 3
  • 9
  • What do you want the application context *for*? – chrylis -cautiouslyoptimistic- Jun 28 '19 at 16:05
  • The duplicate answers the question of why you're having this problem (which is critical to understanding Spring). Generally speaking, if you need the context, just inject it like a normal bean (and use constructor injection, not field injection, everywhere). You almost certainly should be using better features instead of directly manipulating the context, though. – chrylis -cautiouslyoptimistic- Jun 28 '19 at 16:06
  • where does the `ApplicationContextAware` need to be defined, so my code is similar to your manual bean lookup and it's not working. I will then need to retrieve the simple counter bean - that's why I'm trying to get the application context. – Robin Jun 28 '19 at 16:15
  • How to get my simple counter bean autowired? It's not clear from the other answer you called duplicate. – Robin Jun 28 '19 at 16:18
  • You don't need to define `ApplicationContextAware`, just put `ApplicationContext` as a dependency (preferably in a constructor). In this particular case, if I understand correctly what you're trying to do, the convention is to pass the `MeterRegistry` to the controller and have the controller get the counter and save it in a field. If you can confirm that that's what you're after, I'll do a little cleanup and provide a specific answer. – chrylis -cautiouslyoptimistic- Jun 28 '19 at 16:35
  • Thank you for the comment, what I'm trying to do in general - I set up docker compose file to run 4 docker containers - the jar of the app, statsd, statsd exporter and prometheus. I can manually push metrics to statsd and see it appears in prometheus so I can confirm all docker containers are working fine except the one with the app. I want know to create a rest endpoint, call it, make it increment the counter registered in `MeterRegistry` and see the result to appear in prometheus. So I need to learn how to create and update the counter with the bean and how spring autowires things. – Robin Jun 28 '19 at 19:31
  • I'm a complete spring noob, it's very hard to grasp these annotations and automatic hidden code. So not so easy to understand the articles on the web about spring autowiring and beans. – Robin Jun 28 '19 at 19:33

1 Answers1

1

tl;dr: You don't need the context; there's a better way.

ApplicationContextAware is an artifact from much older versions of Spring, before many of the now-standard features were available. In modern Spring, if you need the ApplicationContext, just inject it like any other bean. However, you almost certainly shouldn't interact with it directly, especially for getBean, which should be replaced with injecting whatever you were getting.

In general, when you need a Spring bean, you should declare it as a constructor parameter. (If you have multiple constructors, you need to annotate one with @Autowired, but if there's only a single constructor, Spring is smart enough to know to use it.) If you're using Lombok, you can use @Value to automatically write the constructor, and Groovy and Kotlin have similar features.

In the specific case of Micrometer, which you're showing here, it is not conventional to declare individual metrics as beans because they are fine-grained tools intended to apply to specific code paths. (Some services might have 10 separate metrics to track various possible scenarios.) Instead, you inject the MeterRegistry and select the counters or other metrics that you need as part of your constructor. Here, your controller class should look like this. (I've eliminated the duplicate AtomicLong, but you could add it back in as you showed if there's a specific reason you need it.)

@RestController
public class GreetingController {

  private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);

  private static final String template = "Hello, %s!";

  private final Counter counter;

  public GreetingController(MeterRegistry meterRegistry) {
    counter = meterRegistry.counter("my.counter");
  }


  @RequestMapping("/greeting")
  public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) {

    counter.increment();
    long count = (long) counter.count();

    return new Greeting(count, String.format(template, name));
  }
}
chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • 2
    It worked!! I'm so happy and the solution is so simple. I can see the metric in prometheus now which means all the pipeline is working correctly. Thank you for this. That bean solution was recommended by a person from https://gitter.im/spring-projects/spring-boot. If you know the place where I can learn the latest best practices in spring apart from docs please let me know. – Robin Jun 28 '19 at 21:00
  • @Robin I'd recommend the latest version of *Learning Spring Boot* by Greg Turnquist. – chrylis -cautiouslyoptimistic- Jun 29 '19 at 00:28