首页 > 其他分享 >隐式扫描不到 Bean 的定义

隐式扫描不到 Bean 的定义

时间:2023-05-23 15:57:42浏览次数:36  
标签:HelloWorldController 扫描 隐式 public Bean ComponentScan com class

案例 :隐式扫描不到 Bean 的定义

https://www.java567.com,搜"java")

在构建 Web 服务时,我们常使用 Spring Boot 来快速构建。例如,使用下面的包结构和相关代码来完成一个简易的 Web 版 HelloWorld:

 

 

其中,负责启动程序的 Application 类定义如下:

 package com.spring.puzzle.class1.example1.application
 //省略 import
 @SpringBootApplication
 public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
 }
 ​

提供接口的 HelloWorldController 代码如下:

 package com.spring.puzzle.class1.example1.application
 //省略 import
 @RestController
 public class HelloWorldController {
    @RequestMapping(path = "hi", method = RequestMethod.GET)
    public String hi(){
          return "helloworld";
    };
 }
 ​

上述代码即可实现一个简单的功能:返回helloworld。两个关键类位于同一个包(即 application)中。其中 HelloWorldController 因为添加了@RestController,最终被识别成一个 Controller 的 Bean。

但是,假设有一天,当我们需要添加多个类似的 Controller,同时又希望用更清晰的包层次和结构来管理时,我们可能会去单独建立一个独立于 application 包之外的 Controller 包,并调整类的位置。调整后结构示意如下:

 

 

实际上,我们没有改变任何代码,只是改变了包的结构,但是我们会发现这个 Web 应用失效了,即不能识别出 HelloWorldController 了。也就是说,我们找不到 HelloWorldController 这个 Bean 了。这是为何?

案例解析

要了解 HelloWorldController 为什么会失效,就需要先了解之前是如何生效的。对于 Spring Boot 而言,关键点在于 Application.java 中使用了 SpringBootApplication 注解。而这个注解继承了另外一些注解,具体定义如下:

 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Inherited
 @SpringBootConfiguration
 @EnableAutoConfiguration
 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
 public @interface SpringBootApplication {
 //省略非关键代码
 }
 ​

从定义可以看出,SpringBootApplication开启了很多功能,其中一个关键功能就是 ComponentScan,参考其配置如下:

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)

当 Spring Boot 启动时,ComponentScan 的启用意味着会去扫描出所有定义的 Bean,那么扫描什么位置呢?这是由 ComponentScan 注解的 basePackages 属性指定的,具体可参考如下定义:

 public @interface ComponentScan {
 ​
 /**
  * Base packages to scan for annotated components.
  * <p>{@link #value} is an alias for (and mutually exclusive with) this
  * attribute.
  * <p>Use {@link #basePackageClasses} for a type-safe alternative to
  * String-based package names.
  */
 @AliasFor("value")
 String[] basePackages() default {};
 //省略其他非关键代码
 }
 ​

而在我们的案例中,我们直接使用的是 SpringBootApplication 注解定义的 ComponentScan,它的 basePackages 没有指定,所以默认为空(即{})。此时扫描的是什么包?这里不妨带着这个问题去调试下(调试位置参考 ComponentScanAnnotationParser#parse 方法),调试视图如下:

 

 

从上图可以看出,当 basePackages 为空时,扫描的包会是 declaringClass 所在的包,在本案例中,declaringClass 就是 Application.class,所以扫描的包其实就是它所在的包,即com.spring.puzzle.class1.example1.application。

对比我们重组包结构前后,我们自然就找到了这个问题的根源:在调整前,HelloWorldController 在扫描范围内,而调整后,它已经远离了扫描范围(不和 Application.java 一个包了),虽然代码没有一丝丝改变,但是这个功能已经失效了。

所以,综合来看,这个问题是因为我们不够了解 Spring Boot 的默认扫描规则引起的。我们仅仅享受了它的便捷,但是并未了解它背后的故事,所以稍作变化,就可能玩不转了。

问题修正

针对这个案例,有了源码的剖析,我们可以快速找到解决方案了。当然了,我们所谓的解决方案肯定不是说把 HelloWorldController 移动回原来的位置,而是 真正去满足需求。在这里,真正解决问题的方式是显式配置@ComponentScan。具体修改方式如下:

 @SpringBootApplication
 @ComponentScan("com.spring.puzzle.class1.example1.controller")
 public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
 }
 ​

通过上述修改,我们显式指定了扫描的范围为com.spring.puzzle.class1.example1.controller。不过需要注意的是,显式指定后,默认的扫描范围(即com.spring.puzzle.class1.example1.application)就不会被添加进去了。另外,我们也可以使用@ComponentScans 来修复问题,使用方式如下:

