Flink源码剖析:flink-annotations


本文将详细介绍下flink中的自定义注解模块,了解flink注解的作用与使用,使得后面阅读源码时看到这几个注解不至于很陌生。主要围绕flink源码中的 flink-annotations模块展开。与docs 相关的注解有@ConfigGroup、@ConfigGroups , 通常作用于配置类上,@Documentation.OverrideDefault、@Documentation.CommonOption、@Documentation.TableOption、@Documentation.ExcludeFromDocumentation, 作用于配置类的 ConfigOption 字段上,对配置项做一些修改。另外,还有其他5种标记注解,@Experimental、@Internal、@Public、@PublicEvolving、@VisibleForTesting。
在这里插入图片描述

图1: flink中的自定义注解

1. docs相关注解

1.1 @ConfigGroup

指定一组配置选项,组的名称将用作生成 HTML 文件名,keyPrefix 用于匹配配置项名称前缀。
如 @ConfigGroup(name = “firstGroup”, keyPrefix = “first”),生成的 HTML 文件名为 firstGroup ,其中的配置项名称都是以 first 开头的。

@Target({})
@Internal
public @interface ConfigGroup {
	String name();
	String keyPrefix();
}

1.2 @ConfigGroups

允许一个配置类中的配置项可以按照配置项名称前缀分成不同的组,生成多个 HTML 文件。
如:
@ConfigGroups(groups = {
@ConfigGroup(name = “firstGroup”, keyPrefix = “first”),
@ConfigGroup(name = “secondGroup”, keyPrefix = “second”)})
可以从配置类生成 3 个 HTML 文件,分别为 firstGroup、secondGroup、default,具体可以接着往下看,下面会有示例说明。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Internal
public @interface ConfigGroups {
	ConfigGroup[] groups() default {};
}

下面通过一个示例来说明这两个注解的用途。
查看测试类 ConfigOptionsDocGeneratorTest 中应用到 @ConfigGroups 和 @ConfigGroup 的单测 testCreatingMultipleGroups

@Test
public void testCreatingMultipleGroups() {
	final List<Tuple2<ConfigGroup, String>> tables = ConfigOptionsDocGenerator.generateTablesForClass(
		TestConfigMultipleSubGroup.class);

	assertEquals(tables.size(), 3);
	final HashMap<String, String> tablesConverted = new HashMap<>();
	for (Tuple2<ConfigGroup, String> table : tables) {
		tablesConverted.put(table.f0 != null ? table.f0.name() : "default", table.f1);
	}
}

TestConfigMultipleSubGroup 类 mock 了一个配置项类:
@ConfigGroup(name = “firstGroup”, keyPrefix = “first”) 将 key 以 first 开头的 ConfigOption 归为 firstGroup,
@ConfigGroup(name = “secondGroup”, keyPrefix = “second”) 将 key 以 second 开头的 ConfigOption 归为 secondGroup。

@ConfigGroups(groups = {
		@ConfigGroup(name = "firstGroup", keyPrefix = "first"),
		@ConfigGroup(name = "secondGroup", keyPrefix = "second")})
static class TestConfigMultipleSubGroup {
	public static ConfigOption<Integer> firstOption = ConfigOptions
		.key("first.option.a")
		.defaultValue(2)
		.withDescription("This is example description for the first option.");

	public static ConfigOption<String> secondOption = ConfigOptions
		.key("second.option.a")
		.noDefaultValue()
		.withDescription("This is long example description for the second option.");

	public static ConfigOption<Integer> thirdOption = ConfigOptions
		.key("third.option.a")
		.defaultValue(2)
		.withDescription("This is example description for the third option.");

	public static ConfigOption<String> fourthOption = ConfigOptions
		.key("fourth.option.a")
		.noDefaultValue()
		.withDescription("This is long example description for the fourth option.");
}

我们再看下 ConfigOptionsDocGenerator.generateTablesForClass(Class<?> optionsClass)

