小径分叉的花园

Pain builds character.

一个velocity.log引发的问题

| Comments

问题描述

最近在项目中采用了velocity最一些简单的规则做动态解析,大概的代码如下(经过一定程度简化):

image

这段代码在开发环境下运行地一直很正常,直到昨天部署到了测试环境了,引发了一个诡异的NullPointException

原因分析

首先经过一位同事的Debug,发现在做evaluate,把模板解析成NodeTree,获取parser的过程中,parserPool的值是null,从而引发了NullPointException,代码如下:

image

正常情况下,velocity初始化之后,parserPool都应该正常初始化了,不应该出现parserPoolnull的情况,只有一种可能是,parserPool初始化失败了。

继续跟踪velocity初始化的代码:

image

还没有debug到initializeParserPool()这一步,在initializeLog()这一步velocity就抛出了异常:

1
FileNotFoundException: velocity.log (Permission denied)

那么分析一下velocity日志初始化的过程:

  1. velocity的日志系统的初始化都在LogManager.createLogChute()这个方法里面
  2. 首先,在velocity的jar里面的velocity.properties,配置了一个 runtime.log.logsystem.class = org.apache.velocity.runtime.log.AvalonLogChute, org.apache.velocity.runtime.log.Log4JLogChute, org.apache.velocity.runtime.log.CommonsLogLogChute, org.apache.velocity.runtime.log.ServletLogChute, org.apache.velocity.runtime.log.JdkLogChute,velocity初始化的时候,会遍历这个列表,一个个尝试,而在我们的项目中,有log4j,所以采用的就是第二个org.apache.velocity.runtime.log.Log4JLogChute
  3. 在初始化org.apache.velocity.runtime.log.Log4JLogChute的过程中,velocity会再次去velocity.properties里面找runtime.log = velocity.log,就是运行时日志的路径,而log4j创建appender的时候,就直接采用new File("velocity.log")的方式在文件系统中创建一个日志文件。
  4. 大家知道new File("velocity.log")这种方式是在usr.dir下面直接创建文件的,由于我们的应用是jetty,usr.dir指向的目录是JETTY_HOME

那么,问题的原因应该大概清楚了:

  • 在测试环境上jetty安装的目录是在/usr下面的,普通的用户是对/usr目录没有写权限的,所以出现了Permission Denied
  • 但是对于本机开发环境,尽管jetty也是安装在/usr下面,但是由于/usr目录的权限已经被手工的设置为了任何人都能访问,所以没有出现了Permission Deinied的情况。

问题解决

只需要关闭掉velocity的运行时日志,在ve.init()前加上以下一段代码即可:

1
ve.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new NullLogChute());

总结

大家以后再使用velocity时候,需要特别注意下velocity.log的这个问题,我到Google上搜索了一把,其实这个问题还是挺普遍的,包括之前一位同事在去年做jetty迁移的时候也遇到了同样的问题,总之,以后使用的时候小心吧。

又拍?Flickr? Github才是最适合Geek的图床服务!

| Comments

Yupoo & Flickr Sucks!

一直以来我都在为我的博客寻找最佳的图床服务,最开始用Wordpress自带的,但是明显一旦博客迁移起来太不方便。

后来我试了国内的又拍,不得不说,又拍真心不错,非常能满足我的要求,直到有一天我想要做一个命令行工具,希望可以通过这个工具直接上传图片到又拍上去,但是当我一次又一次尝试和又拍沟通获取他们的API Key而杳无音讯时,不得已,我又转而使用Flickr。

Flickr可以非常方便地申请到API Key,于是我马上着手做了一个工具:Nausicca,但是前几天当我把一些带有透明背景的图片上传到Flickr上去以后,发现压缩后的图片透明背景已经没有了,阴影被黑色取代,再仔细研究一番后发现只能是Flickr Pro帐户才能够下载到原图,真是令人伤心。

Github Rocks!

直到昨天晚上,我在泡着脚,看着美女,突然发现其实可以用Github作为博客的图床服务,只需要做:

  • 在Github上建立一个公开的项目,这个项目专门用来上传图片用。
  • 写一个脚本接受一张图片的路径,然后自动push到你的Github上。
  • 当你把图片push到Github上去以后,以下地址${project_address}/raw/master/${fileName} 便是图片对应的地址,其实${project_address}是你的项目的地址,${fileName}是你上传的图片的文件名。