@ComponentScans(value = { @ComponentScan(value = "com.spring.puzzle.class1.example1.controller") })

顾名思义,可以看出 ComponentScans 相比较 ComponentScan 多了一个s,支持多个包的扫描范围指定。

此时,细心的你可能会发现:如果对源码缺乏了解,很容易会顾此失彼。以 ComponentScan 为例,原有的代码扫描了默认包而忽略了其它包;而 一旦显式指定其它包,原来的默认扫描包就被忽略了。

https://www.java567.com,搜"java")

标签:HelloWorldController,扫描,隐式,public,Bean,ComponentScan,com,class
From: https://www.cnblogs.com/web-666/p/17425455.html

相关文章

  • 使用c#捕获usb扫描枪扫描二维码、条形码结果
    起因最近公司买了一些扫描枪,要做个展会门票扫描,门票格式为一个网址,生成方式是qr二维码以前没玩过这东西,尝试一下看看,结果发现扫描枪根本就是个纯输入设备,和键盘一个性质好吧,先不管这些,把扫描结果记录下来才是正经的,于是在网上搜了一下“c#winform无焦点监听键盘输入......
  • 使用c#捕获usb扫描枪扫描二维码、条形码结果(支持中文版)
    前因之前在18年写过一个使用c#捕获usb扫描枪扫描二维码、条形码结果,当时我是没有遇到需要使用中文的情况,因为二维码都是我自己控制生成的,如果遇到中文,我会提前进行编码编程unicode编码,所以,没有专门针对中文符号做支持但一年多以来,不少人询问,或者在博客下留言,提出了中文扫码支持的......
  • Bean Search 超级好用的搜索工具
    1、引入依赖<dependency><groupId>cn.zhxu</groupId><artifactId>bean-searcher-boot-starter</artifactId><version>4.1.2</version></dependency>2、定义实体类autoMapTo:若不指定别名,自动映射的表orderBy:排序字段,如果数据量大,不建......
  • Nas Docker 安装个人记账web项目:firefly_iii &beancount-gs
    NasDocker安装个人记账web项目:firefly_iii&beancount-gs1.经过搜索以及GPT的询问,通过预览界面感觉firefly_iii官方示例demo:https://demo.firefly-iii.org/官方安装文档:https://docs.firefly-iii.org/firefly-iii/installation/docker/本人采用的是群晖Nasdocker安装:这个......
  • Field userClient in com.demo.order.service.OrderService required a bean of type'
    在SpringCloud项目中使用Feign进行远程调用遇到的错误。原因是因为UserClient在com.demo.feign.clients包下面,而order-service的@EnableFeignClientd注解却在com.demo.order包下面,这两个不在同一个包下,无法扫描到UserClient。解决方法有两种1.指定Feign应该扫描的包@EnableFeig......
  • Jmeter函数助手11-BeanShell
    BeanShell函数用于简单的计算或者运行编程脚本。表达式求值:填入脚本代码或脚本文件${__BeanShell(source(“test.bsh”))}存储结果的变量名(可选) 1、填入一个计算公式返回计算结果88/22=4,${__BeanShell(88/22,)} ......
  • Spring Boot |如何让你的 bean 在其他 bean 之前完成加载
    本文围绕SpringBoot中如何让你的bean在其他bean之前完成加载展开讨论。问题今天有个小伙伴给我出了一个难题:在SpringBoot中如何让自己的某个指定的Bean在其他Bean前完成被Spring加载?我听到这个问题的第一反应是,为什么会有这样奇怪的需求?Talkischeap,sho......
  • Spring Boot |如何让你的 bean 在其他 bean 之前完成加载
    本文围绕SpringBoot中如何让你的bean在其他bean之前完成加载展开讨论。问题今天有个小伙伴给我出了一个难题:在SpringBoot中如何让自己的某个指定的Bean在其他Bean前完成被Spring加载?我听到这个问题的第一反应是,为什么会有这样奇怪的需求?Talkischeap,sho......
  • spring bean的生命周期
    1.SpringBean的生命周期简介Springbean的生命周期是指Bean在Spring(IoC)中从创建到销毁的整个过程。在Spring框架中,Bean的生命周期包括以下阶段:实例化:通过构造函数或工厂方法创建Bean实例。属性赋值:调用Bean实例的setter方法将属性值注入到Bean中。初始化:执行Bean实例......
  • 网站指纹扫描插件(WhatRuns、Wappalyzer)
    我使用的是Chrome浏览器,需要到应用商店搜索下载WhatRunsWappalyzer......