大家都知道,在Web应用程序中,为了节省网络开销,往往吧多个小的js文件整合成一个大的js文件,吧多个小的css文件整合成一个大的js文件,这样原本N次小文件的请求就可以合并成单次的网络请求。最典型的做这件事情的工具是大名鼎鼎的yui-compressor.

其实在Liferay中,我们为了达到合并css,js的目的,用了不同于yui-compressor的方法,这就是我们的主角 MinifierFilter.

既然是Filter,那么它肯定有filter-mapping,我们轻易的在liferay-web.xml中找到了Filter的定义和Filter的mapping.

  1. ...
  2. <filter>
  3. <filter-name>Minifier Filter</filter-name>
  4. <filter-class>com.liferay.portal.servlet.filters.minifier.MinifierFilter</filter-class>
  5. </filter>
  6. <filter> <filter-name>Minifier Filter - JSP</filter-name> <filter-class>com.liferay.portal.servlet.filters.minifier.MinifierFilter</filter-class> <init-param> <param-name>url-regex-pattern</param-name> <param-value>.+/(aui_lang|barebone|css|everything|main)\.jsp</param-value> </init-param> </filter>
  7. ...
  8. <filter-mapping>
  9. <filter-name>Minifier Filter</filter-name>
  10. <url-pattern>*.css</url-pattern>
  11. </filter-mapping>
  12. <filter-mapping>
  13. <filter-name>Minifier Filter</filter-name>
  14. <url-pattern>*.js</url-pattern>
  15. </filter-mapping>
  16. <filter-mapping> <filter-name>Minifier Filter - JSP</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping>
  17. ...

所以,我们可以看到,当客户端对Liferay服务器上请求任意css或者javascript资源时候,都会被这个MinifierFilter所过滤,我们现在就来看下庐山真面目。

因为MinifierFilter最终实现了Filter接口,而doFilter方法在父类的父类BaseFilter中定义,这个doFilter仅仅是调用processFilter()方法,

  1. public void doFilter(
  2. ServletRequest servletRequest, ServletResponse servletResponse,
  3. FilterChain filterChain)
  4. throws IOException, ServletException {
  5. try {
  6. HttpServletRequest request = (HttpServletRequest)servletRequest;
  7. HttpServletResponse response = (HttpServletResponse)servletResponse;
  8. processFilter(request, response, filterChain);
  9. }
  10. catch (IOException ioe) {
  11. throw ioe;
  12. }
  13. catch (ServletException se) {
  14. throw se;
  15. }

所以这就是我们的入口:

  1. protected void processFilter(
  2. HttpServletRequest request, HttpServletResponse response,
  3. FilterChain filterChain)
  4. throws Exception {
  5. Object minifiedContent = getMinifiedContent(
  6. request, response, filterChain);
  7. if (minifiedContent == null) {
  8. minifiedContent = getMinifiedBundleContent(request, response);
  9. }
  10. if (minifiedContent == null) {
  11. processFilter(MinifierFilter.class, request, response, filterChain);
  12. }
  13. else {
  14. if (minifiedContent instanceof File) {
  15. ServletResponseUtil.write(response, (File)minifiedContent);
  16. }
  17. else if (minifiedContent instanceof String) {
  18. ServletResponseUtil.write(response, (String)minifiedContent);
  19. }
  20. }
  21. }


首先,它会去执行06-07行的getMinifiedContent()方法,它会调用以下代码:

在getMinifiedContent()方法中,它会调用2个方法来分别最小化css和最小化js.

如下:

  1. protected Object getMinifiedContent(
  2. HttpServletRequest request, HttpServletResponse response,
  3. FilterChain filterChain)
  4. throws Exception {
  5. ..
  6. ..
  7. String minifiedContent = null;
  8. if (realPath.endsWith(_CSS_EXTENSION)) {
  9. if (_log.isInfoEnabled()) {
  10. _log.info("Minifying CSS " + file);
  11. }
  12. minifiedContent = minifyCss(request, response, file);
  13. response.setContentType(ContentTypes.TEXT_CSS);
  14. FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
  15. }
  16. else if (realPath.endsWith(_JAVASCRIPT_EXTENSION)) {
  17. if (_log.isInfoEnabled()) {
  18. _log.info("Minifying JavaScript " + file);
  19. }
  20. minifiedContent = minifyJavaScript(file);
  21. response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
  22. FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_JAVASCRIPT);
  23. }
  24. else if (realPath.endsWith(_JSP_EXTENSION)) {
  25. if (_log.isInfoEnabled()) {
  26. _log.info("Minifying JSP " + file);
  27. }
  28. StringServletResponse stringResponse = new StringServletResponse(
  29. response);
  30. processFilter(
  31. MinifierFilter.class, request, stringResponse, filterChain);
  32. CacheResponseUtil.setHeaders(response, stringResponse.getHeaders());
  33. response.setContentType(stringResponse.getContentType());
  34. minifiedContent = stringResponse.getString();
  35. if (minifierType.equals("css")) {
  36. minifiedContent = minifyCss(
  37. request, response, realPath, minifiedContent);
  38. }
  39. else if (minifierType.equals("js")) {
  40. minifiedContent = minifyJavaScript(minifiedContent);
  41. }
  42. FileUtil.write(
  43. cacheContentTypeFile, stringResponse.getContentType());
  44. }
  45. else {
  46. return null;
  47. }
  48. FileUtil.write(cacheDataFile, minifiedContent);
  49. return minifiedContent;
  50. }