怎么样?简单吧,当然把图片放在Github上有一定的缺点:

  • 只有原图,没有其他的尺寸提供。
  • Github说不定什么时候就完全被墙了。

当然,优点也是多多:

  • 有原图尺寸提供啊!
  • 备份方便啊,只要clone一下就备份完成了!
  • 够Geek啊,命令行上传多方便啊!

当然,个人建议可以将Github和Flickr结合起来用,只需要原图的时候可以用Github,需要各种尺寸的图片的时候可以用Flickr(当然,你要手工压缩也可以嘛!这样就直接用Github吧)。

The End

image

Lullaby – Loreena McKennit

| Comments

事隔四年多,又听到了这首Loreena McKennit的《Lullaby》。其实第一次听到这首歌,吸引我的并不是Loreena McKennit的声音,而是这首歌中的诗歌朗诵部分,铿锵有力,震撼人心。

今天特地去找了一下,这首歌中的诗来自于英国诗人Willian Blake的诗集《Poetical Sketches》,诗的题目叫《Prolugue, intended for a dramatic piece of King Edward the Fourth》,朗诵这首诗的是Douglas Campbell

这首歌的试听链接是:

歌词部分

O FOR a voice like thunder, and a tongue

To drown the throat of war! When the senses

Are shaken, and the soul is driven to madness,

Who can stand? When the souls of the oppressed

Fight in the troubled air that rages, who can stand?

When the whirlwind of fury comes from the

Throne of God, when the frowns of His countenance

Drive the nations together, who can stand?

When Sin claps his broad wings over the battle,

And sails rejoicing in the flood of death;

When souls are torn to everlasting fire,

And fiends of hell rejoice upon the slain,

O who can stand? O who hath caused this?

O who can answer at the throne of God?

The Kings and Nobles of the land have done it!

Hear it not, Heaven, thy ministers have done it!

Willian Blake Prolugue Intended for a Dramatic Piece of King Edward the Fourth

ASM学习笔记

| Comments

ASM是什么?

简单地说,ASM是一个用于Java字节码操作的类库,它提供了对字节码上一层的抽象,即Java Class文件中的各个部分进行操作的API。ASM这个名字来自于C的关键字__asm__

ASM可以干什么?

先来看一幅图,如下图:

image

如果我们把我们平常从Java文件到运行的过程分成编译器和Java虚拟机两部分,那么Class文件就是两者之间产生联系的桥梁。一般情况下,Class文件是通过javac编译器产生的,然后通过类加载器加载到虚拟机内,再通过执行引擎去执行。现在有了ASM们就可以它的API直接生成符合Java虚拟机规范的Class字节流,这样,ASM做的事情一定程度上正是javac解释器做的工作。

那么,我们就可以通过ASM来实现诸如代码生成,代码混淆,代码转换等等以字节码为操作目标的工作。

如何使用ASM

ASM提供了两套API供使用者使用,一套叫Core API,是基于事件的方式对字节码进行处理;另一套叫Tree API,是基于对象的方式对字节码进行处理。如果你熟悉XML解析,那么实际上Core API就是SAX这种处理模式,而Tree API就是DOM这种处理模式。

Core API

使用Core API进行对字节码进行处理一般需要三个部分:

  • 一个事件的生产者,用于产生各种事件,通常这会是一个ClassReader
  • 一个事件的消费者,用于消费各种事件,通常这会是一个ClassWriter
  • 若干个事件的过滤器,这些过滤器可以对感兴趣的事件进行过滤来处理,这通常会是一些ClassVisitor

其处理过程如下图所示:

image

看下面一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ASMTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream(new File("/Users/apple/Desktop/Test.class"));
        ClassReader cr = new ClassReader(fis);
        ClassWriter cw = new ClassWriter(cr, 0);
        ClassVisitor cv = new ClassVisitor(Opcodes.ASM4, cw) {
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                if ((access & Opcodes.ACC_PUBLIC) != 0) {
                    access = (access ^ Opcodes.ACC_PUBLIC) + Opcodes.ACC_PRIVATE;
                }

                return cv.visitMethod(access, name, desc, signature, exceptions);
            }
        };
        cr.accept(cv, 0);
        FileOutputStream fos = new FileOutputStream(new File("/Users/apple/Desktop/result/Test.class"));
        fos.write(cw.toByteArray());
        fos.flush();
        fos.close();
    }
}

