最近接手一套基于 SpringBoot 我的项目,对我的项目进行重构调整,将公共局部抽离成子项目。在实际的过程中,发现抽离之后的模板中组件并没有被初始化。于是将排查解决过程中收集到的计划及常识汇总分享给大家。
问题起因
问题的起因很简略,因多套零碎的 package 命名不统一。比方业务零碎的包命名为 com.abc.xx,而公共(common)局部的包命名为 com.efg.xx,引入公共 jar 包时默认是无奈初始化的。
对于 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 注解。
定义配置类,在配置类中指定要扫描的包门路:
@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。
通过本篇文章的脉络,咱们能够看到一种学习的形式,通过一个知识点或一个实战中的问题,能够逐渐将常识从点裁减到面,这样不仅能加大学习的范畴,也能构建更牢固的常识图谱。
<center> 程序新视界 :精彩和成长都不容错过 </center>