概念

  将一个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. CodePlus2017 12月月赛 div2火锅盛宴

    当时看到这道题感觉真是难过,我数据结构太弱啦. 我们来看看需要求什么: 1.当前熟了的食物的最小id 2.当前熟了的食物中有没有编号为id的食物 3.当前没熟的食物中有没有编号为id的食物 4.当前没 ...

  2. Linux之源码包

    暂时感觉用不上,到时需要了解的时候再补上

  3. GeoServer手动发布本地Shapefile地图

    首先,本文实现的结果图给大家展现一下: 放大的样子: 颜色是通过属性中某个字段值来分级的,可以自定义. 上面功能是用ArcGIS切片好数据,在Geoserver 中发布,并用google地图作为底图展 ...

  4. KiCad EDA 如何修改 Pcbnew 线路板的背景色?

    KiCad EDA 如何修改 Pcbnew 线路板的背景色? 关于背景色,传统的原理图是白色,线路板是黑色. EDA 软件 类型 颜色 Protel 原理图 浅黄色 Protel PCB 黑色 Orc ...

  5. Leetcode806.Number of Lines To Write String写字符串需要的行数

    我们要把给定的字符串 S 从左到右写到每一行上,每一行的最大宽度为100个单位,如果我们在写某个字母的时候会使这行超过了100 个单位,那么我们应该把这个字母写到下一行.我们给定了一个数组 width ...

  6. QT窗口无边框最前

    this->setWindowFlags(Qt::WindowStaysOnTopHint| Qt::CustomizeWindowHint| Qt::Tool| Qt::FramelessWi ...

  7. github.com访问慢解决

    修改hosts(HOSTS文件路径:C:\Windows\System32\drivers\etc\hosts) 1.打开Dns查询 - 站长工具  http://tool.chinaz.com/dn ...

  8. 块级元素及内联元素对margin、padding的态度

    1.块级元素 margin:跟标准一样,设置该块级元素边框与同级兄弟元素或者父元素的距离,俗称外边距. padding:先延伸边框(也就是优先改变元素尺寸而不动元素中内容的位置),当边框碰到父元素的边 ...

  9. 使用 Javascript 将二进制字符串转成数字

    使用 Javascript 将二进制字符串转成数字 Javascript 转成 数学太简单了. 原来 parseInt 还有这样的用法. function binaryAgent(str) { str ...

  10. iOS 11 适配UIWebView,页面下移20的问题

    方案1: AppDelegate文件 didFinishLaunchingWithOptions()中添加如下代码 if (@available(iOS 11.0, *)) { [[UIScrollV ...