Lombok源码

目录儿

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

jar包结构

Lombok源码_第1张图片

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

找到资源文件
Lombok源码_第2张图片
里面的内容是这样的,每一行是一个类的全限定名

# 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包里面的类文件印证:
Lombok源码_第3张图片
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.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
Lombok源码_第4张图片
里面的类应该都是用作代理Eclipse工具的类
都知道Lombok是配合开发工具使用的,比如EclipseIDEA,因为它本身应该也要代理这些开发工具的某些参与编译相关的类吧
不过暂时没看到IDEA相关的代理

Handler

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

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

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

	@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;
		}
	}

复杂…有空再研究

你可能感兴趣的:(源码分析,java,单例模式,jvm,lombok,注解)