@VisibleForTesting
static List<Tuple2<ConfigGroup, String>> generateTablesForClass(Class<?> optionsClass) {
	// 获取 optionsClass 类上定义的 @ConfigGroups
	ConfigGroups configGroups = optionsClass.getAnnotation(ConfigGroups.class);
	// 抽取 optionsClass 中的所有 ConfigOption 配置项
	List<OptionWithMetaInfo> allOptions = extractConfigOptions(optionsClass);

	// 遍历 @ConfigGroups 注解中的 ConfigGroup[] groups()
	List<Tuple2<ConfigGroup, String>> tables;
	if (configGroups != null) {
		// 解析 optionsClass 上的 ConfigGroup 注解,即是有分组的。另外一个是默认的 ConfigGroup
		tables = new ArrayList<>(configGroups.groups().length + 1);
		Tree tree = new Tree(configGroups.groups(), allOptions);

		for (ConfigGroup group : configGroups.groups()) {
			List<OptionWithMetaInfo> configOptions = tree.findConfigOptions(group);
			// 按照 ConfigOption 的 key 进行排序
 			sortOptions(configOptions);
			tables.add(Tuple2.of(group, toHtmlTable(configOptions)));
		}

		// 所有 @ConfigGroup 前缀都匹配不上的其他 ConfigOption 归为 default 组
		List<OptionWithMetaInfo> configOptions = tree.getDefaultOptions();
		sortOptions(configOptions);
		tables.add(Tuple2.of(null, toHtmlTable(configOptions)));
	} else {
		sortOptions(allOptions);
		tables = Collections.singletonList(Tuple2.of(null, toHtmlTable(allOptions)));
	}
	return tables;
}

运行单测 testCreatingMultipleGroups 的输出结果如下:
firstGroup 配置项组里的配置项名称都是以 first 为前缀的。

<table class="table table-bordered">
    <thead>
        <tr>
            <th class="text-left" style="width: 20%">Key</th>
            <th class="text-left" style="width: 15%">Default</th>
            <th class="text-left" style="width: 10%">Type</th>
            <th class="text-left" style="width: 55%">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><h5>first.option.a</h5></td>
            <td style="word-wrap: break-word;">2</td>
            <td>Integer</td>
            <td>This is example description for the first option.</td>
        </tr>
    </tbody>
</table>

secondGroup 配置项组里的配置项名称都是以 second 为前缀的。

<table class="table table-bordered">
    <thead>
        <tr>
            <th class="text-left" style="width: 20%">Key</th>
            <th class="text-left" style="width: 15%">Default</th>
            <th class="text-left" style="width: 10%">Type</th>
            <th class="text-left" style="width: 55%">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><h5>second.option.a</h5></td>
            <td style="word-wrap: break-word;">(none)</td>
            <td>String</td>
            <td>This is long example description for the second option.</td>
        </tr>
    </tbody>
</table>

TestConfigMultipleSubGroup 中的其他配置项都是没有分组的,默认都放到 default 组中。

<table class="table table-bordered">
    <thead>
        <tr>
            <th class="text-left" style="width: 20%">Key</th>
            <th class="text-left" style="width: 15%">Default</th>
            <th class="text-left" style="width: 10%">Type</th>
            <th class="text-left" style="width: 55%">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><h5>fourth.option.a</h5></td>
            <td style="word-wrap: break-word;">(none)</td>
            <td>String</td>
            <td>This is long example description for the fourth option.</td>
        </tr>
        <tr>
            <td><h5>third.option.a</h5></td>
            <td style="word-wrap: break-word;">2</td>
            <td>Integer</td>
            <td>This is example description for the third option.</td>
        </tr>
    </tbody>
</table>

Documentation 类中定义了修改文档生成器行为的注解结合,包括 @OverrideDefault、@CommonOption、@TableOption、@ExcludeFromDocumentation。下面依次介绍。

1.3 @Documentation.OverrideDefault

作用在 ConfigOption 上的注解,覆盖其默认值。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Internal
public @interface OverrideDefault {
	String value();
}

下面通过一个示例来说明这个注解的用途。
查看测试类 ConfigOptionsDocGeneratorTest 中应用到 @Documentation.OverrideDefault 的单测 testOverrideDefault

@Test
public void testOverrideDefault() {
	String htmlTable = ConfigOptionsDocGenerator.generateTablesForClass(TestConfigGroupWithOverriddenDefault.class).get(0).f1;
}

TestConfigGroupWithOverriddenDefault 类 mock 了一个配置项类,每个配置项都使用了 @Documentation.OverrideDefault 注解覆盖配置项的默认值。

static class TestConfigGroupWithOverriddenDefault {
	@Documentation.OverrideDefault("default_1")
	public static ConfigOption<Integer> firstOption = ConfigOptions
		.key("first.option.a")
		.defaultValue(2)
		.withDescription("This is example description for the first option.");

	@Documentation.OverrideDefault("default_2")
	public static ConfigOption<String> secondOption = ConfigOptions
		.key("second.option.a")
		.noDefaultValue()
		.withDescription("This is long example description for the second option.");
}

运行单测 testOverrideDefault 的输出结果如下:
将 firstOption 的默认值覆盖成了 default_1,secondOption 原先没有默认值,被设置成了 default_2。

