⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7406147685302616100 「鲨鱼辣椒吃辣椒」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

实现这个很简单,只需要完成下面两步:

  • 想办法将class加载到JVM
  • 使Spring扫描到class

为什么需要强调简单,个人感觉因为java中的各种框架大而全的框架以及面对对象的特性(主要继承)给我们造成了很大理解障碍,总感觉java框架源码很复杂晦涩难懂,其实是真的晦涩难懂哈哈。其实本质就这两步骤,没任何心理压力。

对于第一步,在《通过ClassLoader类加载器理解SpringBoot启动原理以及classpath》中我们从Java类加载原理讲到了SpringBoot启动以及类加载原理,那么此时就很容易想出下面三种方案了

  • 扩大-cp由AppClassLoader帮我们加载外部jar
  • 使用SpringBoot提供的启动参数loader.path实现
  • 自定义classloader实现加载指定位置的jar

现在我们设定需要加载的jar在/user/local/java/plugins目录下,分别用几种方案实现

方案1:扩大-cp由AppClassLoader帮我们加载外部jar

可以使用 classpath 指定类加载的路径,但 classpath 的生效是有条件的,其实都可以理解为两种启动方式了:

命令 classpath 生效 说明
java -cp .;lib/x.jar Test ✔ 运行 class
java -cp lib/x.jar -jar app.jar ✖ 运行 jar

使用class启动-cp参数可以生效,那么SpringBoot的Main-Class是JarLauncher所以此时启动命令要改成

java  -cp  /user/local/java/plugins org.springframework.boot.loader.JarLauncher
  • 优点:实现简单
  • 缺点:每个jar包启动命令都一样了,不好区分

方案2 使用SpringBoot扩展的启动参数,使用java -jar 配合Loader.path参数

因为Spring Boot 程序大多是打成 jar 包,使用 java -jar boot.jar 的方式启动 (此时 -cp 无效),可以使用 loader.path 指定类加载路径加载其他 jar,但 loader.path 生效是有条件的:

loader.path 实现了 classpath 的功能。

配置 Main-Class

为了使用 loader.path,需要把 jar 包的 Main-Class 配置为 PropertiesLauncher,在 build.gradle中如下配置,可参考 Using the PropertiesLauncher:

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 打包时排除scope为system的包 -->
<includeSystemScope>false</includeSystemScope>
<!-- jvm启动时通过-Dloader.path加载包,必须指定layout为ZIP,否则-Dloader.path无效!!! -->
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<finalName>Test</finalName>
</build>

其中layout可配置值如下:

  • JAR,即通常的可执行jarMain-Class: org.springframework.boot.loader.JarLauncher
  • WAR,即通常的可执行war,需要的servlet容器依赖位于WEB-INF/lib-providedMain-Class: org.springframework.boot.loader.WarLauncher
  • ZIP,即DIR,类似于JARMain-Class: org.springframework.boot.loader.PropertiesLauncher
  • MODULE,将所有的依赖库打包(scope为provided的除外),但是不打包Spring Boot的任何Launcher
  • NONE,将所有的依赖库打包,但是不打包Spring Boot的任何Launcher

无论启动类是 JarLauncher 或者 PropertiesLauncher,loader.path 引入的 jar 和 Spring Boot 中 lib/*.jar 都是使用类加载器 org.springframework.boot.loader.LaunchedURLClassLoader 进行加载,也就是说他们使用的是同一个类加载器。

注意到同一个程序,打包成不同类型时,PropertiesLauncher (20s) 比 JarLauncher (8s) 启动慢很多。

方案3 自定义类加载器,加载固定目录下的jar包或者读取环境变量路径

这种方式需要程序启动的某个节点,调用自定义类加载器去加载指定目录下的jar包,时间点不是很好控制。但这个定制化程度比较高。

//可以在这里调用自定义类加载
SpringApplication.run(SpringBootDemo.class, args);

方案4 通过设置-Xbootclasspath/a或者 Extention ClassLoader通过改变参数java.ext.dirs实现

会改变系统类加载器加载行为不安全不推荐!!!

加载后的类如何扫描进入Spring容器

对于业务jar包类,可以预先在SpringBoot中指定好扫描包路径,那么只需要插件包路径是com.demo开头即可,如下

@SpringBootApplication(scanBasePackages = {"com.demo"})
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}

如不符合包名前缀一致的情况,需要在jar包META-INF下的spring.factories指定好自动装配的类即可如mybatis-plus:

# Auto Configure
org.springframework.boot.env.EnvironmentPostProcessor=\
com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\
com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
文章目录
  1. 1. 方案1:扩大-cp由AppClassLoader帮我们加载外部jar
  2. 2. 方案2 使用SpringBoot扩展的启动参数,使用java -jar 配合Loader.path参数
  3. 3. 方案3 自定义类加载器,加载固定目录下的jar包或者读取环境变量路径
  4. 4. 方案4 通过设置-Xbootclasspath/a或者 Extention ClassLoader通过改变参数java.ext.dirs实现
  5. 5. 加载后的类如何扫描进入Spring容器