minifyCSS:

从第12行可以看出,如果判断扩展名是.css,那么需要吧文件minify一下,并且设置content-type为text/css,最后把这个文件放入cacheContentTypeFile中。

我们来看下minifyCSS到底做了什么事情:

  1. protected String minifyCss(
  2. HttpServletRequest request, HttpServletResponse response, File file)
  3. throws IOException {
  4. String content = FileUtil.read(file);
  5. content = aggregateCss(file.getParent(), content);
  6. return minifyCss(request, response, file.getAbsolutePath(), content);
  7. }

从这里可以清楚的看出,

05行是先吧这个css文件的内容通过FileUtil读出来,其实这个FileUtil的读的方式会去除所有的换行,参见最终调用的FileImpl的read方法:

  1. public String read(File file, boolean raw) throws IOException {
  2. byte[] bytes = getBytes(file);
  3. if (bytes == null) {
  4. return null;
  5. }
  6. String s = new String(bytes, StringPool.UTF8);
  7. if (raw) {
  8. return s;
  9. }
  10. else {
  11. return StringUtil.replace(
  12. s, StringPool.RETURN_NEW_LINE, StringPool.NEW_LINE);
  13. }
  14. }

然后把去除了所有换行的css文件的内容存入到变量content中。

07行会调用aggregateCSS来对这个css文件的内容做进一步处理,如何处理呢,我们看代码:

  1. public static String aggregateCss(String dir, String content)
  2. throws IOException {
  3. StringBuilder sb = new StringBuilder(content.length());
  4. int pos = 0;
  5. while (true) {
  6. int commentX = content.indexOf(_CSS_COMMENT_BEGIN, pos);
  7. int commentY = content.indexOf(
  8. _CSS_COMMENT_END, commentX + _CSS_COMMENT_BEGIN.length());
  9. int importX = content.indexOf(_CSS_IMPORT_BEGIN, pos);
  10. int importY = content.indexOf(
  11. _CSS_IMPORT_END, importX + _CSS_IMPORT_BEGIN.length());
  12. if ((importX == -1) || (importY == -1)) {
  13. sb.append(content.substring(pos, content.length()));
  14. break;
  15. }
  16. else if ((commentX != -1) && (commentY != -1) &&
  17. (commentX < importX) && (commentY > importX)) {
  18. commentY += _CSS_COMMENT_END.length();
  19. sb.append(content.substring(pos, commentY));
  20. pos = commentY;
  21. }
  22. else {
  23. sb.append(content.substring(pos, importX));
  24. String importFileName = content.substring(
  25. importX + _CSS_IMPORT_BEGIN.length(), importY);
  26. String importFullFileName = dir.concat(StringPool.SLASH).concat(
  27. importFileName);
  28. String importContent = FileUtil.read(importFullFileName);
  29. if (importContent == null) {
  30. if (_log.isWarnEnabled()) {
  31. _log.warn(
  32. "File " + importFullFileName + " does not exist");
  33. }
  34. importContent = StringPool.BLANK;
  35. }
  36. String importDir = StringPool.BLANK;
  37. int slashPos = importFileName.lastIndexOf(CharPool.SLASH);
  38. if (slashPos != -1) {
  39. importDir = StringPool.SLASH.concat(
  40. importFileName.substring(0, slashPos + 1));
  41. }
  42. importContent = aggregateCss(dir + importDir, importContent);
  43. int importDepth = StringUtil.count(
  44. importFileName, StringPool.SLASH);
  45. // LEP-7540
  46. String relativePath = StringPool.BLANK;
  47. for (int i = 0; i < importDepth; i++) {
  48. relativePath += "../";
  49. }
  50. importContent = StringUtil.replace(
  51. importContent,
  52. new String[] {
  53. "url('" + relativePath,
  54. "url(\"" + relativePath,
  55. "url(" + relativePath
  56. },
  57. new String[] {
  58. "url('[$TEMP_RELATIVE_PATH$]",
  59. "url(\"[$TEMP_RELATIVE_PATH$]",
  60. "url([$TEMP_RELATIVE_PATH$]"
  61. });
  62. importContent = StringUtil.replace(
  63. importContent, "[$TEMP_RELATIVE_PATH$]", StringPool.BLANK);
  64. sb.append(importContent);
  65. pos = importY + _CSS_IMPORT_END.length();
  66. }
  67. }
  68. return sb.toString();
  69. }

