FatJar技术
概念
将一个jar及其依赖的三方jar全部打到一个包中,这个包即为FatJar。
作用
作用: Jar包隔离,避免Jar冲突。
打包方式
- maven-shade-plugin插件;
- spring-boot-maven-plugin插件(Spring Boot打包插件);
嵌套Jar资源加载方案
思路:扩展Jar URL协议+定制ClassLoader;
扩展Jar URL协议
问题: JDK内置的Jar URL协议只支持一个’!/’,需要扩展此协议使其支持多个’!/’,以便能够加载jar in jar的资源,如下所示:
jar:file:/data/spring-boot-theory/BOOT-INF/lib/spring-aop-5.0.4.RELEASE.jar!/org/springframework/aop/SpringProxy.class
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 ,实现如下所示:
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.loader;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.Enumeration;
import java.util.jar.JarFile;
import org.springframework.boot.loader.jar.Handler;
/**
* {@link ClassLoader} used by the {@link Launcher}.
*
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
*/
public class LaunchedURLClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
/**
* Create a new {@link LaunchedURLClassLoader} instance.
* @param urls the URLs from which to load classes and resources
* @param parent the parent class loader for delegation
*/
public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
@Override
public URL findResource(String name) {
Handler.setUseFastConnectionExceptions(true);
try {
return super.findResource(name);
}
finally {
Handler.setUseFastConnectionExceptions(false);
}
}
@Override
public Enumeration<URL> findResources(String name) throws IOException {
Handler.setUseFastConnectionExceptions(true);
try {
return new UseFastConnectionExceptionsEnumeration(super.findResources(name));
}
finally {
Handler.setUseFastConnectionExceptions(false);
}
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Handler.setUseFastConnectionExceptions(true);
try {
try {
definePackageIfNecessary(name);
}
catch (IllegalArgumentException ex) {
// Tolerate race condition due to being parallel capable
if (getPackage(name) == null) {
// This should never happen as the IllegalArgumentException indicates
// that the package has already been defined and, therefore,
// getPackage(name) should not return null.
throw new AssertionError("Package " + name + " has already been "
+ "defined but it could not be found");
}
}
return super.loadClass(name, resolve);
}
finally {
Handler.setUseFastConnectionExceptions(false);
}
}
/**
* Define a package before a {@code findClass} call is made. This is necessary to
* ensure that the appropriate manifest for nested JARs is associated with the
* package.
* @param className the class name being found
*/
private void definePackageIfNecessary(String className) {
int lastDot = className.lastIndexOf('.');
if (lastDot >= 0) {
String packageName = className.substring(0, lastDot);
if (getPackage(packageName) == null) {
try {
definePackage(className, packageName);
}
catch (IllegalArgumentException ex) {
// Tolerate race condition due to being parallel capable
if (getPackage(packageName) == null) {
// This should never happen as the IllegalArgumentException
// indicates that the package has already been defined and,
// therefore, getPackage(name) should not have returned null.
throw new AssertionError(
"Package " + packageName + " has already been defined "
+ "but it could not be found");
}
}
}
}
}
private void definePackage(String className, String packageName) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
String packageEntryName = packageName.replace('.', '/') + "/";
String classEntryName = className.replace('.', '/') + ".class";
for (URL url : getURLs()) {
try {
URLConnection connection = url.openConnection();
if (connection instanceof JarURLConnection) {
JarFile jarFile = ((JarURLConnection) connection)
.getJarFile();
if (jarFile.getEntry(classEntryName) != null
&& jarFile.getEntry(packageEntryName) != null
&& jarFile.getManifest() != null) {
definePackage(packageName, jarFile.getManifest(), url);
return null;
}
}
}
catch (IOException ex) {
// Ignore
}
}
return null;
}, AccessController.getContext());
}
catch (java.security.PrivilegedActionException ex) {
// Ignore
}
}
/**
* Clear URL caches.
*/
public void clearCache() {
for (URL url : getURLs()) {
try {
URLConnection connection = url.openConnection();
if (connection instanceof JarURLConnection) {
clearCache(connection);
}
}
catch (IOException ex) {
// Ignore
}
}
}
private void clearCache(URLConnection connection) throws IOException {
Object jarFile = ((JarURLConnection) connection).getJarFile();
if (jarFile instanceof org.springframework.boot.loader.jar.JarFile) {
((org.springframework.boot.loader.jar.JarFile) jarFile).clearCache();
}
}
private static class UseFastConnectionExceptionsEnumeration
implements Enumeration<URL> {
private final Enumeration<URL> delegate;
UseFastConnectionExceptionsEnumeration(Enumeration<URL> delegate) {
this.delegate = delegate;
}
@Override
public boolean hasMoreElements() {
Handler.setUseFastConnectionExceptions(true);
try {
return this.delegate.hasMoreElements();
}
finally {
Handler.setUseFastConnectionExceptions(false);
}
}
@Override
public URL nextElement() {
Handler.setUseFastConnectionExceptions(true);
try {
return this.delegate.nextElement();
}
finally {
Handler.setUseFastConnectionExceptions(false);
}
}
}
}
加载步骤
- 注册定制Handler;
- 获取当前Jar包及其嵌套Jar包URL;
- 创建ClassLoader,进行资源加载;
使用示例代码,如下所示:
import java.net.URL;
import java.util.List;
import com.cainiao.iots.client.utils.loader.ExecutableArchiveLauncher;
import com.cainiao.iots.client.utils.loader.archive.Archive;
import com.cainiao.iots.client.utils.loader.jar.JarFile;
public class IotClientLauncher extends ExecutableArchiveLauncher {
static final String BOOT_INF_LIB = "sar/jars/";
private ClassLoader classLoader;
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
return entry.getName().startsWith(BOOT_INF_LIB);
}
@Override
protected void launch(String[] args) throws Exception {
//step1:注册handler
JarFile.registerUrlProtocolHandler();
//step2:获取当前Jar包及其嵌套Jar包URL
List<Archive> archives = getClassPathArchives();
for(int i = 0; i < archives.size(); i++){
System.out.println("Archive url: " + archives.get(i).getUrl());
}
//step3:创建ClassLoader
this.classLoader = createClassLoader(archives);
}
public ClassLoader getClassLoader() {
return classLoader;
}
public static void main(String[] args) throws Exception {
//1. 创建ClassLoader
IotClientLauncher launcher = new IotClientLauncher();
launcher.launch(args);
ClassLoader loader = launcher.getClassLoader();
//2. 加载jar in jar的资源
URL url = loader.getResource("1.jpg");
Class<?> clazz = loader.loadClass("*.*.*");
}
}
参考:
原文地址:https://blog.csdn.net/yangguosb/article/details/80764971
FatJar技术的更多相关文章
- 阿里Java架构师打包 FatJar 方法小结
在函数计算(Aliyun FC)中发布一个 Java 函数,往往需要将函数打包成一个 all-in-one 的 zip 包或者 jar 包.Java 中这种打包 all-in-one 的技术常称之为 ...
- JAVA核心知识点--打包 FatJar 方法小结
目录 什么是 FatJar 三种打包方法 1. 非遮蔽方法(Unshaded) 2. 遮蔽方法(Shaded) 3. 嵌套方法(Jar of Jars) 小结 参考阅读 原文地址:https://yq ...
- SpringBoot究竟是如何跑起来的?
摘要: 神奇的SpringBoot. 原文:SpringBoot 究竟是如何跑起来的? 作者:老钱 Fundebug经授权转载,版权归原作者所有. 不得不说 SpringBoot 太复杂了,我本来只想 ...
- 关于解决python线上问题的几种有效技术
工作后好久没上博客园了,虽然不是很忙,但也没学生时代闲了.今天上博客园,发现好多的文章都是年终总结,想想是不是自己也应该总结下,不过现在还没想好,等想好了再写吧.今天写写自己在工作后用到的技术干货,争 ...
- SQL Server技术内幕笔记合集
SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...
- 本人提供微软系.NET技术顾问服务,欢迎企业咨询!
背景: 1:目前微软系.NET技术高端人才缺少. 2:企业很难直接招到高端技术人才. 3:本人提供.NET技术顾问,保障你的产品或项目在正确的技术方向. 技术顾问服务 硬服务项: 1:提供技术.决策. ...
- 分布式锁1 Java常用技术方案
前言: 由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题.所以自己结合实际工作中的一些经验和网上看到的一些资 ...
- 【大型网站技术实践】初级篇:借助LVS+Keepalived实现负载均衡
一.负载均衡:必不可少的基础手段 1.1 找更多的牛来拉车吧 当前大多数的互联网系统都使用了服务器集群技术,集群即将相同服务部署在多台服务器上构成一个集群整体对外提供服务,这些集群可以是Web应用服务 ...
- 探真无阻塞加载javascript脚本技术,我们会发现很多意想不到的秘密
下面的图片是我使用firefox和chrome浏览百度首页时候记录的http请求 下面是firefox: 下面是chrome: 在浏览百度首页前我都将浏览器的缓存全部清理掉,让这个场景最接近第一次访问 ...
随机推荐
- 【机器学习PAI实战】—— 玩转人工智能之商品价格预测
摘要: 我们经常思考机器学习,深度学习,以至于人工智能给我们带来什么?在数据相对充足,足够真实的情况下,好的学习模型可以发现事件本身的内在规则,内在联系.我们去除冗余的信息,可以通过最少的特征构建最简 ...
- Spring_Aop_(二)
切面的优先级 @Order(1)注解 指定切面的优先级,值越小优先级越高 @Order(1) @Aspect @Component public class VlidationAspect { @Be ...
- day3-转自金角大王
本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...
- oracle编写分页过程
有了上面的基础,相信大家可以完成分页存储过程了,要求,请大家编写一个存储过程,要求可以输入表名.每页显示记录数.当前页.排序字段(deptno降序).返回总记录数,总页数和返回结果集. 把一个字符串, ...
- Timer和DispatcherTimer简单使用
1. 设定计时器相关属性,使用委托方法处理事件触发 DispatcherTimer dispatcherTimer= new DispatcherTimer(); dispatcherTimer.Ti ...
- [React Native]获取网络状态
使用React Native,可以使用NetInfo API获取手机当前的各个网络状态. componentWillMount() { NetInfo.fetch().done((status)=&g ...
- 小爬爬7:回顾&&crawlSpider
1.回顾昨日内容 回顾 - 全站数据爬取(分页) - 手动请求的发送Request(url,callback) - post请求和cookie处理 - start_requests(self) - F ...
- 洛谷3067 BZOJ 2679题解(折半搜索)
传送门 BZOJ传送门(权限题) 看到n小于20,就可以想到搜索 所有的数要么在集合a中,要么在集合b中,要么都不在 可是3^n复杂度会炸,我们考虑优化 可以利用折半搜索,将前面一半的所有可能情况与后 ...
- Python中的简单实现UDP协议没有粘包问题
服务端: import socket server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #数据报协议->udp server.bind ...
- @codeforces - 1153F@ Serval and Bonus Problem
目录 @description@ @solution@ @accepted code@ @details@ @description@ 从一条长度为 l 的线段中随机选择 n 条线段,共 2*n 个线 ...