Flink-1.5.0-源码分析系列2:buildProgram

下面我们来看看buildProgram是怎么实现的

stop in org.apache.flink.client.cli.CliFrontend.buildProgram

里面有1个细节,就是如果未指定entrypointClassName,就会自己找

Step completed: "thread=main", org.apache.flink.client.program.PackagedProgram.<init>(), line=190 bci=85
190    			entryPointClassName = getEntryPointClassNameFromJar(jarFileUrl);

main[1] step

我们看怎么找!

-----------------

		// if no entryPointClassName name was given, we try and look one up through the manifest
		if (entryPointClassName == null) {
			entryPointClassName = getEntryPointClassNameFromJar(jarFileUrl);
		}

进去看实现

获取manifest文件

// Read from jar manifest
			try {
				manifest = jar.getManifest();
			} catch (IOException ioex) {
				throw new ProgramInvocationException("The Manifest in the jar file could not be accessed '"
					+ jarFile.getPath() + "'. " + ioex.getMessage(), ioex);
			}
Attributes attributes = manifest.getMainAttributes();

			// check for a "program-class" entry first
			className = attributes.getValue(PackagedProgram.MANIFEST_ATTRIBUTE_ASSEMBLER_CLASS);
			if (className != null) {
				return className;
			}

			// check for a main class
			className = attributes.getValue(PackagedProgram.MANIFEST_ATTRIBUTE_MAIN_CLASS);
			if (className != null) {
				return className;
			} else {
				throw new ProgramInvocationException("Neither a '" + MANIFEST_ATTRIBUTE_MAIN_CLASS + "', nor a '" +
						MANIFEST_ATTRIBUTE_ASSEMBLER_CLASS + "' entry was found in the jar file.");
			}

先从manifest文件中看有没有,经过2次查找,有了