这个例子先是从文件流中读入一个待处理的Class文件,然后new了ClassReader,作为事件源,然后new了一个ClassWriter,作为事件的接收者,还实现了一个ClassVisitor,这个ClassVisitor将所有的public方法变成了private的方法。接着通过调用cr.accept方法来触发事件,通过cw.toByteArray来拿到处理后的字节码并且输出到文件。

从前面的图以及代码可以看出,采用Core API处理字节码,其实就是通过继承ClassVisitor,并且覆盖ClassVisitor中的对应的方法来对特定的事件进行处理的过程,其实这里的事件基本上对应到了Class文件中的各个部分,除了常量池部分,所以如果了解了Class文件的结构,那么用Core API处理起来应该得心应手。

除了ClassVisitor之外,Core API还提供了MethodVisitorFieldVisitorAnnotationVisitor来对方法,字段和注解操作。

Tree API

前面说过,Tree API是基于对象的方式来处理字节码,Tree API的最核心的一个类就是ClassNode,它就代表了一个Java Class文件,它里面的属性对应到了一个Class文件的各个部分,如下图:

image

使用Tree API来创建一个类的过程,其实就是设置ClassNode的属性的一个过程,一个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ASMTest {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws IOException {
        ClassNode cn = new ClassNode();
        cn.name = "ASMInterface";
        cn.access = Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE;
        cn.superName = Type.getInternalName(Object.class);
        cn.interfaces.add(Type.getInternalName(Runnable.class));
        cn.version = Opcodes.V1_6;
        cn.sourceFile = "ASMInterface.java";
        MethodNode mn = new MethodNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC, "stop", "()V", null, null);
        cn.methods.add(mn);
        ClassWriter cw = new ClassWriter(0);
        cn.accept(cw);
        FileOutputStream fos = new FileOutputStream(new File("/Users/apple/Desktop/result/ASMInterface.class"));
        fos.write(cw.toByteArray());
        fos.flush();
        fos.close();
    }
}

上面的代码,新建了一个ClassNode,用来创建一个继承了java.lang.Runnable接口的接口ASMInterface,它包含了一个方法public void stop(),最后将生成的字节码通过ClassWriter输出到文件。

和创建类一样,通过Tree API修改一个类也只需要修改ClassNode的属性。如果你要修改方法,字段或者注解,那么可以通过ClassNode拿到MethodNodeFieldNodeAnnotationNode来进行对应的修改。

什么时候使用Core API,什么时候使用Tree API?

Core API和Tree API其实各有优缺点:

  • Core API的优势是处理速度快,占用内存小,因为它不需要在内存中将整个Class文件表示出来,缺点是基于事件的方式处理,如果错过一个事件,那么就是过了这个村,没有这个店了,这样如果需要实现诸如将特定GOTO出插入其他指令,就会比较麻烦,因为GOTO可以跳转到之前的指令,但是之前的指令的事件已经被处理了,到时候只能再次触发一遍事件来处理。
  • Tree API的优势就是Core API的劣势,对于上面提到的GOTO的这种情况,Tree API处理起来就轻松了很多,因为在内存中有Class文件的完整表示,随便什么样的顺序去改都是没有问题的。缺点就是占用内存比较大,处理速度比较慢。

如果你查看ClassNode的源代码,那么可以发现ClassNode事实上继承了ClassVisitor。那么,我们就可以将在实际操作的时候将Core API和Tree API结合起来,灵活运用各自的优缺点去解决问题。

辅助工具类

ASM除了提供了Core API和Tree API两套API以外,还提供了几个比较实用的工具类

CheckClassAdapter

实际上,用ASM生成的字节码可能并不符合Java虚拟机规范的,如果需要检查生成的字节码符不符合规范,那么可以用CheckClassAdapter作为一个ClassVisitor加入到ClassVisitor链中,如果字节码不符合规范,那么CheckClassAdapter就会抛出异常。

ASMifier

