最近在对一个web系统做性能优化.
而对用到的静态资源文件的压缩整合则是前端性能优化中很重要的一环.
好处不仅在于能够减小请求的文件体积,而且能够减少浏览器的http请求数.

因为是基于java的web系统,并且使用的是nginx+tomcat做为服务器.
最后考虑用wro4j和maven plugin在编译期间压缩静态资源.

优化前:
基本上所有的jsp都引用了这一大坨静态文件:

<link rel="stylesheet" type="text/css" href="${ctxPath}/css/skin.css"/><link rel="stylesheet" type="text/css" href="${ctxPath}/css/jquery-ui-1.8.23.custom.css"/><link rel="stylesheet" type="text/css" href="${ctxPath}/css/validationEngine.jquery.css"/>

<script type="text/javascript">var GV = {ctxPath: '${ctxPath}',imgPath: '${ctxPath}/css'};</script><script type="text/javascript" src="${ctxPath}/js/jquery-1.7.2.min.js"></script><script type="text/javascript" src="${ctxPath}/js/jquery-ui-1.8.23.custom.min.js"></script><script type="text/javascript" src="${ctxPath}/js/jquery.validationEngine.js"></script><script type="text/javascript" src="${ctxPath}/js/jquery.validationEngine-zh_CN.js"></script><script type="text/javascript" src="${ctxPath}/js/jquery.fixedtableheader.min.js"></script><script type="text/javascript" src="${ctxPath}/js/roll.js"></script><script type="text/javascript" src="${ctxPath}/js/jquery.pagination.js"></script><script type="text/javascript" src="${ctxPath}/js/jquery.rooFixed.js"></script><script type="text/javascript" src="${ctxPath}/js/jquery.ui.datepicker-zh-CN.js"></script><script type="text/javascript" src="${ctxPath}/js/json2.js"></script><script type="text/javascript" src="${ctxPath}/js/common.js"></script>

引用的文件很多,并且文件体积没有压缩,导致页面请求的时间非常长.

另外还有一个问题,就是为了能够充分利用浏览器的缓存,静态资源的文件名称最好能够做到版本化控制.

这样前端web服务器就可以放心大胆的开启缓存功能而不用担心缓存过期问题,因为如果一旦静态资源文件有修改的话,
会重新生成一个文件名称.

下面我根据自己项目的经验,来介绍下如何较好的解决这两个问题.

分两步进行.

第一步:引入wro4j,在编译时期将上述分散的多个文件整合成少数几个文件,并且将文件最小化.

第二步:在生成的静态资源文件的文件名称上加入时间信息

这是两步优化之后的引用情况:

${platform:cssFile("/wro/basic") }<script type="text/javascript">var GV = {ctxPath: '${ctxPath}',imgPath: '${ctxPath}/css'};</script>${platform:jsFile("/wro/basic") }${platform:jsFile("/wro/custom") }

只引用了1个css文件,2个js文件.http请求从10几个减少到3个,并且整体文件体积缩小了近一半.

下面介绍优化流程.

第一步:合并并且最小化文件.

1.添加wro4j的maven依赖

 <wro4j.version>1.6.2</wro4j.version>

    ...

  <dependency>   <groupId>ro.isdc.wro4j</groupId>   <artifactId>wro4j-core</artifactId>   <version>${wro4j.version}</version>   <exclusions>    <exclusion>

    <!-- 因为项目中的其他jar包已经引入了不同版本的slf4j,所以这里避免jar重叠所以不引入 -->     <groupId>org.slf4j</groupId>     <artifactId>slf4j-api</artifactId>    </exclusion>   </exclusions>  </dependency>

2.添加wro4j maven plugin

   <plugin>    <groupId>ro.isdc.wro4j</groupId>    <artifactId>wro4j-maven-plugin</artifactId>    <version>${wro4j.version}</version>    <executions>     <execution>      <phase>compile</phase>      <goals>       <goal>run</goal>      </goals>     </execution>    </executions>    <configuration>     <targetGroups>basic,custom</targetGroups>

    <!-- 这个配置是告诉wro4j在打包静态资源的时候是否需要最小化文件,开发的时候可以设成false,方便调试 -->     <minimize>true</minimize>     <destinationFolder>${basedir}/src/main/webapp/wro/</destinationFolder>     <contextFolder>${basedir}/src/main/webapp/</contextFolder>

