以Jar形式为Web项目提供资源文件(JS、CSS与图片)

来源:互联网 发布:c语言100以内的素数for 编辑:程序博客网 时间:2024/06/06 20:30

目录[-]

  • 一、背景
  • 二、分析
  • 1、把我需要的JS、CSS与图片等资源copy到Web工程中。
  • 2、通过程序采用流的方式读取Jar中的资源流再输出到页面流。
  • 三、分析结果
  • 四、核心代码开发(Jar端)
  • 1、org.noahx.jarresource.TagLibResourceFilter(程序内逻辑详见注释)
  • 2、pom.xml
  • 五、核心代码开发(Web端)
  • 1、web.xml
  • 2、index.jsp(资源使用样例,JS、CSS与图片)
  • 3、pom.xml
  • 六、Web工程两种模式的Filter日志
  • 1、目录部署方式
  • 2、War包部署方式
  • 七、总结
  • 八、源程序下载
  • 一、背景

    最近正在编写TagLib,在开发的过程中遇到一个资源文件引用问题。因为我开发的TagLib最终是以Jar包的形式提供给项目来用的,所以Jar包中必须包含我开发TagLib所需的JS、CSS与图片等资源。问题就是Tag是在项目的Web工程中运行,如何访问到jar中的资源。

    二、分析

    我想了一下,应该有两种方式:

    1、把我需要的JS、CSS与图片等资源copy到Web工程中。

        好处:

    • 通过原生的Web服务器来访问,速度与性能上讲会好一些。

        缺点:

    • Web工程必须以目录方式部署。(非war)
    • 存放资源的目录名需要与Web工程明确约定。(防止对原Web项目文件进行覆盖)

    2、通过程序采用流的方式读取Jar中的资源流再输出到页面流。

        好处:

    • 不依赖Web工程的部署方式。
    • 不会复制文件到Web工程。

        缺点:

    • 以流的方式实时从Jar中读取。(速度与性能上讲并非最优)
    • 页面流输出时需要指定内容类型Content-Type。(前者会由Web服务器来维护)

    三、分析结果

    最终我准备将1、2两种情况接合使用,默认会采用1复制文件到Web工程的方式。如果发现Web工程无法复制文件则采用2流读取方式。

    四、核心代码开发(Jar端)

    为了进行两种方式的切换定义一个Filter非常适合,可以拦截请求干扰行为,又可以在init初始化时进行资源文件的复制。

    从下面的目录结构可以看出主程序就是一个Filter类,org.noahx.jarresource.resource包下的内容就是我需要的资源目录。Filter会自动判断Web工程的部署方式(目录与War)来决定复制资源目录还是直接流读取。

    1、org.noahx.jarresource.TagLibResourceFilter(程序内逻辑详见注释)

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    packageorg.noahx.jarresource;
     
    importorg.apache.commons.io.FileUtils;
    importorg.apache.commons.io.FilenameUtils;
    importorg.apache.commons.io.IOUtils;
    importorg.apache.commons.lang.StringUtils;
    importorg.slf4j.Logger;
    importorg.slf4j.LoggerFactory;
    importsun.net.www.protocol.file.FileURLConnection;
     
    importjavax.servlet.*;
    importjavax.servlet.http.HttpServletRequest;
    importjavax.servlet.http.HttpServletResponse;
    importjava.io.File;
    importjava.io.IOException;
    importjava.io.InputStream;
    importjava.net.JarURLConnection;
    importjava.net.URL;
    importjava.net.URLConnection;
    importjava.util.Enumeration;
    importjava.util.HashMap;
    importjava.util.Map;
    importjava.util.jar.JarEntry;
    importjava.util.jar.JarFile;
     
    /**
     * Created with IntelliJ IDEA.
     * User: noah
     * Date: 6/24/13
     * Time: 8:18 PM
     * To change this template use File | Settings | File Templates.
     */
    publicclass TagLibResourceFilter implementsFilter {
     
        privatestatic final String RESOURCE_PACKAGE_PATH = "/org/noahx/jarresource/resource";
     
        privatestatic final String RESOURCE_CHARSET = "UTF-8";
     
        privatestatic final String DEFAULT_MINE_TYPE = "application/octet-stream";
     
        privatestatic String resourcePath;
     
        privatefinal Logger logger = LoggerFactory.getLogger(this.getClass());
     
        privateResourceMode resourceMode;
     
        privatestatic enum ResourceMode {
            Dir, Jar
        }
     
        privatestatic final Map<String, String> MINE_TYPE_MAP;
     
        static{
            MINE_TYPE_MAP = newHashMap<String, String>();
            MINE_TYPE_MAP.put("js","application/javascript;charset="+ RESOURCE_CHARSET);
            MINE_TYPE_MAP.put("css","text/css;charset="+ RESOURCE_CHARSET);
            MINE_TYPE_MAP.put("gif","image/gif");
            MINE_TYPE_MAP.put("jpg","image/jpeg");
            MINE_TYPE_MAP.put("jpeg","image/jpeg");
            MINE_TYPE_MAP.put("png","image/png");
     
        }
     
        publicstatic String getResourcePath() {
            returnTagLibResourceFilter.resourcePath;
        }
     
        privatestatic void setResourcePath(String resourcePath) {
            TagLibResourceFilter.resourcePath = resourcePath;
        }
     
        @Override
        publicvoid init(FilterConfig filterConfig) throwsServletException {
            String resPath = filterConfig.getInitParameter("resourcePath");
     
            if(!resPath.startsWith("/")) {
                resPath = "/"+ resPath;
            }
     
            setResourcePath(resPath);
     
            String rootPath = filterConfig.getServletContext().getRealPath("/");
            if(rootPath != null) {   //如果web工程是目录方式运行
     
                String dirPath = filterConfig.getServletContext().getRealPath(resPath);
                File dir = null;
                try{
                    dir = newFile(dirPath);
                    FileUtils.deleteQuietly(dir);//清除老资源
                    FileUtils.forceMkdir(dir);  //重新创建资源目录
     
                    if(logger.isDebugEnabled()){
                        logger.debug("create dir '"+dirPath+"'");
                    }
                }catch(Exception e) {
                    logger.error("Error creating TagLib Resource dir", e);
                }
     
                try{
                    copyResourcesRecursively(this.getClass().getResource(RESOURCE_PACKAGE_PATH), dir); //复制classpath中的资源到目录
                }catch(Exception e) {
                    logger.error(e.getMessage(), e);
                }
     
                resourceMode = ResourceMode.Dir;   //设置为目录模式
            }else{
                resourceMode = ResourceMode.Jar;    //设置为jar包模式
            }
     
            if(logger.isDebugEnabled()){
                logger.debug("ResourceMode:"+resourceMode);
            }
        }
     
        @Override
        publicvoid doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throwsIOException, ServletException {
     
            switch(resourceMode) {
                caseDir:
                    chain.doFilter(request, response);
                    break;
                caseJar: {
                    HttpServletRequest req = (HttpServletRequest) request;
                    String path = req.getRequestURI().substring(req.getContextPath().length());  //uri去掉web上下文
     
                    HttpServletResponse rep = (HttpServletResponse) response;
     
                    if(path.startsWith(getResourcePath() + "/")) {  //resourcePath必须与url-pattern一致
     
                        path = path.substring(getResourcePath().length());     //uri去掉resourcePath
     
                        try{
                            URL resource = this.getClass().getResource(RESOURCE_PACKAGE_PATH + path);    //可能存在潜在安全问题
                            if(resource == null) {      //如果在类路径中没有找到资源->404
                                rep.sendError(HttpServletResponse.SC_NOT_FOUND);
                            }else{
                                InputStream inputStream = readResource(resource);
                                if(inputStream != null) {  //有inputstream说明已经读到jar中内容
                                    String ext = FilenameUtils.getExtension(path).toLowerCase();
                                    String contentType = MINE_TYPE_MAP.get(ext);
                                    if(contentType == null) {
                                        contentType = DEFAULT_MINE_TYPE;
                                    }
                                    rep.setContentType(contentType);   //设置内容类型
     
                                    ServletOutputStream outputStream = rep.getOutputStream();
                                    try{
                                        intsize = IOUtils.copy(inputStream, outputStream);  //向输出流输出内容
                                        rep.setContentLength(size);
                                    }finally{
                                        IOUtils.closeQuietly(inputStream);
                                        IOUtils.closeQuietly(outputStream);
                                    }
                                }else{   //没有inputstream->404
                                    rep.sendError(HttpServletResponse.SC_NOT_FOUND);
                                }
                            }
     
                        }catch(Exception e) {
                            logger.error(e.getMessage(), e);
                            rep.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                        }
                    }else{
                        logger.error("MUST set url-pattern=\"" + resourcePath + "/*\"!!");
                        rep.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    }
     
                }
                break;
            }
     
     
        }
     
        @Override
        publicvoid destroy() {
        }
     
     
        privateInputStream readResource(URL originUrl) throwsException {
            InputStream inputStream = null;
            URLConnection urlConnection = originUrl.openConnection();
            if(urlConnection instanceofJarURLConnection) {
                inputStream = readJarResource((JarURLConnection) urlConnection);
            }elseif (urlConnection instanceofFileURLConnection) {
                File originFile = newFile(originUrl.getPath());
                if(originFile.isFile()) {
                    inputStream = originUrl.openStream();
                }
            }else{
                thrownew Exception("URLConnection["+ urlConnection.getClass().getSimpleName() +
                        "] is not a recognized/implemented connection type.");
            }
     
            returninputStream;
        }
     
        privateInputStream readJarResource(JarURLConnection jarConnection) throwsException {
            InputStream inputStream = null;
            JarFile jarFile = jarConnection.getJarFile();
            if(!jarConnection.getJarEntry().isDirectory()) { //如果jar中内容为目录则不返回inputstream
                inputStream = jarFile.getInputStream(jarConnection.getJarEntry());
            }
            returninputStream;
        }
     
        privatevoid copyResourcesRecursively(URL originUrl, File destination) throwsException {
     
            URLConnection urlConnection = originUrl.openConnection();
            if(urlConnection instanceofJarURLConnection) {
                copyJarResourcesRecursively(destination, (JarURLConnection) urlConnection);
            }elseif (urlConnection instanceofFileURLConnection) {
                FileUtils.copyDirectory(newFile(originUrl.getPath()), destination); //如果不是jar则采用目录copy
                if(logger.isDebugEnabled()){
                    logger.debug("copy dir '"+originUrl.getPath()+"' --> '"+destination.getPath()+"'");
                }
            }else{
                thrownew Exception("URLConnection["+ urlConnection.getClass().getSimpleName() +
                        "] is not a recognized/implemented connection type.");
            }
        }
     
        privatevoid copyJarResourcesRecursively(File destination, JarURLConnection jarConnection) throwsIOException {
            JarFile jarFile = jarConnection.getJarFile();
            Enumeration<JarEntry> entries = jarFile.entries();
            while(entries.hasMoreElements()) {    //遍历jar内容逐个copy
                JarEntry entry = entries.nextElement();
                if(entry.getName().startsWith(jarConnection.getEntryName())) {
                    String fileName = StringUtils.removeStart(entry.getName(), jarConnection.getEntryName());
                    File destFile = newFile(destination, fileName);
                    if(!entry.isDirectory()) {
                        InputStream entryInputStream = jarFile.getInputStream(entry);
                        FileUtils.copyInputStreamToFile(entryInputStream, destFile);
                        if(logger.isDebugEnabled()){
                            logger.debug("copy jarfile to file '"+entry.getName()+"' --> '"+destination.getPath()+"'");
                        }
                    }else{
                        FileUtils.forceMkdir(destFile);
                        if(logger.isDebugEnabled()){
                            logger.debug("create dir '"+destFile.getPath()+"'");
                        }
                    }
                }
            }
        }
    }

    补充:Filter中提供了静态方法getResourcePath()来获得当前的资源路径,我的TagLib中就可以通过该方法获得资源URI。

    2、pom.xml

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <?xmlversion="1.0"encoding="UTF-8"?>
    <projectxmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
     
        <groupId>org.noahx.jarresource</groupId>
        <artifactId>resource</artifactId>
        <version>1.0-SNAPSHOT</version>
     
        <packaging>jar</packaging>
     
        <dependencies>
     
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>2.4</version>
            </dependency>
     
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.5</version>
            </dependency>
     
            <dependency>
                <groupId>commons-lang</groupId>
                <artifactId>commons-lang</artifactId>
                <version>2.6</version>
            </dependency>
     
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
                <scope>provided</scope>
            </dependency>
     
        </dependencies>
    </project>

    使用了commons-io与commons-lang第三方类包

    五、核心代码开发(Web端)

    作为Jar文件的使用端,只需要在web.xml中配置一个filter,就可以访问到Jar中的资源。

    1、web.xml

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <web-appxmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
              http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
             version="2.5">
        <display-name>jar resource web</display-name>
     
        <filter>
            <filter-name>tagLibResourceFilter</filter-name>
            <filter-class>org.noahx.jarresource.TagLibResourceFilter</filter-class>
            <init-param>
                <param-name>resourcePath</param-name>
                <param-value>/tagres</param-value>
            </init-param>
        </filter>
     
        <filter-mapping>
            <filter-name>tagLibResourceFilter</filter-name>
            <url-pattern>/tagres/*</url-pattern>
        </filter-mapping>
     
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
    </web-app>
    注意:由于Servlet 3.0以下无法通过程序获得url-pattern,所以在filter的参数中指定了一个同名路径来使用。filter会用这个路径名称在Web工程下创建资源目录(目录部署)。

    2、index.jsp(资源使用样例,JS、CSS与图片)

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title></title>
        <linkrel="stylesheet"type="text/css"href="tagres/css.css"/>
        <script type="text/javascript"src="tagres/example.js"></script>
    </head>
    <body>
         <imgsrc="tagres/imgs/star-hover4.png"/>star-hover4.png<br/>
         <buttononclick="example();">example.js (example)</button><br/>
         <divclass="redbox">css.css redbox</div>
    </body>
    </html>

    tagres/中的内容就是Jar工程中所提供的资源。(下图为显示效果)

    3、pom.xml

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <?xmlversion="1.0"encoding="UTF-8"?>
    <projectxmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
     
        <groupId>org.noahx.jarresource</groupId>
        <artifactId>web</artifactId>
        <version>1.0-SNAPSHOT</version>
     
        <packaging>war</packaging>
     
        <dependencies>
            <dependency>
                <groupId>org.noahx.jarresource</groupId>
                <artifactId>resource</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
     
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <version>1.7.5</version>
                <scope>runtime</scope>
            </dependency>
     
        </dependencies>
     
    </project>

    六、Web工程两种模式的Filter日志

    1、目录部署方式

    ?
    1
    2
    3
    4
    5
    6
    7
    [JAR-RES] 2013-06-26 13:11:13,132 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - createdir'/nautilus/develop/jar-resource/web/target/web-1.0-SNAPSHOT/tagres' (TagLibResourceFilter.java:93)
    [JAR-RES] 2013-06-26 13:11:13,146 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - createdir'/nautilus/develop/jar-resource/web/target/web-1.0-SNAPSHOT/tagres/' (TagLibResourceFilter.java:240)
    [JAR-RES] 2013-06-26 13:11:13,147 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - createdir'/nautilus/develop/jar-resource/web/target/web-1.0-SNAPSHOT/tagres/imgs' (TagLibResourceFilter.java:240)
    [JAR-RES] 2013-06-26 13:11:13,152 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - copy jarfile tofile'org/noahx/jarresource/resource/imgs/star-hover4.png' --> '/nautilus/develop/jar-resource/web/target/web-1.0-SNAPSHOT/tagres'(TagLibResourceFilter.java:235)
    [JAR-RES] 2013-06-26 13:11:13,153 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - copy jarfile tofile'org/noahx/jarresource/resource/example.js' --> '/nautilus/develop/jar-resource/web/target/web-1.0-SNAPSHOT/tagres'(TagLibResourceFilter.java:235)
    [JAR-RES] 2013-06-26 13:11:13,154 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - copy jarfile tofile'org/noahx/jarresource/resource/css.css' --> '/nautilus/develop/jar-resource/web/target/web-1.0-SNAPSHOT/tagres'(TagLibResourceFilter.java:235)
    [JAR-RES] 2013-06-26 13:11:13,154 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - ResourceMode:Dir (TagLibResourceFilter.java:111)

    可以看到copy资源文件的过程

    2、War包部署方式

    ?
    1
    [JAR-RES] 2013-06-26 13:12:25,287 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - ResourceMode:Jar (TagLibResourceFilter.java:111)
    从Jar中直接读取,并没有copy资源的过程。

    七、总结

    这个Filter很好的解决了我在开发TagLib时遇到的资源引用问题,对我来说应该够用了。

    我们项目中一般采用目录方式部署,我也更希望通过Web服务器来直接访问资源。

    并没有采用maven来组织资源,因为我需要提供给非maven工程使用。

    一些流行的mvc中也有类似的手法,可能是采用流方式读取(猜测),感兴趣的朋友可以查看这些mvc的代码。

    八、源程序下载

    下载包中提供了源代码以及打包后(target目录)的工程。

    http://sdrv.ms/11Mp5gF

    0 0