如何在 Spring Boot 2 中检索应用程序上下文

How to retrieve the Application Context in Spring Boot 2

我将此 ApplicationContextProvider class 与 MyApplication.java(应用程序 运行 的入口点)一起定义:

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;
  }
}

让包 restapi 包含两个 class(Greeting 只是一个 class 来保存数据):

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));
  }
}

最后 MyApplication class 是:

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);
  }    
}

当我 运行 应用程序并在我的浏览器中调用 http://localhost:8081/greeting 时,它在打印 app context is NULL 时崩溃了。如何获取应用程序上下文?我需要它来检索简单的计数器 bean。

tl;dr:你不需要上下文;有更好的方法。

ApplicationContextAware 是 Spring 旧版本的产物,在许多 now-standard 功能可用之前。在现代 Spring 中,如果您需要 ApplicationContext,只需像任何其他 bean 一样注入它。但是,您几乎可以肯定不应该直接与它交互,尤其是对于 getBean,应该将其替换为注入您得到的任何东西。

一般来说,当你需要一个Spring bean时,你应该将它声明为构造函数参数。 (如果你有多个构造函数,你需要用 @Autowired 注释一个,但如果只有一个构造函数,Spring 足够聪明,知道如何使用它。)如果你使用 Lombok,你可以使用@Value自动编写构造函数,Groovy和Kotlin有类似的特性。

在您在这里展示的 Micrometer 的特定情况下,将单个指标声明为 bean 是不常见的,因为它们是 fine-grained 旨在应用于特定代码路径的工具。 (某些服务可能有 10 个单独的指标来跟踪各种可能的情况。)相反,您将 MeterRegistry 和 select 作为构造函数的一部分所需的计数器或其他指标注入。在这里,您的控制器 class 应该如下所示。 (我已经删除了重复项 AtomicLong,但如果您有特殊原因需要它,您可以按照您展示的那样将其添加回去。)

@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));
  }
}