<!-- 这个配置是第二步优化需要用到的,暂时忽略 -->     <wroManagerFactory>com.rootrip.platform.common.web.wro.CustomWroManagerFactory</wroManagerFactory>    </configuration>         </plugin>

如果开发环境是eclipse的话,可以下载m2e-wro4j这个插件.

下载地址:http://download.jboss.org/jbosstools/updates/m2e-wro4j/

这个插件的主要功能是能够帮助我们在开发环境下修改对应的静态文件,或者pom.xml文件的时候能够自动生成打包好的js和css文件.

对开发来说就会方便很多.只要修改源文件就能看见修改后的结果.

3.在WEB-INF目录下添加wro.xml文件,这个文件的作用就是告诉wro4j需要以怎样的策略打包jss和css文件.

<?xml version="1.0" encoding="UTF-8"?><groups xmlns="http://www.isdc.ro/wro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.isdc.ro/wro wro.xsd">

 <group name="basic">  <css>/css/basic.css</css>  <css>/css/skin.css</css>  <css>/css/jquery-ui-1.8.23.custom.css</css>  <css>/css/validationEngine.jquery.css</css>

  <js>/js/jquery-1.7.2.min.js</js>  <js>/js/jquery-ui-1.8.23.custom.min.js</js>  <js>/js/jquery.validationEngine.js</js>  <js>/js/jquery.fixedtableheader.min.js</js>  <js>/js/roll.js</js>  <js>/js/jquery.pagination.js</js>  <js>/js/jquery.rooFixed.js</js>  <js>/js/jquery.ui.datepicker-zh-CN.js</js>  <js>/js/json2.js</js> </group>

 <group name="custom">  <js>/js/jquery.validationEngine-zh_CN.js</js>  <js>/js/common.js</js> </group>

</groups>

官方文档:http://code.google.com/p/wro4j/wiki/WroFileFormat

其实这个配置文件很好理解,如果不愿看官方文档的朋友我在这简单介绍下.

上面这样配置的目的就是告诉wro4j要将

<css>/css/basic.css</css>
<css>/css/skin.css</css>
<css>/css/jquery-ui-1.8.23.custom.css</css>
<css>/css/validationEngine.jquery.css</css>

这四个文件整合到一起,生成一个叫basic.css的文件到指定目录(wro4j-maven-plugin里配置的),将

<js>/js/jquery-1.7.2.min.js</js>
<js>/js/jquery-ui-1.8.23.custom.min.js</js>
<js>/js/jquery.validationEngine.js</js>
<js>/js/jquery.fixedtableheader.min.js</js>
<js>/js/roll.js</js>
<js>/js/jquery.pagination.js</js>
<js>/js/jquery.rooFixed.js</js>
<js>/js/jquery.ui.datepicker-zh-CN.js</js>
<js>/js/json2.js</js>

这几个文件整合到一起,生成一个叫basic.js的文件到指定目录.

最后将

<js>/js/jquery.validationEngine-zh_CN.js</js>
<js>/js/common.js</js>

这两个文件整合到一起,,生成一个叫custom.js的文件到指定目录.

第一步搞定,这时候如果你的开发环境是eclipse并且安装了插件的话,应该就能在你工程的%your webapp%/wor/目录下看见生成好的

basic.css,basic.js和custom.js这三个文件了.

然后你再将你的静态资源引用路径改成

<link rel="stylesheet" type="text/css" href="${ctxPath}/wro/basic.css"/><script type="text/javascript" src="${ctxPath}/wro/basic.js"></script><script type="text/javascript" src="${ctxPath}/wro/custom.js"></script>

就ok了.每次修改被引用到的css或js文件的时候,这些文件都将重新生成.

如果开发环境是eclipse但是没有安装m2e-wro4j插件的话,pom.xml可能需要额外配置.

请参考:[url]https://community.jboss.org/en/tools/blog/2012/01/17/css-and-js-minification-using-eclipse-maven-and-wro4j[/url]

第二步:给生成的文件名称中加入时间信息并通过el自定义函数引用脚本文件.

1. 创建DailyNamingStrategy类

public class DailyNamingStrategy extends TimestampNamingStrategy {

 protected final Logger log = LoggerFactory.getLogger(DailyNamingStrategy.class);

