相关文章推荐
阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天, 点击查看活动详情

今天我们将一块学习一个小技巧,如何在 Spring Boot 应用中查看所有托管在容器中的 Bean?

在 Spring / Spring Boot 应用中,核心功能之一 IoC 容器,它负责所有托管 Bean 的生命周期管理。 获得容器中所有托管 Bean 的方式主要有两种:

  • 使用 ListableBeanFactory 接口
  • 使用 Actuator
  • 01-ListableBeanFactory 接口

    Spring 中 ApplicationContext 接口继承了 ListableBeanFactory 接口。 因此,所有的应用上下文都实现了 ListableBeanFactory 接口,具有该接口定义的所有方法实现,其中就包括我们要使用的:

    String[] getBeanDefinitionNames();
    

    它能够返回容器中注册的所有 Bean 的名称。

    有了这个接口,我们就可以在启动程序后在控制台打印所有 Bean 的名称:

    @SpringBootApplication
    public class ListAllManagedBeanApplication {
        public static void main(String[] args) {
            final ConfigurableApplicationContext ctx = SpringApplication.run(ListAllManagedBeanApplication.class, args);
            final String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
            Arrays.stream(beanDefinitionNames).forEach(System.out::println);
    

    我们能够得到如下输出:

    listAllManagedBeanApplication
    // 省略其他的 bean
    

    @SpringBootApplication注解等价于@EnableAutoConfiguration+@Configuration+@ComponentScan。 其中,@EnableAutoConfiguration会根据 classpath 下的包的不同进行自动话配置,所以上述输出的 Bean 名称会比较多,而且由于每个项目依赖的不同,输出的 Bean 名称也不尽相同; @Configuration@Component的子类型,在@ComponentScan作用下,会为其创建一个 Bean,并托管到容器中。

    将所有的 Bean 名称打印在控制台的意义可能不大,在开发阶段或许能帮助开发者排查问题,在其他测试阶段,帮助并不太大。

    我们可以稍微换个思路,在 Spring Boot 项目中,创建 HTTP API 是非常方便的,所以我们可创建一个 HTTP API,通过访问该接口来获得当前容器中所有的 Bean 名称。 我们新建一个 ManagedBeanVisitorController 类,用来处理 HTTP 请求。

    @RestController
    @RequestMapping("/managed")
    public class ManagedBeanVisitorController implements ApplicationContextAware {
        private ApplicationContext applicationContext;
        @GetMapping("/beans")
        CollectionModel<String> all() {
            final List<String> beanDefinitionNames = Arrays.stream(this.applicationContext.getBeanDefinitionNames()).collect(Collectors.toList());
            return CollectionModel.of(beanDefinitionNames,
                    linkTo(methodOn(ManagedBeanVisitorController.class).all()).withSelfRel());
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
    

    因为需要用到 ApplicationContext 中的 getBeanDefinitionNames 方法,所以我们让 ManagedBeanVisitorController 实现 ApplicationContextAware 接口来获得容器的引用。

    然后,我们运行程序,并访问 http://localhost:8080/magaged/beans 就会得到当前容器中所有 Bean 的名称列表:

    "_embedded": { "stringList": [ "listAllManagedBeanApplication", "managedBeanVisitorController", // 省略其他的 Bean "_links": { "self": { "href": "http://localhost:8080/managed/beans"

    我们在定义接口返回值时,通过 spring-hateoas 来生成 RESTful 风格的 API 接口,更多应用细节可以参考之前的文章# Spring Boot「01」构建 REST API;

    02-Spring Actuator

    Spring Boot 提供了开箱即用的应用监控、检测工具 Actuator,主要用来查看应用的各种信息,例如应用健康状态、各类指标、日志、转储、环境等其他信息。 而且 Actuator 提供了基于 HTTP API 或 JMX Bean 的交互方式,开发者可以非常容易的获取这些信息。

    Spring Actuator 优点类似于上面我们通过 HTTP API 提供的查看接口,只不过共能更完善,也更丰富。 接下来,让我们来体验下如何使用 Spring Actuator 吧。

    默认情况下,Spring Actuator 中的 Endpoint 是不开启的,仅开启了 /health。 通过 HTTP 访问 Actuator 的方式与上节中的方式类似,通过浏览器访问 http://localhost:8080/actuator/,可以查看当前开启的 Endpoint,例如:

    "_links": { "self": { "href": "http://localhost:8080/actuator", "templated": false "health": { "href": "http://localhost:8080/actuator/health", "templated": false "health-path": { "href": "http://localhost:8080/actuator/health/{*path}", "templated": true

    Actuator 中包含了一个 /beans Endpoint,与上节中我们实现的 Controller 功能类似。 接下来,我们以此为例,看一下如何在项目中开启这个 Endpoint。

    首先,可以通过management.endpoints.web.exposure.include=health,info,env,beans来激活 /health | /info | /env | /beans 这四个 Endpoint。 之后访问 http://localhost:8080/actuator/ 就能看到开启的 Endpoint 发生了变化:

    "_links": { "self": { "href": "http://localhost:8080/actuator", "templated": false "beans": { "href": "http://localhost:8080/actuator/beans", "templated": false "health": { "href": "http://localhost:8080/actuator/health", "templated": false "health-path": { "href": "http://localhost:8080/actuator/health/{*path}", "templated": true "info": { "href": "http://localhost:8080/actuator/info", "templated": false "env": { "href": "http://localhost:8080/actuator/env", "templated": false "env-toMatch": { "href": "http://localhost:8080/actuator/env/{toMatch}", "templated": true

    当我们访问 http://localhost:8080/actuator/beans 时,同样也会返回容器中所有的 Bean 信息,只不过比我们之前自己实现的更丰富:

    "listAllManagedBeanApplication": {
      "aliases": [],
      "scope": "singleton",
      "type": "self.samson.example.core.ListAllManagedBeanApplication$$EnhancerBySpringCGLIB$$e6d36e5c",
      "resource": null,
      "dependencies": []
    "managedBeanVisitorController": {
      "aliases": [],
      "scope": "singleton",
      "type": "self.samson.example.core.ManagedBeanVisitorController",
      "resource": "file [ManagedBeanVisitorController.class]",
      "dependencies": []
    

    其他的 Endpoint 示例不再深入讨论,更多关于 Actuator 信息可以参考官方文档1

    03-总结

    今天我们通过两个示例展示了如何获得 Spring Boot IoC 容器中的所有 Bean。 在开发阶段,如果仅出于易于调试的目的,可以通过 ApplicationContext 中的 getBeanDefinitionNames 来获得所有的 Bean 名称。 但如果在生产环境,如果有监控应用的必要,还是推荐使用 Spring Actuator,获得的信息更丰富,与应用的耦合程度也更低。

     
    推荐文章