JVM 上的 Lisp 方言 Clojure 1.9

还是从 OSChina 网站上得知 Clojure 1.9 在  2017 年 12 月发布的,时值这么久才开始真正去了解一下这个新版本。距离上一个版本 1.8 的发布时间(2016 年 1  月), 大概两年才出一个版本,而且总的说来 Clojure 1.9 并没有带来多大惊喜。

唯一能带来点喜气的也就是 Clolure 有了自己的命令行工具了,再也无需寄身于 Leiningen(一个 Clojure 构建工具,相当于 sbt 之于 Scala) 的篱笆之下了。以 Mac OS 平台为例,以前试图用 brew 来直接安装 clojure 的时候会提示找不到 clojure, 建议安装 leiningen.

$ brew install clojure

在 Clojure 1.9 出来之前,上面的命令会得到如下提示信息

Error: No available formula with the name "clojure"
Clojure isn't really a program but a library managed as part of a
project and Leiningen is the user interface to that library.
To install Clojure you should install Leiningen:
brew install leiningen
and then follow the tutorial:
https://github.com/technomancy/leiningen/blob/stable/doc/TUTORIAL.md

所以那时候不得不用 阅读全文 >>

使用 Mockito 的 @InjectMocks 创建被测试类实例

初识 Mockito 这个测试框架后,我们要使用 Mock 的属性创建一个被测试类实例时,大概会下面这么纯手工来打造。

假定类 UserService 有一个属性 UserDao userDao, 需要构造 UserService 实例时 Mock 内部状态

UserDao userDao = Mockito.mock(UserDao.class);
UserService testMe = new UserService(userDao);

如此,userDao 的行为就可以自由模拟了,这种纯手工方式都不需要给测试类添加

@RunWith(MockitoJunitRuner.class)
//或
MockitoAnnotations.initMocks(this);

因为上面两句是给 Mockito 的注解使用的。

如果所有的 Mock 对象全部通过手工来创建,那就不容易体现出 Mockito 的优越性出来。因此对于被测试对象的创建,Mock 属性的注入应该让 @Mock 和 @InjectMocks 这两个注解大显身手了。

标注在实例变量上的 @Mock 相当于是 Mockito.mock(Class) 创建了一个 Mock 对象,而 @InjectMock 标的实例会寻找到相应 Mock 属性想法构造出被测试类的实例。看下面的例子: 阅读全文 >>

推送 Docker 镜像到 Amazon ECR 仓库

Docker 镜像在未指定仓库时默认是从  Docker Hub 拉取的。如果需向 Docker Hub 推送镜像的话还可用 docker login 在交互中完成登陆 Docker Hub 的操作。docker login 的命令格式是

docker login [OPTIONS] [SERVER]

所以我们可以连接到任何的 Docker 镜像仓库,也可以是本机,但我们这里所要介绍的是如何推送镜像到 AWS 给我们提供的 Docker 镜像仓库(Amazon ECR - Amazon Elastic Container Registry)。每个帐号下都有自己独立的仓库,镜像推送到了  Amazon ECR 后我们能够很方便的在 ECS, Batch 服务中使用它,也可以从 ECR 拉取镜像到本地来。

首先我们来做一个运行 Spring Boot Web 的简单的 Docker 镜像,假定已用 mvc pacakge 生成了一个可独立运行的 jar 包 java-webapp-0.0.1-SNAPSHOT.jar 。该应用开启一个 Web 服务,访问 http://localhost:8080 显示一行字符串 Hello World!

创建一个目录 aws-docker, 并把 java-webapp-0.0.1-SNAPSHOT.jar 移入该目录,在其下创建 Dockerfile 文件,文件目录结构如下:

aws-docker
  ├── Dockerfile
  └── java-webapp-0.0.1-SNAPSHOT.jar

阅读全文 >>

Bash/Zsh 下调用 Emacs/Vim 编辑当前命令

Bash 环境

多数时候我们用的是 Bash, 比如个人的 Linux 不愿去定制,远程服务器的由不得你去定制,所以就从 Bash 说起。

默认键绑定 emacs, 操作是 ctrl-x, ctrl-e

在默认的 Bash  环境下,只要在命令行中按下 ctrl-x, ctrl-e 就会把当前命令的内容调入到环境变量  $EDITOR 指示的编辑器(默认为 emacs)去编辑,编辑后保存退出就会立即执行。