 @Override protected long getTimestamp() {  String dateStr = DateUtil.formatDate(new Date(), "yyyyMMddHH");  return Long.valueOf(dateStr); }

}

2.创建CustomWroManagerFactory类

//这个类就是在wro4j-maven-plugin里配置的wroManagerFactory参数public class CustomWroManagerFactory extends  DefaultStandaloneContextAwareManagerFactory { public CustomWroManagerFactory() {  setNamingStrategy(new DailyNamingStrategy()); }}

上面这两个类的作用是使用wro4j提供的文件命名策略,这样生成的文件名就会带上时间信息了.

例如:basic-2013020217.js

但是现在又会发现一个问题:如果静态资源文件名称不固定的话,那怎么样引用呢?

这时候就需要通过动态生成<script>与<link>来解决了.

因为项目使用的是jsp页面,所以通过el自定义函数来实现标签生成.

3.创建PlatformFunction类

public class PlatformFunction {

 private static Logger log = LoggerFactory.getLogger(PlatformFunction.class);

 private static ConcurrentMap<String, String> staticFileCache = new ConcurrentHashMap<>();

 private static AtomicBoolean initialized = new AtomicBoolean(false);

 private static final String WRO_Path = "/wro/";

 private static final String JS_SCRIPT = "<script type=\"text/javascript\" src=\"%s\"></script>"; private static final String CSS_SCRIPT = "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">";

 private static String contextPath = null; 

 /**  * 该方法根据给出的路径,生成js脚本加载标签  * 例如传入参数/wro/custom,该方法会寻找webapp路径下/wro目录中以custom开头,以js后缀结尾的文件名称名称.  * 然后拼成<script type="text/javascript" src="${ctxPath}/wro/custom-20130201.js"></script>返回  * 如果查找到多个文件,返回根据文件名排序最大的文件  * @param str  * @return  */ public static String jsFile(String filePath) {  String jsFile = staticFileCache.get(buildCacheKey(filePath, "js"));  if(jsFile == null) {   log.error("加载js文件失败,缓存中找不到对应的文件[{}]", filePath);  }  return String.format(JS_SCRIPT, jsFile); }

 /**  * 该方法根据给出的路径,生成css脚本加载标签  * 例如传入参数/wro/custom,该方法会寻找webapp路径下/wro目录中以custom开头,以css后缀结尾的文件名称名称.  * 然后拼成<link rel="stylesheet" type="text/css" href="${ctxPath}/wro/basic-20130201.css">返回  * 如果查找到多个文件,返回根据文件名排序最大的文件  * @param str  * @return  */ public static String cssFile(String filePath) {  String cssFile = staticFileCache.get(buildCacheKey(filePath, "css"));  if(cssFile == null) {   log.error("加载css文件失败,缓存中找不到对应的文件[{}]", filePath);  }  return String.format(CSS_SCRIPT, cssFile); }

