概念

  将一个jar及其依赖的三方jar全部打到一个包中,这个包即为FatJar。

作用

  作用: Jar包隔离,避免Jar冲突。

打包方式

  1. maven-shade-plugin插件;
  2. spring-boot-maven-plugin插件(Spring Boot打包插件);

嵌套Jar资源加载方案

  思路:扩展Jar URL协议+定制ClassLoader;

扩展Jar URL协议

  问题: JDK内置的Jar URL协议只支持一个’!/’,需要扩展此协议使其支持多个’!/’,以便能够加载jar in jar的资源,如下所示:

  1. jar:file:/data/spring-boot-theory/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class
  1. jar:file:/data/spring-boot-theory.jar!/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class

  解决方案:定制协议处理器Handler,定制规则查看另一篇,SpringBoot实现如下图所示:

定制ClassLoader

  加载class:重载loadclass,添加对应的包路径;

  加载其它资源:使用URLClassLoader原有逻辑即可;

SpringBoot提供了LaunchedURLClassLoader ,实现如下所示:

  1. /*
  2. * Copyright 2012-2017 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.boot.loader;
  17. import java.io.IOException;
  18. import java.net.JarURLConnection;
  19. import java.net.URL;
  20. import java.net.URLClassLoader;
  21. import java.net.URLConnection;
  22. import java.security.AccessController;
  23. import java.security.PrivilegedExceptionAction;
  24. import java.util.Enumeration;
  25. import java.util.jar.JarFile;
  26. import org.springframework.boot.loader.jar.Handler;
  27. /**
  28. * {@link ClassLoader} used by the {@link Launcher}.
  29. *
  30. * @author Phillip Webb
  31. * @author Dave Syer
  32. * @author Andy Wilkinson
  33. */
  34. public class LaunchedURLClassLoader extends URLClassLoader {
  35. static {
  36. ClassLoader.registerAsParallelCapable();
  37. }
  38. /**
  39. * Create a new {@link LaunchedURLClassLoader} instance.
  40. * @param urls the URLs from which to load classes and resources
  41. * @param parent the parent class loader for delegation
  42. */
  43. public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
  44. super(urls, parent);
  45. }
  46. @Override
  47. public URL findResource(String name) {
  48. Handler.setUseFastConnectionExceptions(true);
  49. try {
  50. return super.findResource(name);
  51. }
  52. finally {
  53. Handler.setUseFastConnectionExceptions(false);
  54. }
  55. }
  56. @Override
  57. public Enumeration<URL> findResources(String name) throws IOException {
  58. Handler.setUseFastConnectionExceptions(true);
  59. try {
  60. return new UseFastConnectionExceptionsEnumeration(super.findResources(name));
  61. }
  62. finally {
  63. Handler.setUseFastConnectionExceptions(false);
  64. }
  65. }
  66. @Override
  67. protected Class<?> loadClass(String name, boolean resolve)
  68. throws ClassNotFoundException {
  69. Handler.setUseFastConnectionExceptions(true);
  70. try {
  71. try {
  72. definePackageIfNecessary(name);
  73. }
  74. catch (IllegalArgumentException ex) {
  75. // Tolerate race condition due to being parallel capable
  76. if (getPackage(name) == null) {
  77. // This should never happen as the IllegalArgumentException indicates
  78. // that the package has already been defined and, therefore,
  79. // getPackage(name) should not return null.
  80. throw new AssertionError("Package " + name + " has already been "
  81. + "defined but it could not be found");
  82. }
  83. }
  84. return super.loadClass(name, resolve);
  85. }
  86. finally {
  87. Handler.setUseFastConnectionExceptions(false);
  88. }
  89. }
  90. /**
  91. * Define a package before a {@code findClass} call is made. This is necessary to
  92. * ensure that the appropriate manifest for nested JARs is associated with the
  93. * package.
  94. * @param className the class name being found
  95. */
  96. private void definePackageIfNecessary(String className) {
  97. int lastDot = className.lastIndexOf('.');
  98. if (lastDot >= 0) {
  99. String packageName = className.substring(0, lastDot);
  100. if (getPackage(packageName) == null) {
  101. try {
  102. definePackage(className, packageName);
  103. }
  104. catch (IllegalArgumentException ex) {
  105. // Tolerate race condition due to being parallel capable
  106. if (getPackage(packageName) == null) {
  107. // This should never happen as the IllegalArgumentException
  108. // indicates that the package has already been defined and,
  109. // therefore, getPackage(name) should not have returned null.
  110. throw new AssertionError(
  111. "Package " + packageName + " has already been defined "
  112. + "but it could not be found");
  113. }
  114. }
  115. }
  116. }
  117. }
  118. private void definePackage(String className, String packageName) {
  119. try {
  120. AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
  121. String packageEntryName = packageName.replace('.', '/') + "/";
  122. String classEntryName = className.replace('.', '/') + ".class";
  123. for (URL url : getURLs()) {
  124. try {
  125. URLConnection connection = url.openConnection();
  126. if (connection instanceof JarURLConnection) {
  127. JarFile jarFile = ((JarURLConnection) connection)
  128. .getJarFile();
  129. if (jarFile.getEntry(classEntryName) != null
  130. && jarFile.getEntry(packageEntryName) != null
  131. && jarFile.getManifest() != null) {
  132. definePackage(packageName, jarFile.getManifest(), url);
  133. return null;
  134. }
  135. }
  136. }
  137. catch (IOException ex) {
  138. // Ignore
  139. }
  140. }
  141. return null;
  142. }, AccessController.getContext());
  143. }
  144. catch (java.security.PrivilegedActionException ex) {
  145. // Ignore
  146. }
  147. }
  148. /**
  149. * Clear URL caches.
  150. */
  151. public void clearCache() {
  152. for (URL url : getURLs()) {
  153. try {
  154. URLConnection connection = url.openConnection();
  155. if (connection instanceof JarURLConnection) {
  156. clearCache(connection);
  157. }
  158. }
  159. catch (IOException ex) {
  160. // Ignore
  161. }
  162. }
  163. }
  164. private void clearCache(URLConnection connection) throws IOException {
  165. Object jarFile = ((JarURLConnection) connection).getJarFile();
  166. if (jarFile instanceof org.springframework.boot.loader.jar.JarFile) {
  167. ((org.springframework.boot.loader.jar.JarFile) jarFile).clearCache();
  168. }
  169. }
  170. private static class UseFastConnectionExceptionsEnumeration
  171. implements Enumeration<URL> {
  172. private final Enumeration<URL> delegate;
  173. UseFastConnectionExceptionsEnumeration(Enumeration<URL> delegate) {
  174. this.delegate = delegate;
  175. }
  176. @Override
  177. public boolean hasMoreElements() {
  178. Handler.setUseFastConnectionExceptions(true);
  179. try {
  180. return this.delegate.hasMoreElements();
  181. }
  182. finally {
  183. Handler.setUseFastConnectionExceptions(false);
  184. }
  185. }
  186. @Override
  187. public URL nextElement() {
  188. Handler.setUseFastConnectionExceptions(true);
  189. try {
  190. return this.delegate.nextElement();
  191. }
  192. finally {
  193. Handler.setUseFastConnectionExceptions(false);
  194. }
  195. }
  196. }
  197. }