其实这段代码非常简单,它就是找出页面上所有的css注释 /* */,然后把这些注释去除,然后找出页面上@import(url=)的这种外部css文件,递归的调用aggregateCSS直到他们不含有外部引入标记,然后把这些文件的内容(已经被去除了注释,换行符等)插入到引入它们的css文件中。

minifyJavaScript:

从第23行可以看出,当遇到文件扩展名是.js时,它就会调用minifyJavaScript方法来最小化这个js文件,并且设置content-type为text/javascript,最后把minify之后的文件存入cacheContentTypeFile。

我们来看下minifyJavaScript到底做了什么事情:

  1. protected String minifyJavaScript(File file) throws IOException {
  2. String content = FileUtil.read(file);
  3. return minifyJavaScript(content);
  4. }

它首先还是利用FileUtil来去除换行(见minifyCSS部分对这个方法的讲解),然后对于已经没有换行符的js文件继续调用minifyJavaScript():

  1. protected String minifyJavaScript(String content) {
  2. return MinifierUtil.minifyJavaScript(content);
  3. }

它又去调用MinifierUtil工具类方法来完成任务,最终执行任务的是MinifierUtil的_minifyJavaScript方法:

  1. private String _minifyJavaScript(String content) {
  2. UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();
  3. try {
  4. JavaScriptCompressor javaScriptCompressor =
  5. new JavaScriptCompressor(
  6. new UnsyncStringReader(content),
  7. new JavaScriptErrorReporter());
  8. javaScriptCompressor.compress(
  9. unsyncStringWriter, _JS_LINE_BREAK, _JS_MUNGE, _JS_VERBOSE,
  10. _JS_PRESERVE_ALL_SEMICOLONS, _JS_DISABLE_OPTIMIZATIONS);
  11. }
  12. catch (Exception e) {
  13. _log.error("JavaScript Minifier failed for\n" + content);
  14. unsyncStringWriter.append(content);
  15. }
  16. return unsyncStringWriter.toString();
  17. }

它会先创建一个JavaScriptCompressor对象,然后用它来压缩没有换行符的js文件,采用的方式是和yahoo的yui-compressor一样的方式,算法很复杂,没必要一行行看了。

minifyJSP:

从34行可以看到,它会先判断是jsp扩展名,当然了, 它也不会对所有的jsp都生效,它生效的jsp文件都在liferay-web.xml中的这个filter的<init-param>中,具体的就是

barebone.jsp,everything.jsp等因为满足init-param的正则表达式的pattern,所以会通过这个过滤器。从第47-56行可以看出他会吧原来cache的所有被minify处理过的css或者js内容再minify一下然后写入String变量,然后第59-60行利用FileUtil进一步去除换行符,然后把cacheContentFile的内容以最终请求的MIME格式来复写一遍。

当我们在文章一开始的MinifierFilter的processFilter()方法中执行了所有的getMinifiedContent调用后:

  1. Object minifiedContent = getMinifiedContent(
  2. request, response, filterChain);

此时,这个Object minifiedContent的内容就不是null了。

现在我们来执行MinifierFilter的最后2个语句,它可以判断这个minifiedContent是个文件还是字符串,从而让其写入ServletResponse对象中并且返回给客户端,写入的方式是调用ServletResponseUtil工具类:

  1. else {
  2. if (minifiedContent instanceof File) {
  3. ServletResponseUtil.write(response, (File)minifiedContent);
  4. }
  5. else if (minifiedContent instanceof String) {
  6. ServletResponseUtil.write(response, (String)minifiedContent);
  7. }
  8. }

由此大功告成,我们所有的css,js资源文件都得到了最小化,然后整合成单个文件.

高级话题:barebone.jsp和everything.jsp

