今天我们将一块学习一个小技巧,如何在 Spring Boot 应用中查看所有托管在容器中的 Bean?
在 Spring / Spring Boot 应用中,核心功能之一 IoC 容器,它负责所有托管 Bean 的生命周期管理。获得容器中所有托管 Bean 的方式主要有两种:
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 接口,更多应用细节可以参考之前的问题x;
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,获得的信息更丰富,与应用的耦合程度也更低。
评论