Yui Compressor Java压缩实践

  最近在做一个项目,需求大概是将公司每个应用的js/css统一管理,更新和发布到cdn上。本文只分享在对js/css进行压缩处理时遇到的经验和问题,其他内容不多做介绍。

  首先,因为每个应用的css/js都在svn库中保存,需要将其“checkout”到本地或者db中。然后使用者根据需要再选择相关文件发布。发布过程大概是生成本地文件,然后使用一些优秀的×nix工具将其同步到cdn上面。本文需要关注的是用户点击发布,生成发布文件的过程,因为我们的YUI compress 操作在这里进行。

  其次,介绍一下YUI的使用。因为工程的依赖是maven控制管理的,所以需要添加依赖:当然你也可以直接去下载对应的jar包添加到你的path目录中使用。

<dependency> <groupId>com.yahoo.platform.yui</groupId> <artifactId>yuicompressor</artifactId> <version>2.3.6</version> </dependency>      

    做前端开发的同学会比较清楚,yuicompressor主要是供他们命令行形式对js/css文件进行压缩的,这样出现问题的可能性也非常之少---待会我们会告诉你为什么会有如此一说。

下载完jar包后,你会看到一个YUICompressor类,只有一个main方法供java命令行使用,这个api必须放弃了。另外,你还会发现JavaScriptCompressor和CssCompressor类,他们才是我们真正需要使用的接口。

先看一下使用方法吧:

CssCOmpressor:

public void compress(String code, Writer writer) { try { Reader in = new InputStreamReader(IOUtils.toInputStream(code)); CssCompressor compressor = new CssCompressor(in); compressor.compress(this.writer, -1); writer.flush(); } catch (Exception e) { logger.error("css compress error:", e); } finally { IOUtils.closeQuietly(this.writer); } }  

JavaScriptCompressor:

@Override public void compress(String code, Writer writer) { Reader in = null; try { in = new InputStreamReader(IOUtils.toInputStream(code)); JavaScriptCompressor compressor = new JavaScriptCompressor(in, new ErrorReporter() { public void warning(String message, String sourceName, int line, String lineSource, int lineOffset) { if (line < 0) { System.err.println("/n[WARNING] " + message); } else { System.err.println("/n[WARNING] " + line + ':' + lineOffset + ':' + message); } } public void error(String message, String sourceName, int line, String lineSource, int lineOffset) { if (line < 0) { System.err.println("/n[ERROR] " + message); } else { System.err.println("/n[ERROR] " + line + ':' + lineOffset + ':' + message); } } public EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset) { error(message, sourceName, line, lineSource, lineOffset); return new EvaluatorException(message); } }); compressor.compress(writer, -1, true, false, false, false); } catch (Exception e) { logger.error("yui js compress error:", e); } finally { IOUtils.closeQuietly(writer); } } 

接口看起来挺简单,但是用起来感觉还是挺别扭的。

压缩接口已经完成,看来工作已经结束了,只需要用一个dispatch调用不同的compress方法就可以了。

其实,问题你才刚刚发现:压缩出来的结果有乱码,究其原因是因为svn上的js/css文件编码格式有些是gbk,有些是utf8格式的。

前端使用命令行的时候,在命令行中指定文件编码就能搞定这个问题。我们的问题就是不清楚文件编码格式,第一个想法就是探测一下文件的编码格式,查了一下居然真有java版本的charset:Jcharset,但是貌似探测的准确性又不高,所以只好放弃之。因为文件格式只有utf8和gbk两种格式,而一般的utf8格式文件一般是带着BOM信息的,这样的文件刚开始的三个字节永远是一样的,所以如果能根据这个规律探测文件编码格式,那似乎问题就解决拉。但是很可惜,我们的文件是不带BOM信息的,所以只好统计整个文件的字节流,看符合utf格式的字节多还是非utf8格式的字节多,如果是前者,那这个文件就很可能是utf8格式,根据这个规律,我们发现,探测的准确度还是比较高的,代码如下:

public static boolean isUTF8(byte[] data) { int count_good_utf = 0; int count_bad_utf = 0; byte current_byte = 0x00; byte previous_byte = 0x00; for (int i = 1; i < data.length; i++) { current_byte = data[i]; previous_byte = data[i - 1]; if ((current_byte & 0xC0) == 0x80) { if ((previous_byte & 0xC0) == 0xC0) { count_good_utf++; } else if ((previous_byte & 0x80) == 0x00) { count_bad_utf++; } } else if ((previous_byte & 0xC0) == 0xC0) { count_bad_utf++; } } return (count_good_utf > count_bad_utf); } 

那么什么时候探测文件编码呢?等压缩文件的时候再去探测,可能你的文件已经乱码,因为你很可能会使用new String或者其他操作,这些操作都是会使用系统默认的编码的;比较正确的是在svn更新下来时,获取文件字节流直接探测,然后保存文件的编码格式;以后读写文件内容时都用这个编码去操作,就不会有问题拉。

然而,如果是这样一种场景:utf格式的js文件调用了另外一个gbk格式的js文件,这个时候可能因为编码不一致会导致调用失败;终极的解决方案是最后压缩出来的文件编码格式都是统一的,比如unicode格式的,java提供了一个命令行native2ascii会将不同编码格式的文件统一编码成ascii文件,这样同样的内容,不一样的编码格式,压缩出来的文件就是一模一样的了。

调用命令行代码如下:

Profiler.start("native2 ascii envert:"); String absolutePath = destinationFile.getAbsolutePath(); String line = new StringBuilder().append(this.commandPath).append(" ").append(absolutePath).append(" ").append(absolutePath).append(" -encoding ").append(page.getEncoding()).toString(); CommandLine cmdLine = CommandLine.parse(line); DefaultExecutor executor = new DefaultExecutor(); int exitValue = executor.execute(cmdLine); Profiler.release(); logger.error("native2 ascii envert time"+Profiler.getDuration()); Profiler.start("YUI compress:"); if (exitValue == 0) { FileReader reader = new FileReader(destinationFile); String content = IOUtils.toString(reader); IOUtils.closeQuietly(reader); Writer writer = new FileWriter(destinationFile); CompressorAdapter.write(page.getFileName(), content, writer); } Profiler.release(); logger.error("YUI compress time"+Profiler.getDuration()); 

有一点需要说明,这个命令行的执行效率不是非常好,有待优化,我们使用的是apache commons.exec的工具调用本地java命令行。

注意:

如果你在使用YUI compress 的时候报数组越界的异常,那你的yui版本可能太高了,我们使用的2.3.6的版本是不会报这种异常的。 


你可能感兴趣的:(java,exception,String,前端开发,yui,byte)