事实上,Liferay启用了2个配置,一个只引入最少最需要的js文件,最终组合为barebone.jsp,一个是引入所有的js文件,最终组合为everything.jsp,他们可以自由切换,切换代码在top_js.jspf中:

  1. <c:choose>
  2. <c:when test="<%= themeDisplay.isThemeJsFastLoad() %>">
  3. <c:choose>
  4. <c:when test="<%= themeDisplay.isThemeJsBarebone() %>">
  5. <script src="<%= HtmlUtil.escape(PortalUtil.getStaticResourceURL(request, themeDisplay.getPathJavaScript() + "/barebone.jsp", "minifierBundleId=javascript.barebone.files", javaScriptLastModified)) %>" type="text/javascript"></script>
  6. </c:when>
  7. <c:otherwise>
  8. <script src="<%= HtmlUtil.escape(PortalUtil.getStaticResourceURL(request, themeDisplay.getPathJavaScript() + "/everything.jsp", "minifierBundleId=javascript.everything.files", javaScriptLastModified)) %>" type="text/javascript"></script>
  9. </c:otherwise>
  10. </c:choose>
  11. </c:when>
  12. <c:otherwise>

这里可以看出来,切换主要去判断themeDisplay.isThemeJSBarebone,而这个配置在portal.properties中,比如我们服务器设置了javascript.barebone.enabled=true,则开启了barebone,则最后看情况可以有barebone.jsp可以有everything.jsp:

  1. #
  2. # Set this property to false to always load JavaScript files listed in the
  3. # property "javascript.everything.files". Set this to true to sometimes
  4. # load "javascript.barebone.files" and sometimes load
  5. # "javascript.everything.files".
  6. #
  7. # The default logic is coded in com.liferay.portal.events.ServicePreAction
  8. # in such a way that unauthenticated users get the list of barebone
  9. # JavaScript files whereas authenticated users get both the list of barebone
  10. # JavaScript files and the list of everything JavaScript files.
  11. #
  12. javascript.barebone.enabled=true

无论是barebone.jsp还是everything.jsp,他们的bundleId和读取目录都是预先是定好的:

  1. #
  2. # Input a list of comma delimited properties that are valid bundle ids for
  3. # the JavaScript minifier.
  4. #
  5. javascript.bundle.ids=\
  6. javascript.barebone.files,\
  7. javascript.everything.files
  8. #
  9. # Define a bundle directory for each property listed in
  10. # "javascript.bundle.ids".
  11. #
  12. javascript.bundle.dir[javascript.barebone.files]=/html/js
  13. javascript.bundle.dir[javascript.everything.files]=/html/js
  14. #

只不过barebone.jsp合并的js文件少,而everything.jsp文件合并全部的js文件:

barebone.jsp合并并且最小化哪些js文件呢?这也可以从portal.properties文件中找到答案:

  1. javascript.barebone.files=\
  2. \
  3. #
  4. # YUI core
  5. #
  6. \
  7. aui/yui/yui.js,\
  8. \
  9. #
  10. # YUI modules
  11. #
  12. \
  13. aui/anim-base/anim-base.js,\
  14. aui/anim-color/anim-color.js,\
  15. aui/anim-curve/anim-curve.js,\
  16. ...

而everything.jsp合并并且最小化哪些js文件呢?它是由javascript.barebone.files 包含的所有js文件,外加如下列表的不在barebone中的文件:

  1. #
  2. # Specify the list of everything files (everything else not already in the
  3. # list of barebone files).
  4. #
  5. javascript.everything.files=\
  6. \
  7. #
  8. # YUI modules
  9. #
  10. \
  11. aui/async-queue/async-queue.js,\
  12. aui/cookie/cookie.js,\
  13. aui/event-touch/event-touch.js,\
  14. aui/querystring-stringify/querystring-stringify.js,\
  15. \
  16. #
  17. # Alloy modules
  18. #
  19. \
  20. aui/aui-io/aui-io-plugin.js,\
  21. aui/aui-io/aui-io-request.js,\
  22. aui/aui-loading-mask/aui-loading-mask.js,\
  23. aui/aui-parse-content/aui-parse-content.js,\
  24. \
  25. #
  26. # Liferay modules
  27. #
  28. \
  29. liferay/address.js,\
  30. liferay/dockbar.js,\
  31. liferay/layout_configuration.js,\
  32. liferay/layout_exporter.js,\
  33. liferay/session.js,\
  34. \
  35. #
  36. # Deprecated JS
  37. #
  38. \
  39. liferay/deprecated.js

这样一分析下来,整个Liferay框架的静态资源加载文件就非常清晰了。

转载于:https://blog.51cto.com/supercharles888/909098

