Lombok源码

news2024/11/27 14:40:00

目录儿

  • jar包结构
  • Main
  • SpiLoadUtil
    • findServices()
    • readServicesFromUrl()
    • META-INF/services/lombok.core.LombokApp
  • ShadowClassLoader
  • Agent
    • lombok.core.AgentLauncher
  • Handler

jar包结构

在这里插入图片描述

Main

这应该是Lombok的入口函数

class Main {
	private static ShadowClassLoader classLoader;
	
	// 这里对 ShadowClassLoader 用了单例模式
	static synchronized ClassLoader getShadowClassLoader() {
		if (classLoader == null) {
			classLoader = new ShadowClassLoader(Main.class.getClassLoader(), "lombok", null, Arrays.<String>asList(), Arrays.asList("lombok.patcher.Symbols"));
		}
		return classLoader;
	}
	
	// 先忽略
	static synchronized void prependClassLoader(ClassLoader loader) {
		getShadowClassLoader();
		classLoader.prependParent(loader);
	}
	
	public static void main(String[] args) throws Throwable {
		ClassLoader cl = getShadowClassLoader();         // 获取 ShadowClassLoader 实例
		Class<?> mc = cl.loadClass("lombok.core.Main");  // 加载指定类 "lombok.core.Main"
		try {
			// 反射调用 lombok.core.Main 的 main 方法
			mc.getMethod("main", String[].class).invoke(null, new Object[] {args}); 
		} catch (InvocationTargetException e) {
			throw e.getCause();
		}
	}
}

ok,看到这个Main函数的逻辑都是围绕着类加载相关的东西,关键在于这个ShadowClassLoader类加载器[跳转]

加载的这个lombok.core.Main可以在Jar包中找到:

Class<?> mc = cl.loadClass("lombok.core.Main");

在这里插入图片描述
看它源码:

public class Main {
	// ...

	public static void main(String[] args) throws IOException {
		// 首先是设置了当前线程的类加载器为加载这个 Main 类的加载器,
		// 在 lombok.launch.Main 中知道这个加载器就是 ShadowClassLoader 影子加载器 
		Thread.currentThread().setContextClassLoader(Main.class.getClassLoader());
		// 创建 Main 实例,关注它的传入参数
		int err = new Main(SpiLoadUtil.readAllFromIterator(
				SpiLoadUtil.findServices(LombokApp.class)), Arrays.asList(args)).go();
		if (err != 0) {
			System.exit(err);
		}
	}
	
	// ...
}

上面创建实例通过SpiLoadUtil.findServices()获取了一些参数,接下来就看看这个工具获取了啥

SpiLoadUtil

上层调用:SpiLoadUtil.findServices(LombokApp.class)

public class SpiLoadUtil {

	public static <C> Iterable<C> findServices(Class<C> target) throws IOException {
		// target = LombokApp.class
		// 第二参数是之前为当前线程设置的 ShadowClassLoader 影子类加载器 
		return findServices(target, Thread.currentThread().getContextClassLoader()); 
	}
	
}

继续往下找

findServices()