如果未安装 Emacs 编辑器,在按下 ctrl-x, ctrl-e 会得到如下提示

[vagrant@localhost ~]$
-bash: emacs: command not found

如果希望使用 vi 来编辑当前命令,就需要设置 EDITOR 环境变量,比如在 .bashrc 中加入

export EDITOR=vi

那么在命令行中按下 ctrl-x, ctrl-e 使用打开  vi 来编辑当前命令。

注:Emacs 要用 ctrl-x, ctrl-c, 再回答 y, 命令保存到临时文中; 而 vi 的相应操作是 :wq, 至少这个操作上 vi 要简洁些。 阅读全文 >>

Bash 下的 emacs 和 vi 键绑定

初衷只是为了记述一下在 Bash/Zsh 中怎么调用 Vi 编辑当前的命令,但一发挥便涉及到了 Bash/Zsh 的键绑定的话题,无法打住,只得另立新篇。这里也只说 Bash 的键绑定,不会有 Zsh 键绑定的内容。

什么是 Bash 的键绑定(keybing) 呢? 就是在 Bash 中的快捷键方案,即相当于某个 IDE 的快捷键配置,或者叫 Keymap。比如说 IntelliJ IDEA 中可选择的 Keymap 有 Eclipse, Emacs, JBuilder, Mac OS X, Mac OS X 10.5+, NetBean, Visual Studio, 以满足不同使用者的习惯。

相应的 Bash 也为我们提供了两种键绑定的方案,即 emacs(默认) 和 vi 键绑定类型。

Emacs 键绑定

我们大多数天天在 Bash 下无意识中使用着 Emacs 键绑定类型,即使可能从未用过 Emacs 本身。比如我们在 Bash 下的按键组合