 public static void init() throws IOException {  if(initialized.compareAndSet(false, true)) {   ServletContext sc = Platform.getInstance().getServletContext();   if(sc == null) {    throw new PlatformException("查找静态资源的时候的时候发现servlet context 为null");   }   contextPath = Platform.getInstance().getContextPath();   File wroDirectory = new ServletContextResource(sc, WRO_Path).getFile();   if(!wroDirectory.exists() || !wroDirectory.isDirectory()) {    throw new PlatformException("查找静态资源的时候发现对应目录不存在[" + wroDirectory.getAbsolutePath() + "]");   }   //将wro目录下已有文件加入缓存   for(File file : wroDirectory.listFiles()) {    handleNewFile(file);   }   //监控wro目录,如果有文件生成,则判断是否是较新的文件,是的话则把文件名加入缓存   new Thread(new WroFileWatcher(wroDirectory.getAbsolutePath())).start();  } }

 private static void handleNewFile(File file) {  String fileName = file.getName();  Pattern p = Pattern.compile("^(\\w+)\\-\\d+\\.(js|css)$");  Matcher m = p.matcher(fileName);  if(!m.find() || m.groupCount() < 2) return;  String fakeName = m.group(1);  String fileType = m.group(2);  //暂时限定只能匹配/wro/目录下的文件  String key = buildCacheKey(WRO_Path + fakeName, fileType);  if(staticFileCache.putIfAbsent(key, fileName) != null) {   synchronized(staticFileCache) {    String cachedFileName = staticFileCache.get(key);    if(fileName.compareTo(cachedFileName) > 0) {     staticFileCache.put(key, contextPath + WRO_Path + fileName);    }   }  } }

 private static String buildCacheKey(String fakeName, String fileType) {  return fakeName + "-" + fileType; }

 static class WroFileWatcher implements Runnable {

  private static Logger log = LoggerFactory.getLogger(WroFileWatcher.class);

  private String wroAbsolutePathStr;

  public WroFileWatcher(String wroPathStr) {   this.wroAbsolutePathStr = wroPathStr;  }

  @Override  public void run() {   Path path = Paths.get(wroAbsolutePathStr);   File wroDirectory = path.toFile();   if(!wroDirectory.exists() || !wroDirectory.isDirectory()) {    String message = "监控wro目录的时候发现对应目录不存在[" + wroAbsolutePathStr + "]";    log.error(message);    throw new PlatformException(message);   }   log.warn("开始监控wro目录[{}]", wroAbsolutePathStr);   try {    WatchService watcher = FileSystems.getDefault().newWatchService();    path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);

    while (true) {     WatchKey key = null;     try {      key = watcher.take();     } catch (InterruptedException e) {      log.error("", e);      continue;     }     for (WatchEvent<?> event : key.pollEvents()) {      if (event.kind() == StandardWatchEventKinds.OVERFLOW) {       continue;      }      WatchEvent<Path> e = (WatchEvent<Path>) event;      Path filePath = e.context();      handleNewFile(filePath.toFile());     }     if (!key.reset()) {      break;     }    }   } catch (IOException e) {    log.error("监控wro目录发生错误", e);   }   log.warn("停止监控wro目录[{}]", wroAbsolutePathStr);  } }}

对应的tld文件就不给出了,根据方法签名编写就行了.

其中的cssFile和jsFile方法分别实现引用css和js文件.

在页面使用的时候类似这样:

${platform:cssFile("/wro/basic") }

${platform:jsFile("/wro/custom") }

这个类的主要功能就是使用jdk7的WatchService监控wro目录的新增文件事件,

一旦有新的文件加到目录里,判断这个文件是不是最新的,如果是的话则使用这个文件名称引用.

这样一旦有新加的资源文件放到wro目录里,则能够自动被引用,不需要做任何代码上的修改,并且基本不影响性能.

到此为止功能已经实现.

但是我考虑到还有两个问题有待完善:

1.因为生成的文件名称精确到小时,如果这个小时之内有多次代码修改,生成的文件名都完全一样.

这样就算线上的代码有修改,对于已经有该文本缓存的浏览器来说,不会重新请求文件,也就看不到文件变化.

不过一般来说线上代码不会如此频繁改动,对于大多数应用来说影响不大.

2.在开发环境开发一段时间之后,wro目录下会生成一大堆的文件(因为m2e-wro4j插件在生成新的文件的时候不会删除旧文件,如果文件名相同会覆盖掉以前的文件),

这时候就需要手动删除时间靠前的旧文件,虽然系统会忽略旧文件,但是我相信大多数程序员和我一样是有些许洁癖的吧.

解决办法还是不少,比如可以写脚本定期清理掉旧文件.

时间有限,有些地方考虑的不是很完善,欢迎拍砖.

参考资料:
[url]http://meri-stuff.blogspot.sk/2012/08/wro4j-page-load-optimization-and-lessjs.html#Configuration[/url]
[url]https://community.jboss.org/en/tools/blog/2012/01/17/css-and-js-minification-using-eclipse-maven-and-wro4j[/url]
[url]http://code.google.com/p/wro4j/wiki/MavenPlugin[/url]
[url]http://code.google.com/p/wro4j/wiki/WroFileFormat[/url]
[url]http://java.dzone.com/articles/using-java-7s-watchservice[/url]

使用wro4j和maven在编译期间压缩js和css文件相关推荐

  1. maven js css 压缩,使用wro4j和maven在编译期间压缩js和css文件(经典)

    最近在对一个web系统做性能优化. 而对用到的静态资源文件的压缩整合则是前端性能优化中很重要的一环. 好处不仅在于能够减小请求的文件体积,而且能够减少浏览器的http请求数. 因为是基于java的we ...

  2. Visual Studio 编译任务压缩js和css文件

    如今网站都在说优化,压缩js和css文件就成了最基本的一种方法,js和css压缩有很多方法,很多网站也提供了这样的功能,也可以用YUI提供的包手动压缩,但是这都不效率啊,能不能在vs生成部署包的时候把 ...

  3. php 压缩js css文件,PHP实现动态压缩js与css文件的方法

    本文实例讲述了PHP实现动态压缩js与css文件的方法.分享给大家供大家参考,具体如下: 正式发布产品时,我们希望将项目里的js,css合并压缩,以减少http请求.防止轻易查看源代码. 使用 只需要 ...

  4. php 压缩html css,PHP实现动态压缩js与css文件的方法

    这篇文章主要介绍了关于PHP实现动态压缩js与css文件的方法,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下 本文实例讲述了PHP实现动态压缩js与css文件的方法.分享给大家供大家参 ...

  5. yui压缩JS和CSS文件

    CSS和JS文件经常需要压缩,比如我们看到的XX.min.js是经过压缩的JS. 压缩文件第一个是可以减小文件大小,第二个是对于JS文件,默认会去掉所有的注释,而且会去掉所有的分号,也会将我们的一些参 ...

  6. JAVA压缩JS或CSS文件

    平时开发过程中使用的js或css文件基本都是未经压缩的方便调试,在真正上线后一般都会使用压缩后的文件,我们可以使用一些工具进行压缩,我们也可以通过JAVA代码自己实现文件的压缩,今天为大家介绍的是使用 ...

  7. grunt合并压缩js、css文件

    参考文章如下: http://www.cnblogs.com/yexiaochai/p/3594561.html http://www.cnblogs.com/yexiaochai/p/3602002 ...

  8. 使用grunt合并压缩js、css文件

    需要了解的知识:1.nodejs的安装与命令行使用2.nodejs安装应用3.grunt的初步了解 本文已假定读者已经熟悉以上知识. 好,我们继续: 任务1:将src目录下的所有zepto及插件合并, ...

  9. IIS 7 启用 gzip 静态压缩 压缩js和css文件

    搞了很久,不如nginx好弄,不知道怎么修改压缩比,也不知道怎么压缩的规则是啥(管理器上没有写),不过反正出来了,一个js文件900多K变成了100多K 1.在web.config文件里面加上: &l ...

最新文章

  1. 如何量化医学图像分割中的置信度?
  2. c/c++ g++默认编译调用成功,O2优化却发生Segmentation fault
  3. 【转贴】Decoda Tutorial LUA调式器
  4. Java hibernate假外键_JAVA基础:Hibernate外键关联与HQL语法
  5. COCOS CREATOR(TS)之按钮事件
  6. spring boot区分生产环境和开发环境
  7. Android UI学习之ListView(使用ArrayAdapter和SimpleAdapter)
  8. shp文件转json
  9. 数学建模入门例题python_用Python分析支付宝轻定投收益--Python数学建模实例
  10. YYLabel 自动布局 不换行 numberOfLines无效
  11. php soap wsse,改变xmlns:wsse命名空间在使用Python Zeep的SOAP请求中
  12. 微博营销案例 – 运来大师看手相
  13. 一些Winodws mobile相关资料的整理
  14. vtd和vt的区别_vtvi和v的区别是什么?
  15. VS2017--无法添加引用--提示“未能完成操作。不支持此接口”
  16. 7kyu Jaden Casing Strings
  17. disallow api.php,爬虫之robots.txt
  18. PSPICE中的各种库文件说明
  19. 我的世界java版复仇双持_我的世界战备双持2mod整合包
  20. [VisionPro] CogPMAlignTool 工具详解

热门文章

  1. 加拿大国家银行开展区块链试点,简化“复杂”谈判流程
  2. 最近组织游泳社的体会
  3. 热水器突然不出热水问题排查记录
  4. linux中time命令详解、脚本监控记录系统硬盘io值、定位linux系统中await值过高占用的盘、定位占用硬盘IO高的程序、iotop命令说明、lsof使用说明【可定位端口所占用程序等】
  5. 运动控制卡应用开发教程之C++
  6. 【自定义控件】仿支付宝支付动画
  7. 无与伦比的工业设计 iPhone 4详细评测
  8. 首款鸿蒙手机,华为首款鸿蒙手机入网:不是P50系列
  9. 这一年,你过得还好吗?
  10. android 手机模拟低内存