ASM作为一个字节码操作工具,相对于其他的字节码操作工具,比如Javassist,写起来还是比较烦琐的,如果你已经有了一个Class文件,想要知道如何通过ASM生成这个Class文件,那么就可以直接用ASMifier这个类,通过这个类,可以直接生成出生成目标类的ASM代码,一定程度上简化了直接手写ASM代码的繁琐工作。

ASMifier可以直接通过命令行来使用,比如那我们刚才生成的那个ASMInterface为例:

image

可以看到ASMifier直接将生成ASMInterface所需要的ASM代码直接打印出来了。

LocalVariableSorter

假设你要往一个方法里面加入一个本地变量,那么你就需要将这个变量加入到本地变量表的最后,遗憾的是,本地变量表的大小只有当你在调用visitMaxs的时候才知道,通常,这个时候已经到了方法的结尾处,再想加本地变量已经晚了,现在通过LocalVariableSorter这个ClassVisitor,你就可以非常简单插入一个本地变量。

总结

ASM作为其他很多的代码生成工具的所依赖的类库,其处理速度快,体积小,并且通过提供了对Class文件各个部分的操作API,可以让你对Class文件进行最细致的修改。但是对于大段大段的代码生成,使用ASM还是显得有些繁琐,即使有ASMifier,还是不如Javassist来的直观,使用过程中最好根据自己的需求进行取舍。

其他说明:

  • 这篇文章中的代码都是基于ASM 4.0的,ASM 4.0和ASM 3.X的代码略有不同,使用过程中需要注意。
  • ASM的官方网站是:http://asm.ow2.org/

魔龙的狂舞

| Comments

image

花了将近四个月的时间,终于将乔治・马丁的冰与火之歌的第五部《魔龙的狂舞》英文版啃完了,这大概是我迄今为止看过的最长的英文小说了,看完这部小说收获最大的地方应该就是对自己的英语阅读能力稍微有了一点信心,后面大概会持续地去看一些英文原版的著作。

美国丽人

| Comments

美国丽人

从来不知道自己想成为什么人,但是我一直努力避免成为一无所是的人

Nausicaa:一个把图片传到Flickr的命令行工具

| Comments

懒惰是程序员的美德

前几天看了一片博客,灵感一现,发现平时发博客的时候,把图片传到图床上去要打开浏览器,再打开Flickr或者Yupoo,再上传照片,再找到照片对应的链接,然后这个链接才能够拿来用。看看吧,这个过程是多么的繁琐,作为一名把懒惰当成第一美德的程序员,我马上就想要折腾出一个命令行工具,来把图片上传到相应的图床,现在这个工具就是Nausicaa。

运行效果

那么,来看看上传的效果吧:

image

Nausicaa在你把图片上传之后,会把图片对应的各个尺寸的静态链接也返回回来,让你可以马上拿到图片的链接。

环境

要使用Nausicaa,最好在Unix-like系统下使用(Windows下谁会去用命令行?)。

另外Nausicaa还需要JRE 6或者以上的版本

下载与使用

Nausicaa的下载地址是:https://github.com/downloads/khotyn/Nausicaa/nausicaa.jar

使用说明,可以直接进项目主页看:https://github.com/khotyn/Nausicaa

Java中Integer的大小是int的几倍

| Comments

今天看到一个不错的PPT:Build Memory-efficient Java Applications,开篇便提出了一个问题,在Hotspot JVM中,32位机器下,Integer对象的大小是int的几倍?

我们都知道在Java语言规范已经规定了int的大小是4个字节,那么Integer对象的大小是多少呢?要知道一个对象的大小,那么必须需要知道对象在虚拟机中的结构是怎样的,来看看Hotspot中对象在内存中的结构:

对象结构

从上面的这张图里面可以看出,对象在内存中的结构主要包含以下几个部分:

  • Mark Word:对象的Mark Word部分占4个字节,其内容是一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。
  • Class对象指针:Class对象指针的大小也是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址
  • 对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节
  • 对齐:最后一部分是对齐填充的字节,按8个字节填充。

根据上面的图,那么我们可以得出Integer的对象的结构如下:

Integer内存结构地址

Integer只有一个int类型的成员变量value,所以其对象实际数据部分的大小是4个字节,然后再在后面填充4个字节达到8字节的对齐,所以可以得出Integer对象的大小是16个字节。