加载步骤

  1. 注册定制Handler;
  2. 获取当前Jar包及其嵌套Jar包URL;
  3. 创建ClassLoader,进行资源加载;

使用示例代码,如下所示:

  1. import java.net.URL;
  2. import java.util.List;
  3. import com.cainiao.iots.client.utils.loader.ExecutableArchiveLauncher;
  4. import com.cainiao.iots.client.utils.loader.archive.Archive;
  5. import com.cainiao.iots.client.utils.loader.jar.JarFile;
  6. public class IotClientLauncher extends ExecutableArchiveLauncher {
  7. static final String BOOT_INF_LIB = "sar/jars/";
  8. private ClassLoader classLoader;
  9. @Override
  10. protected boolean isNestedArchive(Archive.Entry entry) {
  11. return entry.getName().startsWith(BOOT_INF_LIB);
  12. }
  13. @Override
  14. protected void launch(String[] args) throws Exception {
  15. //step1:注册handler
  16. JarFile.registerUrlProtocolHandler();
  17. //step2:获取当前Jar包及其嵌套Jar包URL
  18. List<Archive> archives = getClassPathArchives();
  19. for(int i = 0; i < archives.size(); i++){
  20. System.out.println("Archive url: " + archives.get(i).getUrl());
  21. }
  22. //step3:创建ClassLoader
  23. this.classLoader = createClassLoader(archives);
  24. }
  25. public ClassLoader getClassLoader() {
  26. return classLoader;
  27. }
  28. public static void main(String[] args) throws Exception {
  29. //1. 创建ClassLoader
  30. IotClientLauncher launcher = new IotClientLauncher();
  31. launcher.launch(args);
  32. ClassLoader loader = launcher.getClassLoader();
  33. //2. 加载jar in jar的资源
  34. URL url = loader.getResource("1.jpg");
  35. Class<?> clazz = loader.loadClass("*.*.*");
  36. }
  37. }

参考:

  1. https://segmentfault.com/a/1190000013532009

原文地址:https://blog.csdn.net/yangguosb/article/details/80764971