Liferay MinifierFilter的研究相关推荐

  1. Liferay Portal额外研究-银狐999

    Liferay Portal额外研究(一):初步在新Tomcat下部署 开发者在线 Builder.com.cn 更新时间:2008-03-19作者:银狐999 来源:CSDN 本文关键词: 部署 t ...

  2. Liferay Portal学习笔记-coldTear

    [原创]Liferay Portal学习笔记(一):安装 一. 简单安装 1)下载并安装 JDK1.5 1.  下载并安装 Windows Platform J2SE(TM) Development ...

  3. 上海交大研究生计算机考研真题,2011年上海交通大学计算机研究生机试真题

    Oracle 约束(constraint)的几个参数的小研究 ORACLE中,约束分deferred 跟 immediate 2种: deferred:如果 Oracle 在事务提交(commit)时 ...

  4. Liferay研究-smilingleo

    http://blog.csdn.net/smilingleo/article/details/1859908 Liferay研究之一:Ext环境的搭建 本篇主要介绍如何搭建Ext开发环境.网上也有很 ...

  5. Liferay Dynamic CSS Filter方法的研究 - 总体过程

    背景知识: 最近项目组遇到一个问题就是改了一个new theme之后导致某些css文件不起作用了,这也激起了我的好奇心,让我有机会去研究下Liferay Dynamic CSS Filter的原理. ...

  6. Liferay新注册用户缺省页面的研究

    Liferay新注册用户缺省页面的研究 在Liferay的基础上进行二次开发,我们都比较困惑的一个问题是:如何控制新注册用户登录后的页面? 关于新用户的缺省页面,最核心的代码是在com.liferay ...

  7. Liferay研究之廿九:Liferay5.2基础架构变动

    前几天Liferay正式发布了5.2, 抽空Down下来研究了一天,感觉还是有不少变化的,很多底层的东西都发生了变化.因为现在重点关注于MDD的研究,所以这次研究重点在基础架构(liferay inf ...

  8. Liferay研究之廿九:Liferay5.2基础架构变动收藏

    前几天Liferay正式发布了5.2, 抽空Down下来研究了一天,感觉还是有不少变化的,很多底层的东西都发生了变化.因为现在重点关注于MDD的研究,所以这次研究重点在基础架构 (liferay in ...

  9. Liferay研究之六:Liferay技巧几则

    在Liferay中添加链接的方式 1.通过liferay-ui:icon标签:    <% ...      PortletURL moreURL = renderResponse.create ...

最新文章

  1. apache2.2 做后端,增加真实ip到日志中
  2. Linux命令中的rpm安装命令
  3. 吴恩达机器学习笔记57-基于内容的推荐系统(Content Based Recommendations)
  4. Android Resources
  5. 以Java的视角来聊聊BIO、NIO与AIO的区别
  6. springboot+shiro框架中上传到服务器的图片不能查看,访问404
  7. 机械制图国家标准的绘图模板_如何使用p5js构建绘图应用
  8. mysql binlog 备份_做好mysql运维,必须熟练掌握备份和恢复,实战一次不行多来几次...
  9. ubuntu20.04使用终端命令安装谷歌Chrome浏览器稳定版
  10. Bootstrap(二)——Grid栅格布局
  11. css img 等比例平铺怎么个屏幕
  12. python异常捕获_Python 异常的捕获、异常的传递与主动抛出异常操作示例
  13. 基于京东家电商品知识图谱的自动问答系统(二) -- IDEA搭建开发环境
  14. 三步教你手动破解网易云音乐加密
  15. java文本框失去焦点事件,jQuery 文本框得失焦点的简单实例
  16. Matlab科研绘图颜色补充(特别篇)—51种中国传统颜色
  17. 盘点飞机上的各种警报
  18. 多看系统kindle最新版_谁说电气造价难?这样系统梳理一下简单多了!小白必看...
  19. Springboot 基于CXF构建WebService服务
  20. 两台笔记本电脑共享屏幕(其中一台电脑当做另外一台电脑的扩展屏幕,多屏显示)

热门文章

  1. Node.js + React + MongoDB 实现 TodoList 单页应用
  2. 如何充分利用各大银行免费资源最大限度减少理财成本
  3. shiro——SimpleAuthenticationInfo中的参数
  4. CentOS7 安装Mysql5.6 后启动失败处理 The server quit without updating PID file
  5. akka actor父子监管的实现
  6. 比特币一种点对点的电子现金系统是哪一年诞生的_驭凡学堂 中本聪创造比特币的原因是为了解决技术难题...
  7. php-fpm的平滑启动
  8. spring@Autowired的对象为null,非容器中的类如何调用容器中的类
  9. Netty入门笔记-Linux网络I/O模型介绍
  10. MySQL中保存emoji表情