ctrl + a     跳到命令行的开始
ctrl + e     跳到命令行末尾
!!               重复最后一个命令
ctrl + l      清屏操作,类似于  clear 命令
ctrl + c      中断/杀掉当前运行的进程 (SIGINT)
ctrl + d     发送 EOF 标记,这会关掉当前的 shell (EXIT)
ctrl + z      发送 SIGTSTP 给当前任务,使其挂起送到后台。(所以如果 vi 未正常退出,而是按 ctrl + z 的话,vi 进程还呆在后台

它们都是来自于 Emacs 键绑定。是不是那么的熟悉啊? 阅读全文 >>

Scala 中应用 Future 并发编程

Java 世界里进行并发编程有 Future 和  ExecutorService(当成 ThreadPool 来对待),及至 Java 8 引入了更为趁手的 CompletableFuture。那么使用 Scala 该如何进行并发编程呢?Scala 能毫无障碍的使用 Java 类库,所以完全可以用 Java 的 API 来使用线程池,只是那不太 Scala 罢了。

既然是 Scala, 就尽量写成 Scala Style 吧,Scala 也有自己的 Future, 还有 Promise 呢? 至于 Promise 与 Future 多大区别,语义上似乎也差不多,只看到 Promise 中有一个 Future 的属性。如果想玩得高级一点的话就该把  Actor 弄起来,它算是所谓的纤程,多个  Actor 可跑在同一个线程中,当然启动要快,内存消耗少,还避免了上下文切换。

这里还是先体验 Scala Future 的用法。Future 本身只是描述了一个任务,及将要获得的结果(或执行的动作),因此那样一个任务也是要放到线程池中去执行。这和 Java 的 Future/ThreadPool 是一样的概念。稍有不同的是线程池的创建与使用,线程池的默认大小配置等。看个简单的应用示例,本次测试的 Scala 版本是 2.11.12,为了突出实际的线程池大小,我们把任务数设置为 20 个

Scala Future 并发编程体验

阅读全文 >>

实现 Amazon S3 数据(文件)分段上传

探索是否能以流式写数据到 S3

通常,在我们项目中用 Java 代码上传数据到 S3 是下面那样的操作

AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient();
s3Client.putObject("bucket_name", "s3key.txt", new ByteArrayInputStream("hello".getBytes()), new ObjectMetadata()); //ObjectMetadata 没什么特别的话可以为 null

虽然  putObject() 的第三个参数是一个流,但它是输入流, 并非输出流啊。也就是说在执行该方法时必须把所有待上传的上据全部准备在这个输入流中,所以这里就直接用一个 ByteArrayInputStream 来包裹需上入到 S3 的数据内容。

当然 putObject() 也就无法像 FileObjectOutputStream 那样流式写入内容到文件中,因为 putObject() 前后都没有与 bucket 上那个文件上有联系。即使是用 PipedInputStream/PipedOutputStream  也不行, 比如说下面的代码 阅读全文 >>

用 PreparedStatement 向 SqlServer 中一次性插入多条记录

标准 SQL 都提供了下面这种方式一条 INSERT INTO 语句插入多条记录

INSERT INTO Customers(Id, Name, Age) VALUES (1, 'Name1', 21.5), (2, 'Name2', 32.3)

VALUES 之后用括号列出每一条记录。但是在 Java 中想把上面的语句转换成 PreparedStatement  来插入多条记录时就有些问题。要么写成

INSERT INTO Customers(Id, Name, Age) VALUES(?, ?, ?), (?, ?, ?), (?, ?, ?) ....

我们不知道 VALUES 后应该列多少个问号,而且  JDBC 对参数的个数是有限制的,最多 2000 个参数。如果根据字段个数来算一次添加多少条记录,那么这条 SQL 语句也是动态的,不能很好的作为 PreparedStatement 进行预编译。以一个表三个字段为例,2000 个参数下一次性最多能插入记录数 666 条,也可能由于输入是 666 条记录的任意数量,所以生成的语句非静态的。

这种方式与每次手动拼凑一个完全静态的 INSERT INTO 语句应该不会有太多的差别。

如果只是写成 

INSERT INTO Customers(Id, Name, Age) VALUES(?, ?, ?)

然后试图进行下面的操作 阅读全文 >>

体验 Scala 2.12 支持的 Java 8 风格(SAM) Lambda

上一次关注 Scala 新版本特性还是在将近五年前,针对的是  Scala 2.10. 后来也一直在使用 Scala,基本上是 Scala 2.11,但对 Scala 2.11 所带来的新特性基本无知,大约有个 Macro 功能,没什么机会用上,应用 sbt 时稍有接触。还是老句老话,了解新特性最可靠的文档是每个版本的的 Release Notes, 比如 Scala 2.12.0 Release Notes.

其中 Scala 2.12 带来的主要特性在于对 Java 8 的充分支持:

  1. Scala 可以有方法实现的 trait 直接编译为带默认方法的 Java 接口
  2. Lambda 表达式无需生成相应的类,而是用到 invokedynamic 字节码指令(这个是 Java 7 加进来的新指令)
  3. 最方便的功能莫过于终于支持 Java  8 风格的 Lambda,即功能性接口的 SAM(Single Abstract Method)

Scala 的 Lambda 内部实现

这儿主要是体验 Scala 2.12 如何使用 Java 8 风格的 Lambda. 在 Scala 2.12 之前,Scala 对 Lambda 的支持是为你准备了一大堆的 trait 类,有

  1. Function0, Function1, ...... Function22 (接收多个参数,返回一个值)
  2. Product1, Product2, ...... Product22 (函数返回多个值,即 TupleX 时用的)

阅读全文 >>

Spark 提交任务时 Invalid signature file digest 错误

第一次用  spark-submit 提交任务,是 Scala 的程序,命令格式是

spark-submit --class <main-class> --master local[*] --name "My first Spark Job" spark-test-fat-1.0-SNAPSHOT.jar

结果报出错误

Exception in thread "main" java.lang.SecurityException: Invalid signature file digest for Manifest main attributes
at sun.security.util.SignatureFileVerifier.processImpl(SignatureFileVerifier.java:330)
at sun.security.util.SignatureFileVerifier.process(SignatureFileVerifier.java:263)
at java.util.jar.JarVerifier.processEntry(JarVerifier.java:318)
......
at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:119)
at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)

Google 查了下,是需要把 jar 文件中的 META-INF/*.RSA META-INF/*.DSA META-INF/*.SF 文件清理掉。我们可以执行如下命令从现有的 jar 中除去

zip -d <jar file name>.jar META-INF/*.RSA META-INF/*.DSA META-INF/*.SF

是因为 jar 包中包含有这些文件才造成提交 Spark 时验证文件签名摘要时失败。 阅读全文 >>