`

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

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

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

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

Html代码  收藏代码

    <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,在编译时期将上述分散的多个文件整合成少数几个文件,并且将文件最小化.

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


这是两步优化之后的引用情况:
Html代码  收藏代码

    ${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依赖
Xml代码  收藏代码

    <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

Xml代码  收藏代码

       <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文件.
Java代码  收藏代码

    <?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这三个文件了.

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

Html代码  收藏代码

    <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可能需要额外配置.

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



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

1. 创建DailyNamingStrategy类
Java代码  收藏代码

    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类

Java代码  收藏代码

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



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

例如:basic-2013020217.js

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

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

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


3.创建PlatformFunction类

Java代码  收藏代码

    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插件在生成新的文件的时候不会删除旧文件,如果文件名相同会覆盖掉以前的文件),

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

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

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

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

相关推荐

    wro4j 1.7.2 发布,Web 资源优化工具

    wro4j 1.7.2 为 maven 插件增加多个上下文的文件夹,linter goals 支持增量构建,更新了 jruby、emberjs 等。 wro4j是一个Web资源优化工具。众所周知,到服务器加载一个大的文件比加载两个小的文件快,因为会...

    wro4j-core-1.8.0.jar

    Wro4j是用于分析和优化Web资源的工具,它汇集了几乎所有现代Web工具:JsHint,CssLint,JsMin,Google Closure压缩器,YUI Compressor,UglifyJs,Dojo Shrinksafe,Css变量支持,JSON压缩,Less,Sass,...

    wro4j-spring-boot-starter:Spring Boot启动器和wro4j的自动配置(http:alexo.github.iowro4j)

    Spring Boot启动程序和wro4j的自动配置: 是用于分析和优化Web资源的工具。 它汇集了几乎所有现代Web工具:JsHint,CssLint,JsMin,Google Closure压缩器,YUI Compressor,UglifyJs,Dojo Shrinksafe,Css变量...

    mvn-wro4j-angular:maven,wro4j amd 角度

    通过Maven插件集成WRO4J,可以实现对Angular应用中CSS和JS文件的自动化合并和压缩,进一步提升应用性能。 项目"mvn-wro4j-angular-master"的源代码很可能包含了以下内容:Maven的POM.xml文件,定义了项目结构和依赖...

    web代码混淆方案.混淆js代码,压缩css代码

    例如,WRO4J是一个自动化资源管理工具,它可以将多个CSS文件合并为一个,并进行压缩。这样可以减少HTTP请求的数量,提高页面加载速度。另外,Yahoo!的YUI Compressor也是一个常用的CSS和JavaScript压缩工具,它能...

    wro4j-core-1.7.9.jar

    wro4j-core-1.7.9.jar 是基于mmseg算法的一个轻量级开源中文分词器所需要的jar,已经测验过,欢迎下载!

    wro4j-boot:Spring Boot Wro4j配置样本

    wro4j引导Spring Boot Wro4j配置样本为了避免wro4j尝试从/WEB-INF/加载wro.xml问题,请检查Wro4jCustomXmlModelManagerFactory ,它直接从/wro.xml加载它。 其他详细信息可以在找到

    hedwig-server-4.2.1.zip

    在实际应用中,wro4j-taglib提供了如`&lt;wro:css&gt;`和`&lt;wro:js&gt;`等标签,可以直接在JSP页面中引用预处理的CSS和JS文件。这些标签会自动处理wro4j配置文件中定义的资源合并和压缩,减轻了开发者的工作负担。例如,通过`...

    dspace-services-3.0.zip

    WRO4J 是一个用于优化 Web 资源(如 CSS、JavaScript 文件)的工具,它可以合并、压缩和缓存这些资源,以提高网页加载速度和减少网络请求。 【标签】"开源项目" 表明这两个软件都是遵循开源许可证的,允许用户自由...

    Web资源优化工具wro4j.zip

    wro4j是一个Web资源优化工具。众所周知,到服务器加载一个大的文件比加载两个小的文件快,因为会增加...开发wro4j项目的目的是减少加载一个页面的请求次数和传输的数据量。 点击下图查看大图。 标签:wro4j

    spring-boot-autoconfigure-wro4j:一个spring-boot-autoconfigure库,它简化了https上wro4j库的使用

    弹簧启动自动配置wro4j 一个spring-boot-autoconfigure库,简化了库的使用。 您只需将此库添加到您启用了spring-boot-autoconfigure的项目中,便会为您注册一个wro4j过滤器。 该库当前依赖于wro4j-extension库,该...

    wro4j:从 code.google.compwro4j 自动导出

    它可以帮助您保持静态资源(js 和 css),在(使用简单的过滤器)或(使用 maven 插件)合并和缩小它们,并且有您可能会觉得有用在处理网络资源时。入门为了开始使用 wro4j,您只需执行 3 个简单的步骤。第 1 步:...

    less.ruble:适用于 Wro4j Runner 的 Aptana Studio 3x 捆绑包

    适用于 Wro4j Runner 的 Aptana Studio 3x 捆绑包 使用 Wro4j Runner 优化和/或最小化基于 Eclipse 的 IDE 中的 CSS/JS 文件。 作者 学分 链接 执照 此捆绑包在 MIT 和 GPL 许可下获得双重许可:

    nfsdb-guice-1.0.2.zip

    wro4j是一个免费的开源java项目,它汇集了几乎所有的现代web工具:jshint、csslint、jsmin、google closure compressor、yui compressor、uglifyjs、dojo shrinksafe、css变量支持、json压缩、less、sass、...

    应用级产品开发平台APDPlat.zip

    13)、JS和CSS压缩使用YUI Compressor。 14)、JAVA代码混淆使用Allatori。 15)、获取系统信息使用Sigar。 16)、单元测试使用JUnit。 17)、配置文件加密使用Jasypt。 18)、缓存使用EhCache或是Memcached。 19)、...

    wro标准车型.pdf

    wro

    WebRex:WebRex是用于在运行时进行Web资源聚合和优化的工具。 与wro4j等其他开放源代码优化器相比,它的突出优点是聚合和优化是动态的,而不是静态的

    WebRex目录包括WebRex标签库在JSP页面中定义一个插槽将资源分配到插槽其他... 有许多开源解决方案可以处理资源聚合,例如Google Wro4j和JAWR,但是这些开源解决方案大多数都是“静态”聚合解决方案。 对于静态聚合,

    SpringWebApplication:使用 gradle 的规范 Spring Web 应用程序。 包括 CSSJS 支持

    JSP 对象使用 HTML/静态内容(.html、.css、.js) spring环境中使用WRO4J优化CSS和JS加载RESTful 接口的 URL 参数使用从控制器返回 JSON 内容客户端从控制器调用和返回 JSONP 内容从类路径加载配置属性文件使用 ...

    uberpoc:结束所有 POC 的 POC。 或不

    介绍这是一个单模块 maven 项目,演示了如何集成以下技术: Spring Boot(基于Spring MVC的项目的单jar部署) Sass Maven 插件(Sass 转 CSS) Wro4J(用于构建时 javascript 组合/缩小) Handlebars.java(服务器端...

    icampus-console

    Wro4j是一个Web资源优化工具,它可以合并、压缩和最小化JavaScript和CSS文件,以提高网页加载速度。在"icampus-console"项目中,wro4j被用来优化AngularJS的资源,减少HTTP请求的数量,从而提升用户体验。通过...

Global site tag (gtag.js) - Google Analytics