FatJar技术的更多相关文章

  1. 阿里Java架构师打包 FatJar 方法小结

    在函数计算(Aliyun FC)中发布一个 Java 函数,往往需要将函数打包成一个 all-in-one 的 zip 包或者 jar 包.Java 中这种打包 all-in-one 的技术常称之为 ...

  2. JAVA核心知识点--打包 FatJar 方法小结

    目录 什么是 FatJar 三种打包方法 1. 非遮蔽方法(Unshaded) 2. 遮蔽方法(Shaded) 3. 嵌套方法(Jar of Jars) 小结 参考阅读 原文地址:https://yq ...

  3. SpringBoot究竟是如何跑起来的?

    摘要: 神奇的SpringBoot. 原文:SpringBoot 究竟是如何跑起来的? 作者:老钱 Fundebug经授权转载,版权归原作者所有. 不得不说 SpringBoot 太复杂了,我本来只想 ...

  4. 关于解决python线上问题的几种有效技术

    工作后好久没上博客园了,虽然不是很忙,但也没学生时代闲了.今天上博客园,发现好多的文章都是年终总结,想想是不是自己也应该总结下,不过现在还没想好,等想好了再写吧.今天写写自己在工作后用到的技术干货,争 ...

  5. SQL Server技术内幕笔记合集

    SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...

  6. 本人提供微软系.NET技术顾问服务,欢迎企业咨询!

    背景: 1:目前微软系.NET技术高端人才缺少. 2:企业很难直接招到高端技术人才. 3:本人提供.NET技术顾问,保障你的产品或项目在正确的技术方向. 技术顾问服务 硬服务项: 1:提供技术.决策. ...

  7. 分布式锁1 Java常用技术方案

    前言:       由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题.所以自己结合实际工作中的一些经验和网上看到的一些资 ...

  8. 【大型网站技术实践】初级篇:借助LVS+Keepalived实现负载均衡

    一.负载均衡:必不可少的基础手段 1.1 找更多的牛来拉车吧 当前大多数的互联网系统都使用了服务器集群技术,集群即将相同服务部署在多台服务器上构成一个集群整体对外提供服务,这些集群可以是Web应用服务 ...

  9. 探真无阻塞加载javascript脚本技术,我们会发现很多意想不到的秘密

    下面的图片是我使用firefox和chrome浏览百度首页时候记录的http请求 下面是firefox: 下面是chrome: 在浏览百度首页前我都将浏览器的缓存全部清理掉,让这个场景最接近第一次访问 ...

随机推荐

  1. ajax请求与form表单提交共存的时候status为canceled

    chrome浏览器调试,发现,status竟然是canceled状态 网上总论: 1.在URL变更后,会对当前正在执行的ajax进求进行中止操作.中止后该请求的状态码将为canceled 2.在使用到 ...

  2. oracle有哪些审计项

    ACTION NAME 0 UNKNOWN 1 CREATE TABLE 2 INSERT 3 SELECT 4 CREATE CLUSTER 5 ALTER CLUSTER 6 UPDATE 7 D ...

  3. openstack安装dashboard后访问horizon出错 500 or 504

    访问controller/horizon出错500:internal server error 访问controller/horizon出错504:internal server error  gat ...

  4. Python之路,Day1 - Python基础1 --转自金角大王

    本节内容 Python介绍 发展史 Python 2 or 3? 安装 Hello World程序 变量 用户输入 模块初识 .pyc是个什么鬼? 数据类型初识 数据运算 表达式if ...else语 ...

  5. Person Re-identification 系列论文笔记(一):Scalable Person Re-identification: A Benchmark

    打算整理一个关于Person Re-identification的系列论文笔记,主要记录近年CNN快速发展中的部分有亮点和借鉴意义的论文. 论文笔记流程采用contributions->algo ...

  6. React Native-组件的引用

    之前文章中,我们使用了许多React Native组件,也定义了一些组件.但是我们都没有定义组件的标识,我们都是通过回调方法处理组件对应的事件,这种情况能满足绝大多数需求,有些情况我们需要对组件进行操 ...

  7. iOS打包上传ipa文件时,报错<ERROR ITMS-90096: "Your binary is not optimized for iPhone 5 - New iPhone apps......>的解决方案

    很长一段时间习惯了用企业级证书发布,最近的新项目使用Xcode 9.1发布到AppStore时遇到了一个小问题(emm..其实问题跟Xcode版本没关系,我也不知道为什么要声明这个233),如下: E ...

  8. uni-app禁止滑动穿透

    <view class="topWrapper" v-show="chooseShow" @click="chooseShow = false& ...

  9. uva 10566 Crossed Ladders (二分)

    http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&p ...

  10. PHP实现购物车的思路和源码分析

    正文内容 这里主要是记录下自己的购物车的思路,具体功能实现,但是尚未在实际项目中用到,不对之处欢迎指正 项目中需要添加购物车. 目录说明 buy.php 点击购买之后的操作 car.php 购物车,显 ...