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

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

推送 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 键绑定。是不是那么的熟悉啊? 阅读全文 >>

实现 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  也不行, 比如说下面的代码 阅读全文 >>

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 时验证文件签名摘要时失败。 阅读全文 >>

IntelliJ IDEA 中创建 Maven Scala 项目

Scala 项目看家的构建工具当然是 SBT, 假如我们已习惯于 Maven, 想要用 Maven 来构建 Scala 项目该如何做呢?那首先要找到一个 Maven Scala 相应的 Archetype, 然后用命令 mvn archetype:generate 或是在 IntelliJ IDEA 使用 Maven 项目创建向导来选择一个 Maven Scala Archetype。这里主要介绍 IntelliJ IDEA 中 Maven 向导创建 Scala 项目的方式。

首先确保我们已安装了 IntelliJ IDEA 的 Maven 和  Scala 插件。插件中自带了 org.scala-tools.archetypes:scala-archetype-simple:1.2 的 Maven archetype, 这是一个貌视 Scala 官方的 archetype。我们可以尝试基于它来创建一个 Maven Scala 项目。通过 IntelliJ IDEA 的菜单 File/New/Project..., 在弹出的窗口中选择 Maven/Create from archetype, 然后找到 scala-archetype-simple, 自带版本为 1.2, 当前最新版也不过 1.3, 那还是  2010 年建立,别提有多老了。 阅读全文 >>

记录一下 Spring 如何扫描注解的 Bean 与资源

Spring 相关代码分析

本文通过对 Spring 的源代码来理解它是如何扫描 Bean 与资源的,因为自己有一个类似的需求,想把一堆的配置文件丢到 resources 下某个目录中,在程序启动的时候能加载它们。因为文件名是不一定的,所以不能直接指定文件名来加载,通过对 Spring 扫描资源的理解后,可以在自己的代码中手工扫描那些配置文件,以后有任何新的配置文件只需要扔到相应的配置目录即可。

下面以一个最简单的 Spring Boot 项目为例,调试并观察源代码

还是直奔主题吧,不一步一步的去探寻到底是哪个实现类去扫描资源的,用 Google 找到的是 ClassPathScanningCandidateComponentProvider, 因此直接在这个类的敏感位置上打上断点,比如它的构造函数 阅读全文 >>

谁说 HTTP GET 就不能通过 Body 来发送数据呢?

当我们被问及 HTTP 的 GET 与 POST 两种请求方式的区别的时候,很多答案是说 GET 的数据须通过 URL 以 Query Parameter 来传送,而 POST 可以通过请求体来发送数据,所以因 URL 的受限,往往 GET 无法发送太多的字符。这个回答好比在启用了 HTTPS 时,GET 请求 URL 中的参数仍然是明文传输的一样。

GET 果真不能通过 Request Body 来传送数据吗?非也。如此想法多半是因循着网页中 form 的 method 属性只有 get 与 post 两种而来。因为把 form 的 method 设置为 post, 表单数据会放在 body 中,而 method 为 get(默认值) 时, 提交时浏览器会把表单中的字符拼接到 action 的 URL 后作为 query parameter 传送。于是乎就有了这么一种假像:HTTP GET 必须通过 URL 的查询参数来发送数据。

其实 HTTP 规范并未规定说 GET 就不能发送 body 数据,在 RFC GET 中只是说

The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.

只是说 GET 意味着通过 URI 来识别资源。

我也是本着传统上对 GET 与 POST 区别的误解很多年,今天突然意识到 GET 应该可以使用 body, 况且 HTTP 本身是一个纯文本的协议。没有测试就没有 100% 的发言权,所以做了如下的测试 阅读全文 >>

自定义类加载器动态加载 JDBC 驱动

我们可以用自定义的 URLClassLoader 从外部动态加载类,并使用它。但数据库驱动的管理类 DriverManager 却不比较苛刻,不承认非当前应用系统加载器加载的驱动类。见 DriverManager 的 JavaDoc 

When the method getConnection is called, the DriverManager will attempt to locate a suitable driver from amongst those loaded at initialization and those loaded explicitly using the same classloader as the current applet or application

对于有有应用自定义类加载器加载数据库驱动类的需求时,就要对原 Driver 简单包装一下。继续往后会说介绍为什么要这么做。

说明一下,DriverManager 能够根据 JDBC 连接字符串匹配到驱动类,所以一般来说都不需要显式调用 DriverManager.registerDriver() 方法。

先看 DriverManager 在应用外部驱动类时会出现什么情况 阅读全文 >>