<table class="table table-bordered">
    <thead>
        <tr>
            <th class="text-left" style="width: 20%">Key</th>
            <th class="text-left" style="width: 15%">Default</th>
            <th class="text-left" style="width: 10%">Type</th>
            <th class="text-left" style="width: 55%">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><h5>first.option.a</h5></td>
            <td style="word-wrap: break-word;">default_1</td>
            <td>Integer</td>
            <td>This is example description for the first option.</td>
        </tr>
        <tr>
            <td><h5>second.option.a</h5></td>
            <td style="word-wrap: break-word;">default_2</td>
            <td>String</td>
            <td>This is long example description for the second option.</td>
        </tr>
    </tbody>
</table>

1.4 @Documentation.CommonOption

作用在 ConfigOption 上的注解,使其包含在 “Common Options” 片段中,
按 position 值排序,position 值小的配置项排在前面。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Internal
public @interface CommonOption {
	int POSITION_MEMORY = 10;
	int POSITION_PARALLELISM_SLOTS = 20;
	int POSITION_FAULT_TOLERANCE = 30;
	int POSITION_HIGH_AVAILABILITY = 40;
	int POSITION_SECURITY = 50;

	int position() default Integer.MAX_VALUE;
}

下面通过一个示例来说明这个注解的用途。
查看测试类 ConfigOptionsDocGeneratorTest 中应用到 @Documentation.CommonOption 的单测 testCommonOptions

@Test
public void testCommonOptions() throws IOException, ClassNotFoundException {
	final String projectRootDir = System.getProperty("rootDir");
	final String outputDirectory = TMP.newFolder().getAbsolutePath();

	final OptionsClassLocation[] locations = new OptionsClassLocation[] {
		new OptionsClassLocation("flink-docs", TestCommonOptions.class.getPackage().getName())
	};

	ConfigOptionsDocGenerator.generateCommonSection(projectRootDir, outputDirectory, locations, "src/test/java");
	Formatter formatter = new HtmlFormatter();
	String output = FileUtils.readFile(Paths.get(outputDirectory, ConfigOptionsDocGenerator.COMMON_SECTION_FILE_NAME).toFile(), StandardCharsets.UTF_8.name());

}

TestCommonOptions 类 mock 了一个配置项类:
COMMON_OPTION 使用了 @Documentation.CommonOption 注解,position 使用默认值为 Integer.MAX_VALUE,
COMMON_POSITIONED_OPTION 也是用了 @Documentation.CommonOption 注解,position 值指定为2,这个配置项肯定排在 COMMON_OPTION 前面。

public class TestCommonOptions {

	@Documentation.CommonOption
	public static final ConfigOption<Integer> COMMON_OPTION = ConfigOptions
		.key("first.option.a")
		.defaultValue(2)
		.withDescription("This is the description for the common option.");

	public static final ConfigOption<String> GENERIC_OPTION = ConfigOptions
		.key("second.option.a")
		.noDefaultValue()
		.withDescription("This is the description for the generic option.");

	@Documentation.CommonOption(position = 2)
	public static final ConfigOption<Integer> COMMON_POSITIONED_OPTION = ConfigOptions
		.key("third.option.a")
		.defaultValue(3)
		.withDescription("This is the description for the positioned common option.");
}

运行单测 testCommonOptions 的输出结果如下:

<table class="table table-bordered">
    <thead>
        <tr>
            <th class="text-left" style="width: 20%">Key</th>
            <th class="text-left" style="width: 15%">Default</th>
            <th class="text-left" style="width: 10%">Type</th>
            <th class="text-left" style="width: 55%">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><h5>third.option.a</h5></td>
            <td style="word-wrap: break-word;">3</td>
            <td>Integer</td>
            <td>This is the description for the positioned common option.</td>
        </tr>
        <tr>
            <td><h5>first.option.a</h5></td>
            <td style="word-wrap: break-word;">2</td>
            <td>Integer</td>
            <td>This is the description for the common option.</td>
        </tr>
    </tbody>
</table>

1.5 @Documentation.TableOption

作用于 table 配置项上,用于添加元数据标签,配置执行模式(批处理、流式处理、两者兼有)。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Internal
public @interface TableOption {
	ExecMode execMode();
}

我们看下 ConfigOptionsDocGenerator 类中的 toHtmlString 方法:

