本文从以下四个方面手把手教你写Kafka Streams程序:

一. 设置Maven项目

二. 编写第一个Streams应用程序:Pipe

三. 编写第二个Streams应用程序:Line Split

四. 编写第三个Streams应用程序:Wordcount

一. 设置Maven项目

我们将使用Kafka Streams Maven Archetype来创建Streams项目结构:

  1. mvn archetype:generate \
  2. -DarchetypeGroupId=org.apache.kafka \
  3. -DarchetypeArtifactId=streams-quickstart-java \
  4. -DarchetypeVersion=1.1.0 \
  5. -DgroupId=streams.examples \
  6. -DartifactId=streams.examples \
  7. -Dversion=0.1 \
  8. -Dpackage=myapps

如果你需要,您可以为groupId,artifactId和package设置不同的值。假设您使用上述参数值,该命令将创建一个如下所示的项目结构:

  1. > tree streams.examples
  2. streams-quickstart
  3. |-- pom.xml
  4. |-- src
  5. |-- main
  6. |-- java
  7. | |-- myapps
  8. | |-- LineSplit.java
  9. | |-- Pipe.java
  10. | |-- WordCount.java
  11. |-- resources
  12. |-- log4j.properties

项目中包含的pom.xml文件已经定义了Streams依赖项,并且在src/main/java已经有几个Streams示例程序。 既然我们要从头开始编写这样的程序,现在我们先删除这些例子:

  1. > cd streams-quickstart
  2. > rm src/main/java/myapps/*.java

二. 编写第一个Streams应用程序:Pipe

It's coding time now! Feel free to open your favorite IDE and import this Maven project, or simply open a text editor and create a java file under src/main/java. Let's name it Pipe.java:
现在是编码时间! 随意打开你最喜欢的IDE并导入这个Maven项目,或者直接打开一个文本编辑器并在src/main/java下创建一个java文件。 我们将其命名为Pipe.java

  1. package myapps;
  2. public class Pipe {
  3. public static void main(String[] args) throws Exception {
  4. }
  5. }

我们在main中来编写这个pipe程序。请注意,由于IDE通常可以自动添加导入语句,因此我们不会列出导入语句。但是,如果您使用的是文本编辑器,则需要手动添加导入,并且在本节末尾,我们将为您显示带有导入语句的完整代码段。

编写Streams应用程序的第一步是创建一个java.util.Properties映射来指定StreamsConfig中定义的不同Streams执行配置值。 需要设置的几个重要配置值:StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,它指定用于建立初始连接到Kafka集群的host/port列表,以及StreamsConfig.APPLICATION_ID_CONFIG,它提供了Streams的唯一标识符应用程序与其他应用程序进行区分:

  1. Properties props = new Properties();
  2. props.put(StreamsConfig.APPLICATION_ID_CONFIG, "streams-pipe");
  3. props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); // assuming that the Kafka broker

假设这个应用程序和集群在同一台机器运行。

另外,你也可以自定义其他配置,例如设置消息key-value对的默认序列化和反序列:

  1. props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
  2. props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

有关Kafka Streams的完整配置列表,请参阅这里

接下来我们将定义Streams应用程序的计算逻辑。在Kafka Streams中,这种计算逻辑被定义为连接处理器节点的拓扑结构。我们可以使用拓扑构建器来构建这样的拓扑,

  1. final StreamsBuilder builder = new StreamsBuilder();

然后使用此拓扑构建器,创建主题为streams-plaintext-input源流(ps:就是数据的来源):

  1. KStream<String, String> source = builder.stream("streams-plaintext-input");

现在我们得到一个KStream,它不断的从来源主题streams-plaintext-input获取消息。消息是String类型的key-value对。我们可以用这个流做的最简单的事情就是将它写入另一个Kafka主题streams-pipe-output中:

  1. source.to("streams-pipe-output");

请注意,我们也可以将上面两行连接成一行,如下所示:

  1. builder.stream("streams-plaintext-input").to("streams-pipe-output");

我们可以通过执行以下操作来检查此构建器创建的拓扑结构类型:

  1. final Topology topology = builder.build();

将描述输出:

  1. System.out.println(topology.describe());

如果我们现在编译并运行程序,它会输出以下信息:

  1. > mvn clean package
  2. > mvn exec:java -Dexec.mainClass=myapps.Pipe
  3. Sub-topologies:
  4. Sub-topology: 0
  5. Source: KSTREAM-SOURCE-0000000000(topics: streams-plaintext-input) --> KSTREAM-SINK-0000000001
  6. Sink: KSTREAM-SINK-0000000001(topic: streams-pipe-output) <-- KSTREAM-SOURCE-0000000000
  7. Global Stores:
  8. none

如上所示,它说明构建的拓扑有两个处理器节点,源节点KSTREAM-SOURCE-0000000000和sink节点KSTREAM-SINK-0000000001KSTREAM-SOURCE-0000000000连续读取Kafka主题streams-plaintext-input的消息,并将它们传送到其下游节点KSTREAM-SINK-0000000001; KSTREAM-SINK-0000000001会将其接收到的每条消息写入另一个Kafka主题streams-pipe-output中( --><-- 箭头指示该节点的下游和上游处理器节点,即在拓扑图中的“children”和“parents“)。 它还说明,这种简单的拓扑没有与之相关联的全局状态存储(我们将在后面的章节中更多地讨论状态存储)。

请注意,我们总是可以像在上面那样在任何给定点上描述拓扑,而我们正在代码中构建它,因此作为用户,您可以交互式地“尝试并品尝”拓扑中定义的计算逻辑,直到你满意为止。假设我们已经完成了这个简单的拓扑结构,它只是以一种无尽的流式方式将数据从一个Kafka主题管道传输到另一个主题,我们现在可以使用我们刚刚构建的两个组件构建Streams客户端:配置map和拓扑对象(也可以从props map构造一个StreamsConfig对象,然后将该对象传递给构造函数,可以重载KafkaStreams构造函数来实现任一类型)。

  1. final KafkaStreams streams = new KafkaStreams(topology, props);

通过调用它的start()函数,我们可以触发这个客户端的执行。在此客户端上调用close()之前,执行不会停止。 例如,我们可以添加一个带有倒计时的shutdown hook来捕获用户中断,并在终止该程序时关闭客户端:

  1. final CountDownLatch latch = new CountDownLatch(1);
  2. // attach shutdown handler to catch control-c
  3. Runtime.getRuntime().addShutdownHook(new Thread("streams-shutdown-hook") {
  4. @Override
  5. public void run() {
  6. streams.close();
  7. latch.countDown();
  8. }
  9. });
  10. try {
  11. streams.start();
  12. latch.await();
  13. } catch (Throwable e) {
  14. System.exit(1);
  15. }
  16. System.exit(0);

到目前为止,完整的代码如下所示:

  1. package myapps;
  2. import org.apache.kafka.common.serialization.Serdes;
  3. import org.apache.kafka.streams.KafkaStreams;
  4. import org.apache.kafka.streams.StreamsBuilder;
  5. import org.apache.kafka.streams.StreamsConfig;
  6. import org.apache.kafka.streams.Topology;
  7. import java.util.Properties;
  8. import java.util.concurrent.CountDownLatch;
  9. public class Pipe {
  10. public static void main(String[] args) throws Exception {
  11. Properties props = new Properties();
  12. props.put(StreamsConfig.APPLICATION_ID_CONFIG, "streams-pipe");
  13. props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
  14. props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
  15. props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
  16. final StreamsBuilder builder = new StreamsBuilder();
  17. builder.stream("streams-plaintext-input").to("streams-pipe-output");
  18. final Topology topology = builder.build();
  19. final KafkaStreams streams = new KafkaStreams(topology, props);
  20. final CountDownLatch latch = new CountDownLatch(1);
  21. // attach shutdown handler to catch control-c
  22. Runtime.getRuntime().addShutdownHook(new Thread("streams-shutdown-hook") {
  23. @Override
  24. public void run() {
  25. streams.close();
  26. latch.countDown();
  27. }
  28. });
  29. try {
  30. streams.start();
  31. latch.await();
  32. } catch (Throwable e) {
  33. System.exit(1);
  34. }
  35. System.exit(0);
  36. }
  37. }

如果您已经在localhost:9092上运行了Kafka,并且创建了主题streams-plaintext-inputstreams-pipe-output,则可以在IDE或命令行上使用Maven运行此代码:

  1. > mvn clean package
  2. > mvn exec:java -Dexec.mainClass=myapps.Pipe

有关如何运行Streams应用程序并观察计算结果的详细说明,请阅读Play with a Streams部分。本节的其余部分我们不会谈论这一点。

三. 编写第二个Streams应用程序:Line Split

我们已经学会了如何构建Streams客户端及其两个关键组件:StreamsConfig和Topology。 现在让我们继续通过增加当前拓扑来添加一些实际的处理逻辑。我们可以首先复制现有的Pipe.java类来创建另一个程序:

  1. > cp src/main/java/myapps/Pipe.java src/main/java/myapps/LineSplit.java

并更改其类名以及应用程序ID配置以,与之前的程序区分开来:

  1. public class LineSplit {
  2. public static void main(String[] args) throws Exception {
  3. Properties props = new Properties();
  4. props.put(StreamsConfig.APPLICATION_ID_CONFIG, "streams-linesplit");
  5. // ...
  6. }
  7. }

由于每个源流的消息都是一个字符串类型的键值对,因此让我们将值字符串视为文本行,并使用FlatMapValues运算符将其分成单词:

  1. KStream<String, String> source = builder.stream("streams-plaintext-input");
  2. KStream<String, String> words = source.flatMapValues(new ValueMapper<String, Iterable<String>>() {
  3. @Override
  4. public Iterable<String> apply(String value) {
  5. return Arrays.asList(value.split("\\W+"));
  6. }
  7. });

操作员将把源流作为输入,并通过按顺序处理源流中的每条消息并将其值字符串分解为一个单词列表,并生成每个单词作为输出的新消息,从而生成一个名为单词的新流。这是一个无状态的操作,无需跟踪以前收到的消息或处理结果。请注意,如果您使用的是JDK 8,则可以使用lambda表达式并简化上面的代码:

  1. KStream<String, String> source = builder.stream("streams-plaintext-input");
  2. KStream<String, String> words = source.flatMapValues(value -> Arrays.asList(value.split("\\W+")));

最后,我们可以将单词流写回另一个Kafka主题,比如说stream-linesplit-output。 再次,这两个步骤可以如下所示连接(假设使用lambda表达式):

  1. KStream<String, String> source = builder.stream("streams-plaintext-input");
  2. source.flatMapValues(value -> Arrays.asList(value.split("\\W+")))
  3. .to("streams-linesplit-output");

如果我们现在将此扩展拓扑描述打印出来System.out.println(topology.describe()),我们将得到以下结果:

  1. > mvn clean package
  2. > mvn exec:java -Dexec.mainClass=myapps.LineSplit
  3. Sub-topologies:
  4. Sub-topology: 0
  5. Source: KSTREAM-SOURCE-0000000000(topics: streams-plaintext-input) --> KSTREAM-FLATMAPVALUES-0000000001
  6. Processor: KSTREAM-FLATMAPVALUES-0000000001(stores: []) --> KSTREAM-SINK-0000000002 <-- KSTREAM-SOURCE-0000000000
  7. Sink: KSTREAM-SINK-0000000002(topic: streams-linesplit-output) <-- KSTREAM-FLATMAPVALUES-0000000001
  8. Global Stores:
  9. none

正如我们上面看到的,一个新的处理器节点KSTREAM-FLATMAPVALUES-0000000001被注入到原始源节点和sink节点之间的拓扑中。 它将源节点作为其父节点,将sink节点作为其子节点。换句话说,源节点获取的每个消息,将首先遍历新加入的KSTREAM-FLATMAPVALUES-0000000001节点进行处理,并且结果将生成一个或多个新消息。它们将继续往下走到sink节点回写给kafka。注意这个处理器节点是“无状态的”,因为它不与任何仓库相关联(即(stores:[]))。

完整的代码如下所示(假设使用lambda表达式):

  1. package myapps;
  2. import org.apache.kafka.common.serialization.Serdes;
  3. import org.apache.kafka.streams.KafkaStreams;
  4. import org.apache.kafka.streams.StreamsBuilder;
  5. import org.apache.kafka.streams.StreamsConfig;
  6. import org.apache.kafka.streams.Topology;
  7. import org.apache.kafka.streams.kstream.KStream;
  8. import java.util.Arrays;
  9. import java.util.Properties;
  10. import java.util.concurrent.CountDownLatch;
  11. public class LineSplit {
  12. public static void main(String[] args) throws Exception {
  13. Properties props = new Properties();
  14. props.put(StreamsConfig.APPLICATION_ID_CONFIG, "streams-linesplit");
  15. props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
  16. props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
  17. props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
  18. final StreamsBuilder builder = new StreamsBuilder();
  19. KStream<String, String> source = builder.stream("streams-plaintext-input");
  20. source.flatMapValues(value -> Arrays.asList(value.split("\\W+")))
  21. .to("streams-linesplit-output");
  22. final Topology topology = builder.build();
  23. final KafkaStreams streams = new KafkaStreams(topology, props);
  24. final CountDownLatch latch = new CountDownLatch(1);
  25. // ... same as Pipe.java above
  26. }
  27. }

四. 编写第三个Streams应用程序:Wordcount

现在让我们进一步通过计算源文本流中单词的出现,来向拓扑中添加一些“有状态”计算。按照类似的步骤,我们创建另一个基于LineSplit.java类的程序:

  1. public class WordCount {
  2. public static void main(String[] args) throws Exception {
  3. Properties props = new Properties();
  4. props.put(StreamsConfig.APPLICATION_ID_CONFIG, "streams-wordcount");
  5. // ...
  6. }
  7. }

为了计算单词,我们可以首先修改flatMapValues,将它们全部作为小写字母(假设使用lambda表达式):

  1. source.flatMapValues(new ValueMapper<String, Iterable<String>>() {
  2. @Override
  3. public Iterable<String> apply(String value) {
  4. return Arrays.asList(value.toLowerCase(Locale.getDefault()).split("\\W+"));
  5. }
  6. });

我们必须首先指定我们要关键流的字符串value,即小写单词,用groupBy操作。该运算符生成一个新的分组流,然后可以由一个计数操作员汇总,该操作员可以在每个分组键上生成一个运行计数:

  1. KTable<String, Long> counts =
  2. source.flatMapValues(new ValueMapper<String, Iterable<String>>() {
  3. @Override
  4. public Iterable<String> apply(String value) {
  5. return Arrays.asList(value.toLowerCase(Locale.getDefault()).split("\\W+"));
  6. }
  7. })
  8. .groupBy(new KeyValueMapper<String, String, String>() {
  9. @Override
  10. public String apply(String key, String value) {
  11. return value;
  12. }
  13. })
  14. // Materialize the result into a KeyValueStore named "counts-store".
  15. // The Materialized store is always of type <Bytes, byte[]> as this is the format of the inner most store.
  16. .count(Materialized.<String, Long, KeyValueStore<Bytes, byte[]>> as("counts-store"));

请注意,count运算符有Materialized参数,该参数指定运行计数应存储在名为counts-store的状态存储中。 此Counts仓库可以实时查询,详情请参阅开发者手册。

请注意,为了从主题streams-wordcount-output读取changelog流,需要将值反序列化设置为org.apache.kafka.common.serialization.LongDeserializer。假设可以使用JDK 8的lambda表达式,上面的代码可以简化为:

  1. KStream<String, String> source = builder.stream("streams-plaintext-input");
  2. source.flatMapValues(value -> Arrays.asList(value.toLowerCase(Locale.getDefault()).split("\\W+")))
  3. .groupBy((key, value) -> value)
  4. .count(Materialized.<String, Long, KeyValueStore<Bytes, byte[]>>as("counts-store"))
  5. .toStream()
  6. .to("streams-wordcount-output", Produced.with(Serdes.String(), Serdes.Long()));

如果我们再次将这种扩展拓扑描述为System.out.println(topology.describe()),我们将得到以下结果:

  1. > mvn clean package
  2. > mvn exec:java -Dexec.mainClass=myapps.WordCount
  3. Sub-topologies:
  4. Sub-topology: 0
  5. Source: KSTREAM-SOURCE-0000000000(topics: streams-plaintext-input) --> KSTREAM-FLATMAPVALUES-0000000001
  6. Processor: KSTREAM-FLATMAPVALUES-0000000001(stores: []) --> KSTREAM-KEY-SELECT-0000000002 <-- KSTREAM-SOURCE-0000000000
  7. Processor: KSTREAM-KEY-SELECT-0000000002(stores: []) --> KSTREAM-FILTER-0000000005 <-- KSTREAM-FLATMAPVALUES-0000000001
  8. Processor: KSTREAM-FILTER-0000000005(stores: []) --> KSTREAM-SINK-0000000004 <-- KSTREAM-KEY-SELECT-0000000002
  9. Sink: KSTREAM-SINK-0000000004(topic: Counts-repartition) <-- KSTREAM-FILTER-0000000005
  10. Sub-topology: 1
  11. Source: KSTREAM-SOURCE-0000000006(topics: Counts-repartition) --> KSTREAM-AGGREGATE-0000000003
  12. Processor: KSTREAM-AGGREGATE-0000000003(stores: [Counts]) --> KTABLE-TOSTREAM-0000000007 <-- KSTREAM-SOURCE-0000000006
  13. Processor: KTABLE-TOSTREAM-0000000007(stores: []) --> KSTREAM-SINK-0000000008 <-- KSTREAM-AGGREGATE-0000000003
  14. Sink: KSTREAM-SINK-0000000008(topic: streams-wordcount-output) <-- KTABLE-TOSTREAM-0000000007
  15. Global Stores:
  16. none

如上所述,拓扑现在包含两个断开的子拓扑。第一个子拓扑的接收节点KSTREAM-SINK-0000000004将写入一个重新分区主题Counts-repartition,它将由第二个子拓扑的源节点KSTREAM-SOURCE-0000000006读取。重分区topic通过使用聚合键“shuffle”的源流,在这种情况下,聚合键为值字符串。此外,在第一个子拓扑结构内部,在分组KSTREAM-KEY-SELECT-0000000002节点和sink节点之间注入无状态的KSTREAM-FILTER-0000000005节点,以过滤出聚合key为空的任何中间记录。

在第二个子拓扑中,聚合节点KSTREAM-AGGREGATE-0000000003与名为Counts的状态存储相关联(名称由用户在count运算符中指定)。在即将到来的流源节点接收到每个消息时,聚合处理器将首先查询其关联的Counts存储以获得该密钥的当前计数,并将其增加1,然后将新计数写回仓库。将每个更新的key计数传送到KTABLE-TOSTREAM-0000000007节点,KTABLE-TOSTREAM-0000000007节点将该更新流解释为消息流,然后再传输到汇聚节点KSTREAM-SINK-0000000008以写回Kafka。

完整的代码如下所示(假设使用lambda表达式):

  1. package myapps;
  2. import org.apache.kafka.common.serialization.Serdes;
  3. import org.apache.kafka.streams.KafkaStreams;
  4. import org.apache.kafka.streams.StreamsBuilder;
  5. import org.apache.kafka.streams.StreamsConfig;
  6. import org.apache.kafka.streams.Topology;
  7. import org.apache.kafka.streams.kstream.KStream;
  8. import java.util.Arrays;
  9. import java.util.Locale;
  10. import java.util.Properties;
  11. import java.util.concurrent.CountDownLatch;
  12. public class WordCount {
  13. public static void main(String[] args) throws Exception {
  14. Properties props = new Properties();
  15. props.put(StreamsConfig.APPLICATION_ID_CONFIG, "streams-wordcount");
  16. props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
  17. props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
  18. props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
  19. final StreamsBuilder builder = new StreamsBuilder();
  20. KStream<String, String> source = builder.stream("streams-plaintext-input");
  21. source.flatMapValues(value -> Arrays.asList(value.toLowerCase(Locale.getDefault()).split("\\W+")))
  22. .groupBy((key, value) -> value)
  23. .count(Materialized.<String, Long, KeyValueStore<Bytes, byte[]>>as("counts-store"))
  24. .toStream()
  25. .to("streams-wordcount-output", Produced.with(Serdes.String(), Serdes.Long());
  26. final Topology topology = builder.build();
  27. final KafkaStreams streams = new KafkaStreams(topology, props);
  28. final CountDownLatch latch = new CountDownLatch(1);
  29. // ... same as Pipe.java above
  30. }
  31. }

本文转发自 http://orchome.com/957

在本指南中,我们将从头开始帮助你搭建自己的Kafka Streams流处理程序。 强烈建议您首先阅读快速入门,了解如何运行使用Kafka Streams编写的Streams应用程序(如果尚未这样做)。

关于Kafka深入学习视频, 如Kafka领导选举, offset管理, Streams接口, 高性能之道, 监控运维, 性能测试等,

请关注个人微信公众号: 求学之旅, 发送Kafka, 即可收获Kafka学习视频大礼包一枚。

  1.  

手把手教你写Kafka Streams程序的更多相关文章

  1. 手把手教你写对拍程序(PASCAL)

    谁适合看这篇文章? ACMERS,OIERS或其它参加算法竞赛或需要算法的人 对操作系统并不太熟悉的人 不会写对拍的人 在网上找不到一个特别详细的对拍样例的人 不嫌弃我写的太低幼的人 前言 在NOIP ...

  2. [zt]手把手教你写对拍程序(PASCAL)

    谁适合看这篇文章? ACMERS,OIERS或其它参加算法竞赛或需要算法的人 对操作系统并不太熟悉的人 不会写对拍的人 在网上找不到一个特别详细的对拍样例的人 不嫌弃我写的太低幼的人 前言 在NOIP ...

  3. 手把手教你写LKM rookit! 之 第一个lkm程序及模块隐藏(一)

    唉,一开始在纠结起个什么名字,感觉名字常常的很装逼,于是起了个这<手把手教你写LKM rookit> 我觉得: 你们觉得:...... 开始之前,我们先来理解一句话:一切的操作都是系统调用 ...

  4. 手把手教你写Sublime中的Snippet

    手把手教你写Sublime中的Snippet Sublime Text号称最性感的编辑器, 并且越来越多人使用, 美观, 高效 关于如何使用Sublime text可以参考我的另一篇文章, 相信你会喜 ...

  5. 只有20行Javascript代码!手把手教你写一个页面模板引擎

    http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...

  6. [原创]手把手教你写网络爬虫(5):PhantomJS实战

    手把手教你写网络爬虫(5) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 大家好!从今天开始,我要与大家一起打造一个属于我们自己的分布式爬虫平台,同时也会对涉及到的技术进行详细介绍.大 ...

  7. 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接

    本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...

  8. 手把手教你写DI_3_小白徒手支持 `Singleton` 和 `Scoped` 生命周期

    手把手教你写DI_3_小白徒手支持 Singleton 和 Scoped 生命周期 在上一节:手把手教你写DI_2_小白徒手撸构造函数注入 浑身绷带的小白同学:我们继续开展我们的工作,大家都知道 Si ...

  9. 手把手教你写DI_0_DI是什么?

    DI是什么? Dependency Injection 常常简称为:DI. 它是实现控制反转(Inversion of Control – IoC)的一个模式. fowler 大大大神 "几 ...

随机推荐

  1. Python习题(分页显示)

    class Page: def __init__(self, lst, pageSize): self.lst = lst # 数据 self.pageSize = pageSize # 每页显示多少 ...

  2. Leecode刷题之旅-C语言/python-169求众数

    /* * @lc app=leetcode.cn id=169 lang=c * * [169] 求众数 * * https://leetcode-cn.com/problems/majority-e ...

  3. C++编译错误杂记

    目录 2018年12月23日 error: no matching function for call to ××× 2018年12月10日 error: expected ')' before '* ...

  4. MySQL 重要语法

    1.查询表abc中的所有数据: SELECT * FROM abc WHERE 1=1; where 1=1; 这个条件始终为True,在不定数量查询条件情况下,1=1可以很方便的规范语句.

  5. mybatis中的resultMap实际作用

    resultMap和resultType在实际的使用上完全可以进行替换,但是resultMap有比resultType更多的一个功能.我们先定义一个简单的resultMap例子 <resultM ...

  6. 20154327 Exp6 信息搜集与漏洞扫描

    基础问题回答 (1)哪些组织负责DNS,IP的管理. 全球根服务器均由美国政府授权的ICANN统一管理,负责全球的域名根服务器.DNS和IP地址管理. 全球根域名服务器:绝大多数在欧洲和北美(全球13 ...

  7. clr via c#读书笔记五:常量和字段

    1.常量是值从不变化的符号.只能定义编译器识别的基元类型的常量.如:Boolean,Char,Byte,SByte,Int16,UInt16,Int32,UInt32,Int64,Single,Dou ...

  8. CC3200使用MQTT的SSL加密证书可用日期修改

    1. 在使用CC3200进行SSL加密的时候,需要证书,但是证书有一个截止日期,如果当前CC3200没有设置这个日期,那么证书通信会失败,需要添加代码 int setDeviceTime() { Sl ...

  9. JDBC 工具类模板c3p0

    JDBC 工具类模板 package com.itheima.sh.utils; import com.mchange.v2.c3p0.ComboPooledDataSource; import ja ...

  10. Ruby基础教程 1-10

    类结构 1.数值类结构     Fixnum到Bignum会自动转换   2.常用数值表示   3. ans=10.divmod(3) ans[0]是商  ans[1]是余数   4.实例方法roun ...