zoukankan      html  css  js  c++  java
  • SpringBoot扫描不到组件?给你提供几种方案

    最近接手一套基于SpringBoot项目,对项目进行重构调整,将公共部分抽离成子项目。在实践的过程中,发现抽离之后的模板中组件并没有被初始化。于是将排查解决过程中搜集到的方案及知识汇总分享给大家。

    问题原因

    问题的原因很简单,因多套系统的package命名不一致。比如业务系统的包命名为com.abc.xx,而公共(common)部分的包命名为com.efg.xx,引入公共jar包时默认是无法初始化的。

    springboot

    对于SpringBoot项目,我们知道扫描的路径从启动类所在包开始,扫描当前包及其子级包下的所有文件。上图如果启动类在com.abc包下,肯定是无法扫描到com.def包内的组件的。

    场景延伸

    SpringBoot的这个机制还延伸出另外两个场景。

    第一个场景是如果SpringBoot的启动类放的包路径靠下,那么在它上级目录中的组件是无法被扫描并初始化的。新手往往会因放错位置导致启动时异常。

    第二个场景是故意将一些不需要纳入SpringBoot容器的类放在其他包中,避免被SpringBoot容器加载。当然此时也可以使用ComponentScan来指定排除对应的包。

    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── com
    │   │   │       └── secbro
    │   │   │           ├── SpringBootMainApplication.java
    │   │   │           ├── controller
    │   │   │           │   └── DruidController.java
    │   │   │           ├── model
    │   │   │           │   └── Order.java
    │   │   │           └── service
    │   │   │               ├── OrderService.java
    │   │   │               └── impl
    │   │   │                   └── OrderServiceImpl.java
    
    

    上述项目结构中,如果将类直接放在com目录或com目录的其他子目录下,默认是不会被初始化的。

    通过@ComponentScan扫描

    回到正题,遇到类似不被初始化的情况,我们可以使用的最简单的方案就是手动指定扫描包路径。

    在启动类上的@SpringBootApplication注解内部集成了@ComponentScan注解。此时我们可以显示的指定扫描的包。

    @SpringBootApplication
    @ComponentScan({"com.abc.xx","com.def.xx"})
    public class SpringBootMainApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(SpringBootMainApplication.class, args);
    	}
    
    }
    

    此种用法一定要先包含本项目要扫描的路径“com.abc.xx”,然后再在后面添加上common项目要扫描的路径“com.def.xx”。

    如果其他项目不需要初始化common中的内容,则可不进行指定。

    自定义@Enable****注解

    上述方法虽然能够解决问题,但如果直接写包名,难免没有个统一的规范。此时可考虑使用@Enable类型的注解。

    了解SpringBoot机制的朋友都知道,最重要的一个注解便是@EnableAutoConfiguration。类似的,我们定义一个可以通过注解之后便可使用的Enable注解。

    springboot

    定义配置类,在配置类中指定要扫描的包路径:

    @Component
    @ComponentScan("com.def.xx")
    public class CommonConfig {
    }
    

    定义Enable注解类,并通过@Import导入配置类:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(CommonConfig.class)
    public @interface EnableCommon {
    }
    

    然后,在启动类中便可使用@EnableCommon此注解来指定实例化对应的package。

    @EnableCommon
    @SpringBootApplication
    public class SpringBootMainApplication {
    // ...
    }
    

    在此过程中需要注意的是CommonConfig是位于common项目当中的。如果CommonConfig直接可被SpringBoot扫描到,那也就不需要EnableCommon注解了。

    自定义starter

    我们使用SpringBoot之所以方便,得益于它的特性之一便是可以使用已经集成好的starter。同样,我们也可以自定义一套starter来达到自动化配置的效果。

    由于这种模式更适用于自动化集成某一个组件,并不太适合这里说的common公共项目。因此就不再代码演示,只说一下大概的思路。详细实例可参考我的新书《SpringBoot技术内幕:架构设计与实现原理》。

    定义starter首先需要依赖自动配置的组件,也就是pom文件中添加如下配置:

    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
      </dependency>
    </dependencies>
    

    然后再定义具体的服务(或初始化)类,比如HelloWorldService以及该服务类初始化的参数类HelloWorldProperties。通过@ConfigurationProperties注解可以将Application中对应的属性初始化到类的属性中。

    然后呢,再提供一个基于@ConditionalOnClass配置的HelloWorldAutoConfiguration类,指定当HelloWorldService存在于类路径时,便会进行初始化。

    最后一步,在META-INF目录下创建spring.factories,启动添加类似如下配置:

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.secbro.HelloWorldAutoConfiguration
    

    该类是为SpringBoot提供的扫描入口。

    此时,当其他项目需要该starter时,直接引入便可注入使用HelloWorldService类了。

    关于此处建议大家专门看一篇相关的实战文章,可以更好的理解。这里只提供了一个大概的思路。

    小结

    关于SpringBoot的@ComponentScan基本上已经可以满足需求了,第二种方案是基于@ComponentScan的改进方案。而第三种方案更多的是基于SpringBoot的核心原理来处理的。当然最好是避免同一个项目使用多个顶级package。

    通过本篇文章的脉络,我们可以看到一种学习的方式,通过一个知识点或一个实战中的问题,可以逐步将知识从点扩充到面,这样不仅能加大学习的范围,也能构建更牢固的知识图谱。


    程序新视界:精彩和成长都不容错过

    程序新视界-微信公众号

  • 相关阅读:
    [原创]二路归并排序针对数组的场景(C++版)
    [原创]装饰模式(java版)
    [原创]Java中Map根据值(value)进行排序实现
    [原创]适配器模式(java版)
    信了你的邪
    String和Date转换
    电商运营面试题
    springCloud发送请求多对象参数传递问题
    JS实现页面以年月日时分秒展示时间
    java三种注释以及参数涵义(转)
  • 原文地址:https://www.cnblogs.com/secbro/p/13836801.html
Copyright © 2011-2022 走看看