private static String toHtmlString(final OptionWithMetaInfo optionWithMetaInfo) {
	ConfigOption<?> option = optionWithMetaInfo.option;
	String defaultValue = stringifyDefault(optionWithMetaInfo);
	String type = typeToHtml(optionWithMetaInfo);
	Documentation.TableOption tableOption = optionWithMetaInfo.field.getAnnotation(Documentation.TableOption.class);
	StringBuilder execModeStringBuilder = new StringBuilder();
	if (tableOption != null) {
		// 如果 ConfigOption 上有 @Documentation.TableOption 注解,则读取它的 execMode 字段,拼接到 html 内容中。
		Documentation.ExecMode execMode = tableOption.execMode();
		if (Documentation.ExecMode.BATCH_STREAMING.equals(execMode)) {
			execModeStringBuilder.append("<br> <span class=\"label label-primary\">")
					.append(Documentation.ExecMode.BATCH.toString())
					.append("</span> <span class=\"label label-primary\">")
					.append(Documentation.ExecMode.STREAMING.toString())
					.append("</span>");
		} else {
			execModeStringBuilder.append("<br> <span class=\"label label-primary\">")
					.append(execMode.toString())
					.append("</span>");
		}
	}

	return "" +
		"        <tr>\n" +
		"            <td><h5>" + escapeCharacters(option.key()) + "</h5>" + execModeStringBuilder.toString() + "</td>\n" +
		"            <td style=\"word-wrap: break-word;\">" + escapeCharacters(addWordBreakOpportunities(defaultValue)) + "</td>\n" +
		"            <td>" + type + "</td>\n" +
		"            <td>" + formatter.format(option.description()) + "</td>\n" +
		"        </tr>\n";
}

1.6 @Documentation.ExcludeFromDocumentation

作用于 ConfigOption 配置项,用于从最终生成的 HTML 文档中移除配置项。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Internal
public @interface ExcludeFromDocumentation {
	/**
	 * The optional reason why the config option is excluded from documentation.
	 * 解释下从文档中移除配置项的原因
	 */
	String value() default "";
}

下面通过一个示例来说明这个注解的用途。
查看测试类 ConfigOptionsDocGeneratorTest 中应用到 @Documentation.ExcludeFromDocumentation 的单测 testConfigOptionExclusion

@Test
public void testConfigOptionExclusion() {
	final String htmlTable = ConfigOptionsDocGenerator.generateTablesForClass(TestConfigGroupWithExclusion.class).get(0).f1;
}

TestConfigGroupWithExclusion 类 mock 了一个配置项类:
excludedOption 使用了 @Documentation.ExcludeFromDocumentation 注解,在生成的 HTML 文档中它将被移除。

static class TestConfigGroupWithExclusion {
	public static ConfigOption<Integer> firstOption = ConfigOptions
		.key("first.option.a")
		.defaultValue(2)
		.withDescription("This is example description for the first option.");

	@Documentation.ExcludeFromDocumentation
	public static ConfigOption<String> excludedOption = ConfigOptions
		.key("excluded.option.a")
		.noDefaultValue()
		.withDescription("This should not be documented.");
}

运行单测 testConfigOptionExclusion 的输出结果如下:

<table class="table table-bordered">
    <thead>
        <tr>
            <th class="text-left" style="width: 20%">Key</th>
            <th class="text-left" style="width: 15%">Default</th>
            <th class="text-left" style="width: 10%">Type</th>
            <th class="text-left" style="width: 55%">Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><h5>first.option.a</h5></td>
            <td style="word-wrap: break-word;">2</td>
            <td>Integer</td>
            <td>This is example description for the first option.</td>
        </tr>
    </tbody>
</table>

2. 其他标记注解

关于这几种标记注解,源码中暂时还没有找到相关测试用例,后续补充。

2.1 @Experimental

表示标记对象是试验使用的注解,带有此注解的类是没有经过严格测试和不稳定的,可能在以后的版本中被修改或移除。

@Documented
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR })
@Public
public @interface Experimental {
}

2.2 @Internal

将稳定的公共的api注解为内部开发者api,内部开发者api是稳定的,面向Flink内部,可能随着版本变化。

@Documented
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR })
@Public
public @interface Internal {
}

2.3 @Public

标注类为开放的,稳定的。
类、方法、属性被这个这个注解修饰时,表示在小版本迭代(1.0,1.1,1.2)中,都维持稳定,应用程序将根据同一大版本进行编译。

@Documented
@Target(ElementType.TYPE)
@Public
public @interface Public {}

2.4 @PublicEvolving

带有此注解的类和方法用于公共使用,并且具有稳定的行为。但是,它们的接口和签名不被认为是稳定的,并且当跨版本时可能会变化。

@Documented
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR })
@Public
public @interface PublicEvolving {
}

2.5 @VisibleForTesting

标注有些方法、属性、构造函数、类等在 test 阶段可见,用于测试。
例如,当方法是 private 的,不打算在外部去调用的,但是有些内部测试需要访问它,所以加上 VisibleForTesting 注解进行内部测试。

@Documented
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR })
@Internal
public @interface VisibleForTesting {}
原创文章 25 获赞 22 访问量 1246

猜你喜欢

转载自blog.csdn.net/a1240466196/article/details/105511850