今天我们将一块学习一个小技巧,如何在 Spring Boot 应用中查看所有托管在容器中的 Bean?
在 Spring / Spring Boot 应用中,核心功能之一 IoC 容器,它负责所有托管 Bean 的生命周期管理。获得容器中所有托管 Bean 的方式主要有两种:
01-ListableBeanFactory 接口
Spring 中 ApplicationContext 接口继承了 ListableBeanFactory 接口。因此,所有的应用上下文都实现了 ListableBeanFactory 接口,具有该接口定义的所有方法实现,其中就包括我们要使用的:
String[] getBeanDefinitionNames();
复制代码
它能够返回容器中注册的所有 Bean 的名称。
有了这个接口,我们就可以在启动程序后在控制台打印所有 Bean 的名称:
@SpringBootApplicationpublic 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,获得的信息更丰富,与应用的耦合程度也更低。
评论