Avro 生成代码使用案例

摘要:最近研究 Kafka,其中数据对象的序列化和反序列化操作除了内置的序列化器外,还可以自定义对象序列化器,另外还有一中方法就是使用 Avro,这种方法通过规范Schema 达到更通用的效果。本文单独抽离出如何使用 Avro 生成代码,该过程同样也可以在 Kafka 使用中利用。

<!--more-->

概述

Avro 是 Hadoop 中的一个子项目,也是 Apache 中一个独立的项目,Avro 是一个基于二进制数据传输高性能的中间件。在 Hadoop 的其他项目中,例如 HBaseHiveClient 端与服务端的数据传输也采用了这个工具。Avro 是一个数据序列化的系统,它可以提供:

1、丰富的数据结构类型 2、快速可压缩的二进制数据形式 3、存储持久数据的文件容器 4、远程过程调用 RPC 5、简单的动态语言结合功能,Avro 和动态语言结合后,读写数据文件和使用 RPC 协议都不需要生成代码,而代码生成作为一种可选的优化只值得在静态类型语言中实现。 Avro 支持跨编程语言实现(C, C++, C#,Java, Python, Ruby, PHP),Avro 提供着与诸如 Thrift 和 Protocol Buffers 等系统相似的功能,但是在一些基础方面还是有区别的,主要是:

1、动态类型:Avro 并不需要生成代码,模式和数据存放在一起,而模式使得整个数据的处理过程并不生成代码、静态数据类型等等。这方便了数据处理系统和语言的构造。 2、未标记的数据:由于读取数据的时候模式是已知的,那么需要和数据一起编码的类型信息就很少了,这样序列化的规模也就小了。 3、不需要用户指定字段号:即使模式改变,处理数据时新旧模式都是已知的,所以通过使用字段名称可以解决差异问题。 Avro 和动态语言结合后,读/写数据文件和使用 RPC 协议都不需要生成代码,而代码生成作为一种可选的优化只需要在静态类型语言中实现。

当在 RPC 中使用 Avro 时,服务器和客户端可以在握手连接时交换模式。服务器和客户端有着彼此全部的模式,因此相同命名字段、缺失字段和多余字段等信息之间通信中需要解决的一致性问题就可以容易解决。

还有,Avro 模式是用 JSON(一种轻量级的数据交换模式)定义的,这样对于已经拥有 JSON 库的语言可以容易实现

本文主要介绍如何使用 AvroSchema 文件生成代码,不会详细介绍 Schema 的语法等内容,可参考官方文档,以及如下博文。Avro介绍

使用Avro生成代码

目标

在开发中,如果我们要使用 Avro 往往需要生成类文件,虽然这些文件不需要被管理,每次编译都可以自动生成,但是如果开发中能有一个生成好的类文件,对于测试和开发都有好处。本文介绍使用 Maven 依赖来完成这个过程,好处是不需要自己下载 jar,自己敲命令行来完成代码生成。

这里着重介绍以下问题:

  1. AvroMaven 插件的配置
  2. 如何执行代码生成命令

AvroMaven 插件的配置

首先引入依赖 org.apache.avro

<dependencies>
	<!-- avro Dependency-->
	<dependency>
		<groupId>org.apache.avro</groupId>
		<artifactId>avro</artifactId>
		<version>1.8.2</version>
	</dependency>
</dependencies>

然后添加 plugin

<plugin>
	<groupId>org.apache.avro</groupId>
	<artifactId>avro-maven-plugin</artifactId>
	<version>1.8.2</version>
	<!--global plugin configuration which used for run goal separately-->
	<configuration>
		<!--The Avro source directory for schema, protocol and IDL files.-->
		<sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory>
		<!--The directory where Avro writes code-generated sources. IMPORTANT!! -->
		<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
		<!--The input directory containing any Avro files used in testing.-->
		<testSourceDirectory>${project.basedir}/src/test/avro/</testSourceDirectory>
		<!--The output directory where Avro writes code-generated files for your testing purposes.-->
		<testOutputDirectory>${project.basedir}/src/test/java/</testOutputDirectory>
	</configuration>
	<executions>
		<execution>
			<!--
			IMPORTANT: Because this configuration is set in the execution with phase `generate-sources`.
			So only run compile or package which include this phase will apply this configuration.
			So if only make this configuration, run `mvn avro:schema` will only generate to the target
			folder with default configuration.
			-->
			<phase>generate-sources</phase>
			<goals>
				<goal>schema</goal>
				<!--If you want to generate other code, need add more goals-->
				<goal>protocol</goal>
				<goal>idl-protocol</goal>
			</goals>
			<!---->
			<configuration>
				<!--default is ${basedir}/src/main/avro
				The Avro source directory for schema, protocol and IDL files.-->
				<sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory>

				<!--default is ${project.build.directory}/generated-sources/avro
				 The directory where Avro writes code-generated sources. IMPORTANT!! -->
				<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>

				<!--default is ${basedir}/src/test/avro
				The input directory containing any Avro files used in testing.-->
				<testSourceDirectory>${project.basedir}/src/test/avro/</testSourceDirectory>

				<!--default is ${project.build.directory}/generated-test-sources/avro
				The output directory where Avro writes code-generated files for your testing purposes.-->
				<testOutputDirectory>${project.basedir}/src/test/java/</testOutputDirectory>

				<!--fieldVisibility PUBLIC_DEPRECATED
				Determines the accessibility of fields (e.g. whether they are public or private).
				Must be one of PUBLIC, PUBLIC_DEPRECATED or PRIVATE. PUBLIC_DEPRECATED merely
				adds a deprecated annotation to each field, e.g. "@Deprecated public long time".-->


				<!--In addition, the includes and testIncludes configurables can also be used to
				specify alternative file extensions to the defaults, which are **/*.avsc, **/*.avpr
				and **/*.avdl for schema, protocol and IDL files respectively.-->
				<!--<includes>
				  <include>**/*.avro</include>
				</includes>
				<testIncludes>
				  <testInclude>**/*.test</testInclude>
				</testIncludes>-->
			</configuration>
		</execution>
	</executions>
</plugin>

这个配置需要注意几点

配置的位置

官方文档中配置放在了 execution 标签之下,这就意味着这个配置是在对应的 phase 执行的环境下才有效。所以官方的案例只有在执行 generate-sources 时才会生效。

如果我们希望能够通过 mvn 执行 goal 的时候使配置生效,我们可以把配置放在 plugin 之下,这样所有的 goal 都会使用该配置文件。

配置选项说明

重点如如下几种配置:

配置项默认值描述
sourceDirectory${basedir}/src/main/avroAvro的Schema,Protocol,IDL 等文件
outputDirectory${project.build.directory}/generated-sources/avro代码生成的目标文件夹
testSourceDirectory${basedir}/src/test/avro测试使用的Schema,Protocol,IDL 等文件
testOutputDirectory${project.build.directory}/generated-test-sources/avro代码生成的目标文件夹

指定生成的包路径

通过上面的配置我们发现,只能指定到根路径,如果希望生成到指定的包路径,需要通过 Avro 的源文件指定 namespace 来唯一的指定包路径:

下面是一个 Schema 文件 weather.avsc

{
  "namespace": "science.mengxin.java.avro.demo",
  "type": "record",
  "name": "Weather",
  "doc": "A weather reading.",
  "fields": [
    {
      "name": "station",
      "type": "string",
      "order": "ignore"
    },
    {
      "name": "time",
      "type": "long"
    },
    {
      "name": "temp",
      "type": "int"
    }
  ]
}

如何执行代码生成命令

有了以上的配置,生成代码就非常简单

  1. 执行 mvn compile 或者 mvn package,代码将会按照配置文件生成
  2. 执行 mvn avro:schememvn avro:protocolmvn avro:idl-protocol 单独生成对应的代码

代码参考

以下是一个简单源码案例,仅供参考。

转载于:https://my.oschina.net/mengxin/blog/1853071

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Apache Avro是一个数据序列化系统,旨在支持快速、节省空间的数据交换和远程过程调用。它使用JSON格式定义数据结构,并支持动态类型,使其易于在不同编程语言之间进行交互。 使用Avro的步骤如下: 1. 定义数据结构:使用Avro的JSON格式定义数据结构,包括字段名、类型和默认值等信息。 例如,定义一个Person对象: ``` { "namespace": "example.avro", "type": "record", "name": "Person", "fields": [ {"name": "name", "type": "string"}, {"name": "age", "type": "int"} ] } ``` 2. 生成代码使用Avro工具生成指定语言的代码,包括数据结构类和序列化/反序列化类等。 例如,使用Avro工具生成Java代码: ``` $ java -jar avro-tools-1.9.2.jar compile schema person.avsc . ``` 3. 序列化数据:使用生成代码将数据序列化为字节数组。 例如,使用Java代码创建Person对象并序列化: ``` Person person = new Person("Alice", 30); ByteArrayOutputStream out = new ByteArrayOutputStream(); DatumWriter<Person> writer = new SpecificDatumWriter<Person>(Person.class); Encoder encoder = EncoderFactory.get().binaryEncoder(out, null); writer.write(person, encoder); encoder.flush(); byte[] bytes = out.toByteArray(); ``` 4. 反序列化数据:使用生成代码将字节数组反序列化为数据对象。 例如,使用Java代码反序列化字节数组: ``` ByteArrayInputStream in = new ByteArrayInputStream(bytes); DatumReader<Person> reader = new SpecificDatumReader<Person>(Person.class); Decoder decoder = DecoderFactory.get().binaryDecoder(in, null); Person person2 = reader.read(null, decoder); ``` 这样,就完成了数据的序列化和反序列化。 以下是一个完整的Java代码示例: ``` import org.apache.avro.Schema; import org.apache.avro.Schema.Parser; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericRecord; import org.apache.avro.io.DatumReader; import org.apache.avro.io.DatumWriter; import org.apache.avro.io.Decoder; import org.apache.avro.io.DecoderFactory; import org.apache.avro.io.Encoder; import org.apache.avro.io.EncoderFactory; import org.apache.avro.specific.SpecificDatumReader; import org.apache.avro.specific.SpecificDatumWriter; import org.apache.avro.specific.SpecificRecord; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; public class AvroExample { public static void main(String[] args) throws IOException { // Define schema String schemaJson = "{\n" + " \"namespace\": \"example.avro\",\n" + " \"type\": \"record\",\n" + " \"name\": \"Person\",\n" + " \"fields\": [\n" + " {\"name\": \"name\", \"type\": \"string\"},\n" + " {\"name\": \"age\", \"type\": \"int\"}\n" + " ]\n" + "}"; Schema schema = new Parser().parse(schemaJson); // Serialize data Person person = new Person("Alice", 30); ByteArrayOutputStream out = new ByteArrayOutputStream(); DatumWriter<Person> writer = new SpecificDatumWriter<Person>(Person.class); Encoder encoder = EncoderFactory.get().binaryEncoder(out, null); writer.write(person, encoder); encoder.flush(); byte[] bytes = out.toByteArray(); // Deserialize data ByteArrayInputStream in = new ByteArrayInputStream(bytes); DatumReader<Person> reader = new SpecificDatumReader<Person>(Person.class); Decoder decoder = DecoderFactory.get().binaryDecoder(in, null); Person person2 = reader.read(null, decoder); System.out.println(person2.getName()); // Alice System.out.println(person2.getAge()); // 30 } public static class Person implements SpecificRecord { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } @Override public void put(int i, Object o) { } @Override public Object get(int i) { if (i == 0) { return name; } else if (i == 1) { return age; } return null; } @Override public Schema getSchema() { return null; } } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值