因此,我们可以得出Integer对象的大小是原生的int类型的4倍

关于对象的内存结构,需要注意数组的内存结构和普通对象的内存结构稍微不同,因为数据有一个长度length字段,所以在对象头后面还多了一个int类型的length字段,占4个字节,接下来才是数组中的数据,如下图:

数组对象内存结构

关于对象内存布局更多的内容,可以看这篇文章:Java Objects Memory Structure

Varamyr – 让Nook 2也可以轻松阅读中文的epub

| Comments

好啦,这个标题有点标题党的嫌疑,前几天入手了Nook 2,但是苦于Nook对中文支持并不好,看中文的epub直接显示方块或者问号之类的乱码。

网上看了下这个问题的大致解决方法有两个:一个是Root掉Nook;另一个往epub文件中塞入一段CSS,再在Nook中看的时候只需要选择Publisher Defaults就可以正常显示中文。

第一个方法风险太高,可能导致Nook变砖;

第二个方法的话可以用Calibre之类的工具来完成,但是Calibre这个东西本身太重了,每次打开关闭很耗时间,UI又丑,实在不想用。

所以,我需要一个命令行的工具来完成往epub中塞入一段CSS这个任务,这样可以和*nix下的其他工具结合起来使用,检测一个文件夹中如果新加入了epub文件,就直接进行转换,让整个过程完全自动化。现在这个命令行工具就是Varamyr(Varamyr是马丁大爷的冰与火之歌的第五部《魔龙的狂舞》序言里面的一个狼灵,打酱油的角色。)

运行Varamyr你需要

  1. JRE环境
  2. *nix系统,不支持windows也不会支持windows
  3. 下载varamyr.jar

如何使用?

  1. 运行命令:java -jar varamyr.jar <path-to-epub-file><path-to-epub-file>为你需要修改的epub文件的路径
  2. 看看在你修改的epub文件的路径下是不是多了一个类似xxxx-varamyr.epub的文件。

源代码 & 原理

Nook 2 入手记

| Comments

经过了2个多月的挣扎,我终于了入手了一款电子书:Barnes & Noble 出品的 Nook Simple Touch(即Nook 2),之所以选择Nook 2,是因为Kindle 3已经停产,现在价格比较高,Kindle 4和Kindle Touch的外观我实在是不喜欢,一股山寨味,于是在淘宝上找了一个店下单了Nook 2,前天晚上下单,昨天从深圳发货,今天早上就到手上了,快递非常迅速。

image

拿到手以后首先是外包装,白色,很小,右上角有一个THE ALL NEW NOOK的字样,表示是全新的Nook,而不是官翻的机子。

image

打开包装以后里面是就一只Nook和一个USB的连接线,没有其他配件了。首次使用需要充足够的电才能够开机。

image

充电中,虽然屏幕上写着要等15分钟,但是实际上要差不多1个小时。等冲到了足够的电Nook会自己启动,然后就是一堆设置,连接上Wifi去激活Nook。

image

看看背面,Nook的开关键在背后,背面的中间是凹进去的,握起来比较舒服。

现在来看看Nook看书的效果到底如何吧:

image

这个是看英文的epub格式的电子书的照片,可以看到英文字体效果还是非常不错的。

image

这张是看中文的epub格式的电子书,非常坑爹,直接显示方块了,Nook原生并不支持中文,需要用Root的方式去解决。

image

然后是英文的pdf,可以看到Nook已经对pdf的文字进行了重新排列,以适合Nook的显示大小。

image

最后是中文的pdf,看起来效果也还可以。

另外再讲讲操作体验,Nook是红外线触摸屏的,触摸屏的体验非常不错,翻页非常灵敏,触摸屏用来查单词也是非常方便,只要点住单词即可,除了触摸屏以外,Nook在左右两边还提供了4个翻页键,但是遗憾的是按起来需要用点力,感觉不是很好。

屏幕的显示方面,效果还马马虎虎,因为Nook 2采用的是局部刷新,翻了6页以后才全刷,所以翻页后可以看到一些残影,这个非常让人不爽,不知道有没有什么办法可以解决这个问题。

至于和Kindle比较哪个好,哪个差,我也说不上来,总体来说,Nook 2还算是令人满意吧,如果你也要买一个电子书,不妨页考虑下Nook。