目录儿
- 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.EclipsePatcherfor (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.EclipsePatcher
是 Lombok
Jar中的一个类,貌似是用来做补丁的
可以看到在源码中看到lombok.eclipse.agent
包
里面的类应该都是用作代理Eclipse
工具的类
都知道Lombok
是配合开发工具使用的,比如Eclipse
和IDEA
,因为它本身应该也要代理这些开发工具的某些参与编译相关的类吧
不过暂时没看到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 创建 gettercreateGetterForFields(level, fields, annotationNode, true, lazy, onMethod);break;case TYPE:if (lazy) annotationNode.addError("'lazy' is not supported for @Getter on a type.");// 为 Type 创建 gettergenerateGetterForType(node, annotationNode, level, false, onMethod);break;}}
复杂…有空再研究