public static <C> Iterable<C> findServices(final Class<C> target, ClassLoader loader) throws IOException {
		// 没有传入类加载器时,就用系统默认的类加载器
		if (loader == null) loader = ClassLoader.getSystemClassLoader(); 
		// 获取 META-INF/services/lombok.core.LombokApp 这个资源文件
		Enumeration<URL> resources = loader.getResources("META-INF/services/" + target.getName());
		// 创建一个键值对容器,存储资源文件中的元素(类的全限定名)
		final Set<String> entries = new LinkedHashSet<String>();
		while (resources.hasMoreElements()) {
			URL url = resources.nextElement();
			readServicesFromUrl(entries, url);
		}
		
		final Iterator<String> names = entries.iterator();
		final ClassLoader fLoader = loader; // 固定类加载器
		return new Iterable<C> () {         // 遍历加载键值对容器中存储的类
			@Override public Iterator<C> iterator() {
				return new Iterator<C>() {
					@Override public boolean hasNext() {
						return names.hasNext();
					}
					
					@Override public C next() {
						try {
							// 反射创建实例
							return target.cast(Class.forName(names.next(), true, fLoader).getConstructor().newInstance());
						} catch (Exception e) {
							Throwable t = e;
							if (t instanceof InvocationTargetException) t = t.getCause();
							if (t instanceof RuntimeException) throw (RuntimeException) t;
							if (t instanceof Error) throw (Error) t;
							throw new RuntimeException(t);
						}
					}
					
					@Override public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}

接下来深入函数中的readServicesFromUrl(entries, url)和资源文件META-INF/services/lombok.core.LombokApp

readServicesFromUrl()

	private static void readServicesFromUrl(Collection<String> list, URL url) throws IOException {
		InputStream in = url.openStream();
		BufferedReader r = null;
		try {
			if (in == null) return;
			r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
			while (true) {
				String line = r.readLine();
				if (line == null) break;
				int idx = line.indexOf('#'); // 忽略注释
				if (idx != -1) line = line.substring(0, idx);
				line = line.trim();
				if (line.length() == 0) continue;
				list.add(line); // 存到集合中
			}
		} finally {
			try {
				if (r != null) r.close();
				if (in != null) in.close();
			} catch (Throwable ignore) {}
		}
	}

META-INF/services/lombok.core.LombokApp

找到资源文件
在这里插入图片描述
里面的内容是这样的,每一行是一个类的全限定名

# Generated by SpiProcessor
# Mon, 18 Apr 2022 04:23:46 +0200
lombok.bytecode.PoolConstantsApp
lombok.bytecode.PostCompilerApp
lombok.core.Main$LicenseApp
lombok.core.Main$VersionApp
lombok.core.PublicApiCreatorApp
lombok.core.configuration.ConfigurationApp
lombok.core.runtimeDependencies.CreateLombokRuntimeApp
lombok.delombok.DelombokApp
lombok.eclipse.agent.MavenEcjBootstrapApp
lombok.installer.Installer$CommandLineInstallerApp
lombok.installer.Installer$CommandLineUninstallerApp
lombok.installer.Installer$GraphicalInstallerApp

ShadowClassLoader

影子类加载器,继承了普通的类加载器,然后添加了自己的类加载规则

class ShadowClassLoader extends ClassLoader {
	// 全限定名
	private static final String SELF_NAME = "lombok/launch/ShadowClassLoader.class";
	// 类文件后缀
	private final String sclSuffix;
	// ...

	// 构造器
	ShadowClassLoader(ClassLoader source, // 父·类加载器
					  String sclSuffix,   // 类文件后缀
					  String selfBase,    // 类加载路径
					  List<String> parentExclusion, // 类加载排除名单
					  List<String> highlanders // 这个好像是一个类名单,用来确保类只加载一次
					  ) {//...}
}

根据ShadowClassLoader的说明:

The shadow classloader serves to completely hide almost all classes in a given jar file by using a different file ending. The shadow classloader also serves to link in a project as it is being developed (a ‘bin’ dir from an IDE for example).
Classes loaded by the shadowloader use “.SCL.sclSuffix” in addition to “.class”. In other words, most of the class files in a given jar end in this suffix, which serves to hide them from any tool that isn’t aware of the suffix (such as IDEs generating auto-complete dialogs, and javac’s classpath in general). Only shadowloader can actually load these classes.
———————————————————————————————————
意思是之所以要自己搞一个类加载器,是为了对外隐藏Lombok Jar包里面的类,只能通过这个影子类加载器才能加载到这些类,避免被其他工具或插件识别加载。具体的方式是把这些需要隐藏的类文件以.SCL.sclSuffix作为后缀而非常规的.class。因此在Lombok Jar包里面的类文件绝大部分都以.SCL.sclSuffix作为后缀名。(.sclSuffix通常是.lombok

可以从Lombok Jar包里面的类文件印证:
在这里插入图片描述
Main函数中,创建ShadowClassLoader实例的参数如下

classLoader = new ShadowClassLoader(Main.class.getClassLoader(), 
									"lombok", 
									null, 
									Arrays.<String>asList(), 
									Arrays.asList("lombok.patcher.Symbols"));

其中
Main.class.getClassLoader()这个类加载器就是加载Main函数的类加载器,也就是 Spring容器的类加载器
"lombok"指定的是加载的类文件的后缀
null这里没有指定类加载路径
Arrays.<String>asList()指定类加载的排除名单,这里名单为空
Arrays.asList("lombok.patcher.Symbols")这个是把lombok.patcher.Symbols这个类转成数组,而lombok.patcher.Symbols里面是某些类的名单,用来保证名单里面的类只加载一次

Agent

这个是个代理类,实际操作的是lombok.core.AgentLauncher这个类的runAgents()函数

final class Agent {
	public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable {
		runLauncher(agentArgs, instrumentation, true);
	}
	
	public static void premain(String agentArgs, Instrumentation instrumentation) throws Throwable {
		runLauncher(agentArgs, instrumentation, false);
	}
	
	private static void runLauncher(String agentArgs, Instrumentation instrumentation, boolean injected) throws Throwable {
		// 通过 lombok.launch.Main 拿到影子类加载器实例
		ClassLoader cl = Main.getShadowClassLoader(); 
		try {
			Class<?> c = cl.loadClass("lombok.core.AgentLauncher");
			Method m = c.getDeclaredMethod("runAgents", String.class, Instrumentation.class, boolean.class, Class.class);
			m.invoke(null, agentArgs, instrumentation, injected, Agent.class);
		} catch (InvocationTargetException e) {
			throw e.getCause();
		}
	}
}

看看这个 lombok.core.AgentLauncher 是何东西

lombok.core.AgentLauncher

public class AgentLauncher {
	// 有一个内部接口
	public interface AgentLaunchable {
		void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Exception;
	}
	
	public static void runAgents(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Throwable {
		// 是一个类全限定名集合,实际上只有一个元素:lombok.eclipse.agent.EclipsePatcher
		for (AgentInfo info : AGENTS) {
			try {
				Class<?> agentClass = Class.forName(info.className());
				// 反射创建实例
				AgentLaunchable agent = (AgentLaunchable) agentClass.getConstructor().newInstance();
				agent.runAgent(agentArgs, instrumentation, injected, launchingContext);
			} catch (Throwable t) {
				if (t instanceof InvocationTargetException) t = t.getCause();
				info.problem(t, instrumentation);
			}
		}
	}
}

lombok.eclipse.agent.EclipsePatcherLombok Jar中的一个类,貌似是用来做补丁的
可以看到在源码中看到lombok.eclipse.agent
在这里插入图片描述
里面的类应该都是用作代理Eclipse工具的类
都知道Lombok是配合开发工具使用的,比如EclipseIDEA,因为它本身应该也要代理这些开发工具的某些参与编译相关的类吧
不过暂时没看到IDEA相关的代理

Handler

javac包(编译相关)中有一个handlers的包,里面全是相关注解的处理类,这应该是Lombok的注解处理核心代码部分
在这里插入图片描述
HandleGetter 为案例分析

public class HandleGetter extends JavacAnnotationHandler<Getter> {
	// ...
}

其继承了JavacAnnotationHandler,重写了核心函数handler(),然后其他都是自己的处理逻辑
在这里插入图片描述
看处理函数,就是一些注解的判断啊,然后对不同的注解位置做不同的方法注入啊这些

	@Override public void handle(AnnotationValues<Getter> annotation, JCAnnotation ast, JavacNode annotationNode) {
		handleFlagUsage(annotationNode, ConfigurationKeys.GETTER_FLAG_USAGE, "@Getter");
		
		Collection<JavacNode> fields = annotationNode.upFromAnnotationToFields();
		deleteAnnotationIfNeccessary(annotationNode, Getter.class);
		deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel");
		JavacNode node = annotationNode.up();
		Getter annotationInstance = annotation.getInstance();
		AccessLevel level = annotationInstance.value(); // 注解值
		boolean lazy = annotationInstance.lazy();       // 是否懒加载
		if (lazy) handleFlagUsage(annotationNode, ConfigurationKeys.GETTER_LAZY_FLAG_USAGE, "@Getter(lazy=true)");
		
		if (level == AccessLevel.NONE) {
			if (lazy) annotationNode.addWarning("'lazy' does not work with AccessLevel.NONE.");
			return;
		}
		
		if (node == null) return;
		
		List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode);
		
		switch (node.getKind()) {
		case FIELD:
			// 为 Fields 创建 getter
			createGetterForFields(level, fields, annotationNode, true, lazy, onMethod);
			break;
		case TYPE:
			if (lazy) annotationNode.addError("'lazy' is not supported for @Getter on a type.");
			// 为 Type 创建 getter
			generateGetterForType(node, annotationNode, level, false, onMethod);
			break;
		}
	}

复杂…有空再研究

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/598174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3 使用的 Pinia

文章目录 一、Pinia API与Vuex s4 有很大不同二、使用步骤1.安装2.使用3、组件中应用案例 官网&#xff1a;https://pinia.web3doc.top/ 一、Pinia API与Vuex s4 有很大不同 没有 mutations。mutations 被认为是非常几长的。最初带来了 devtools 集成&#xff0c;但这不再是问…

成功解决windows下将.pyx文件编译成.pyd文件

在linux上正常跑通的算法&#xff0c;搬到windows下使用就报错了 原来是其中涉及到.pyx文件的编译 在linux下.pyx会被编译成.so的文件&#xff0c;但是在windows下是无法使用.so文件的 需要重新编译成windows下的.pyd格式的才可以直接将python模块成功导入到算法中 所以需要解…

vue3到vue2组件重构方法笔记

这两天的任务是把一批做好的vue3组件放在vue2项目中使用&#xff0c;将组合式api分散开有一些零散的技巧&#xff0c;所以写一篇转化笔记以供大家参考 先上vue3一个组件的示例代码 <template><div ref"GForms" :style"{background: props.background…

27 # node 基本概念

node 基本概念 1、node 是什么&#xff1f; node.js 是一个基于 chrome v8 引擎的 JavaScript 运行环境&#xff08;runtime&#xff09;&#xff0c;node 不是一门语言&#xff0c;是让 js 运行在后端的运行时&#xff0c; 并且不包括 JavaScript 全集&#xff0c;因为在服务…

Collections工具类(java)

Collections工具类 java.util.Collections; 是集合的工具类作用&#xff1a;Collections不是集合&#xff0c;而是集合的工具类 Collections常用的API 方法名称说明public static <T> boolean addAll(Collection<T> c,T... elements)批量添加元素public static …

MySQL秘籍:让你的表操作炉火纯青

&#x1f495;每个人都有自己的一生&#xff0c;不要和别人去比较。比较只会让你感到沮丧和不满足。关注自己的成长和进步&#xff0c;并享受属于自己的旅程。 &#x1f495; &#x1f43c;作者&#xff1a;不能再留遗憾了&#x1f43c; &#x1f386;专栏&#xff1a;MySQL学习…

【LeetCode】11,盛最多水的容器。 难度等级:中等。双指针解法值得深入学习。

文章目录 一、题目二、我的解法&#xff1a;双重for循环&#xff0c;超出时间限制三、最优解法&#xff1a;双指针从两侧开始遍历 【LeetCode】11&#xff0c;盛最多水的容器。 难度等级&#xff1a;中等。 一、题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#x…

小米、华为、海尔竞争中,全屋智能「崛起」2023

智能家居进入下半场&#xff0c;互联网大厂凭借着自身的流量红利收割了一部分市场份额&#xff1b;家电企业在向家装企业转型的过程中&#xff0c;也有其自带的流量优势和渠道优势&#xff1b;通信厂商借着“链接”优势&#xff0c;三大阵营在智能家居行业都各占鳌头。 作者|思…

NPM 制作命令行工具 - 进阶辅助库

一、简介 通过 NPM 制作命令行工具 - 入门案例 已经基本知道如何制作命令行工具了&#xff0c;现在就是内部命令处理。 如果不使用第三方插件辅助&#xff0c;那就只能对传入的参数进行一个一个判断处理&#xff0c;添加注释&#xff0c;这是很麻烦的&#xff0c;所以&#xf…

移动端开发之基础知识:视口、三倍图、移动端开发选择、移动端技术解决方案、移动端常见布局

移动端开发之流式布局 移动端基础浏览器现状手机屏幕现状移动端调试方法 视口布局视口视觉视口理想视口总结&#xff1a; meta视口标签标准的viewport设置 三倍图物理像素&物理像素比多倍图背景缩放 background-size背景图三倍图 多倍图切图 cutterman 移动端开发选择移动端…

基于JavaWeb的私人牙科诊所管理系统

目录 1、项目背景 2、项目目标 3、项目功能 4、系统架构 5、项目源码 6、论文目录&#xff08;16000字&#xff09; 1、项目背景 在当前社会医疗水平的高速发展下&#xff0c;口腔方面的医疗在社会上不断发展壮大。私人化牙科诊所呈现蓬勃发展的趋势&#xff0c;各方面的…

【JavaSE】方法的使用--05

目录 一、方法的概念与使用 1.1 什么是方法 1.2 方法的定义 1.3 方法调用的执行过程 1.4 实参和形参的关系&#xff08;重要&#xff09; 1.5 有无返回值的方法 二、方法的重载 2.1 方法重载的概念 2.2 方法重载的要求 2.3 方法签名 前言&#xff1a; 之前很久没写这…

python基础----01-----环境搭建

一 python介绍 1.1 Python 特点 Python 是完全面向对象的语言。函数、模块、数宁、宁符串都是对象&#xff0c;在 Python 中一切皆对象。完全支持继承、重载、多重继承。支持重载运算符&#xff0c;也支持泛型设计。Python 拥有一个强大的标准库&#xff0c;Python 语言的核心…

谷云科技受邀出席2023华南CIO大会-应用与数据集成专家

2023年6月10-11日&#xff0c;我们将于中国珠海国际会展中心迎来第6届 S-CIO 2023华南CIO大会暨信息技术交易会 。大会将邀请近1000位来自广东、广西、福建、海南等地的企业IT高管及行业专家深入探讨企业数字化运营的关键问题&#xff0c;以“ 千人论坛-生态展区-专业分论坛-华…

2023年成人高考标准拿证流程(入学前入学后)

很多小伙伴对成人高考“报名→学习→毕业”的流程还是很陌生哈&#xff0c;下面给大家整理了一份详细的报考流程和攻略。 大家可以收藏起来&#xff0c;仔细看看。 成考全流程—入学前 5月—8月 联系报名机构老师预报名&#xff0c;选定自己要报考院校专业&#xff0c;了解报…

36 KVM管理设备-配置虚拟串口

文章目录 36 KVM管理设备-配置虚拟串口36.1 概述36.2 操作步骤 36 KVM管理设备-配置虚拟串口 36.1 概述 在虚拟化环境下&#xff0c;由于管理和业务的需求&#xff0c;虚拟机与宿主机需要互相通信。但在云管理系统复杂的网络架构下&#xff0c;运行在管理平面的服务与运行在业…

Servlet简介和环境设置

目录 Servlet 简介 Servlet 环境设置 导入jar包 web.xml文件配置 WebServlet注解配置 web.xml文件的方式和WebServlet区别 Servlet 简介 Servlet 是运行在 Web 服务器或应用服务器上的程序&#xff0c;它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上…

对csv文件,又get了新的认知(二)

背景 最近在做数据处理时&#xff0c;发现别人给的 csv 文件用 txt 打开后&#xff0c;发现里面的所有字段都是带双引号&#xff0c;与自己之前见过的 csv 文件有点不一样&#xff0c;自己脑海里面隐约也见过 python 有相关的设置参数&#xff0c;于是就查看 python 官方文档中…

Linux 常用开发工具(上)(yum、vim)知识点+完整思维导图+实图例子+深入细节+通俗易懂建议收藏

绪论 耐心是一切聪明才智的基础。—— 柏拉图。本章进入到Linux下的一些常用的工具&#xff0c;这些工具能帮助我们去更好的使用Linux操作系统。 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 附&#xff1a;红色&#xff0c;部分为重点部分&a…

一个无标记点面捕头盔,如何实现高精度面部表情捕捉?

在影视、动画、 游戏、虚拟直播应用中 虚拟数字人 可以犹如真人般实时驱动 背后少不了面部捕捉技术 随着面部捕捉技术不断革新&#xff0c;从有标记点到无标记点发展&#xff0c;再到如今佩戴一个面捕头盔就可以轻松做到精准面捕。 广州虚拟动力多年沉淀经验&#xff0c;根…