Step completed: "thread=main", org.apache.flink.client.program.PackagedProgram.getEntryPointClassNameFromJar(), line=592 bci=276
592    			if (className != null) {

main[1] print className
 className = "org.apache.flink.streaming.examples.kafka.Kafka010Example"

 

Manifest-Version: 1.0
Implementation-Title: flink-examples-streaming
Implementation-Version: 1.5.0
Archiver-Version: Plexus Archiver
Built-By: admin
Specification-Vendor: The Apache Software Foundation
Specification-Title: flink-examples-streaming
Implementation-Vendor-Id: org.apache.flink
Implementation-Vendor: The Apache Software Foundation
Main-Class: org.apache.flink.streaming.examples.kafka.Kafka010Example
Created-By: Apache Maven 3.0.5
Build-Jdk: 1.8.0_171
Specification-Version: 1.5.0

好,找到了!

然后会提取依赖包,如果这个大jar包里有的话,原理就是

public static List<File> extractContainedLibraries(URL jarFile) throws ProgramInvocationException {
	    //看到这里了//看到这里了//看到这里了
		Random rnd = new Random();
		//看到这里了
		JarFile jar = null;
		try {
			jar = new JarFile(new File(jarFile.toURI()));
			final List<JarEntry> containedJarFileEntries = new ArrayList<JarEntry>();
			//看到这里了
			Enumeration<JarEntry> entries = jar.entries();
			while (entries.hasMoreElements()) {
				JarEntry entry = entries.nextElement();
				String name = entry.getName();
				//看到这里了
				if (name.length() > 8 && name.startsWith("lib/") && name.endsWith(".jar")) {
					containedJarFileEntries.add(entry);
				}//看到这里了
			}

			if (containedJarFileEntries.isEmpty()) {
				return Collections.emptyList();
			}//看到这里了
			else {
				// go over all contained jar files
				final List<File> extractedTempLibraries = new ArrayList<File>(containedJarFileEntries.size());
				final byte[] buffer = new byte[4096];

				boolean incomplete = true;

				try {
					for (int i = 0; i < containedJarFileEntries.size(); i++) {
						final JarEntry entry = containedJarFileEntries.get(i);
						String name = entry.getName();
						name = name.replace(File.separatorChar, '_');

						File tempFile;
						try {
							tempFile = File.createTempFile(rnd.nextInt(Integer.MAX_VALUE) + "_", name);
							tempFile.deleteOnExit();
						}
						catch (IOException e) {
							throw new ProgramInvocationException(
								"An I/O error occurred while creating temporary file to extract nested library '" +
										entry.getName() + "'.", e);
						}

						extractedTempLibraries.add(tempFile);

						// copy the temp file contents to a temporary File
						OutputStream out = null;
						InputStream in = null;
						try {

							out = new FileOutputStream(tempFile);
							in = new BufferedInputStream(jar.getInputStream(entry));

							int numRead = 0;
							while ((numRead = in.read(buffer)) != -1) {
								out.write(buffer, 0, numRead);
							}
						}
						catch (IOException e) {
							throw new ProgramInvocationException("An I/O error occurred while extracting nested library '"
									+ entry.getName() + "' to temporary file '" + tempFile.getAbsolutePath() + "'.");
						}
						finally {
							if (out != null) {
								out.close();
							}
							if (in != null) {
								in.close();
							}
						}
					}

					incomplete = false;
				}
				finally {
					if (incomplete) {
						deleteExtractedLibraries(extractedTempLibraries);
					}
				}

				return extractedTempLibraries;
			}
		}
		catch (Throwable t) {
			throw new ProgramInvocationException("Unknown I/O error while extracting contained jar files.", t);
		}
		finally {
			if (jar != null) {
				try {
					jar.close();
				} catch (Throwable t) {}
			}
		}
	}

然后需要构建大一统的buildUserCodeClassLoader

原理就是

	public static ClassLoader buildUserCodeClassLoader(List<URL> jars, List<URL> classpaths, ClassLoader parent) {
		URL[] urls = new URL[jars.size() + classpaths.size()];//看到这里了
		for (int i = 0; i < jars.size(); i++) {
			urls[i] = jars.get(i);//填充jar包里的所有jar,包含主jar包本身及内部的提取jar包
		}
		for (int i = 0; i < classpaths.size(); i++) {
			urls[i + jars.size()] = classpaths.get(i);//包含参数指定的jar包
		}
		return FlinkUserCodeClassLoaders.parentFirst(urls, parent);
	}

注意这里有一个return FlinkUserCodeClassLoaders.parentFirst(urls, parent);,也就是本身的classLoader优先
===

	public static URLClassLoader parentFirst(URL[] urls, ClassLoader parent) {
		return new ParentFirstClassLoader(urls, parent);
	}

是不是超级简单,后面我们也可以用这种方式来使用了,很棒!!!

然后通过这个新的classLoader来加载主类

注意加载主类的细节,加载完后要还原的

private static Class<?> loadMainClass(String className, ClassLoader cl) throws ProgramInvocationException {
		ClassLoader contextCl = null;
		try {//看到这里了//看到这里了//看到这里了//看到这里了//看到这里了//看到这里了//看到这里了//看到这里了
			contextCl = Thread.currentThread().getContextClassLoader();
			Thread.currentThread().setContextClassLoader(cl);
			return Class.forName(className, false, cl);
		}
		catch (ClassNotFoundException e) {
			throw new ProgramInvocationException("The program's entry point class '" + className
				+ "' was not found in the jar file.", e);
		}
		catch (ExceptionInInitializerError e) {
			throw new ProgramInvocationException("The program's entry point class '" + className
				+ "' threw an error during initialization.", e);
		}
		catch (LinkageError e) {
			throw new ProgramInvocationException("The program's entry point class '" + className
				+ "' could not be loaded due to a linkage failure.", e);
		}
		catch (Throwable t) {
			throw new ProgramInvocationException("The program's entry point class '" + className
				+ "' caused an exception during initialization: " + t.getMessage(), t);
		} finally {//需要复原
			if (contextCl != null) {
				Thread.currentThread().setContextClassLoader(contextCl);
			}
		}
	}

好,然后判断这个主类是不是if (Program.class.isAssignableFrom(this.mainClass)) {

当前我们的卡夫卡例子不是这个类的实现类,所以走另外一个分支

然后判断有没有这个mainClass有main方法

} else if (hasMainMethod(mainClass)) {
			this.program = null;
		} else {
			throw new ProgramInvocationException("The given program class neither has a main(String[]) method, nor does it implement the " +
					Program.class.getName() + " interface.");
		}
private static boolean hasMainMethod(Class<?> entryClass) {
		Method mainMethod;
		try {//直接提取main方法
			mainMethod = entryClass.getMethod("main", String[].class);
		} catch (NoSuchMethodException e) {
			return false;
		}
		catch (Throwable t) {
			throw new RuntimeException("Could not look up the main(String[]) method from the class " +
					entryClass.getName() + ": " + t.getMessage(), t);
		}
		//确保是static public方法
		return Modifier.isStatic(mainMethod.getModifiers()) && Modifier.isPublic(mainMethod.getModifiers());
	}

 

转载于:https://my.oschina.net/qiangzigege/blog/2120205

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值