原创

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://jeecg.blog.csdn.net/article/details/48813871

一、背景

最近正在编写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
package org.noahx.jarresource;
 
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.net.www.protocol.file.FileURLConnection;
 
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.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.
 */
public class TagLibResourceFilter implements Filter {
 
    private static final String RESOURCE_PACKAGE_PATH = "/org/noahx/jarresource/resource";
 
    private static final String RESOURCE_CHARSET = "UTF-8";
 
    private static final String DEFAULT_MINE_TYPE = "application/octet-stream";
 
    private static String resourcePath;
 
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    private ResourceMode resourceMode;
 
    private static enum ResourceMode {
        Dir, Jar
    }
 
    private static final Map<String, String> MINE_TYPE_MAP;
 
    static {
        MINE_TYPE_MAP = new HashMap<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");
 
    }
 
    public static String getResourcePath() {
        return TagLibResourceFilter.resourcePath;
    }
 
    private static void setResourcePath(String resourcePath) {
        TagLibResourceFilter.resourcePath = resourcePath;
    }
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        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 = new File(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
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 
        switch (resourceMode) {
            case Dir:
                chain.doFilter(request, response);
                break;
            case Jar: {
                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 {
                                    int size = 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
    public void destroy() {
    }
 
 
    private InputStream readResource(URL originUrl) throws Exception {
        InputStream inputStream = null;
        URLConnection urlConnection = originUrl.openConnection();
        if (urlConnection instanceof JarURLConnection) {
            inputStream = readJarResource((JarURLConnection) urlConnection);
        } else if (urlConnection instanceof FileURLConnection) {
            File originFile = new File(originUrl.getPath());
            if (originFile.isFile()) {
                inputStream = originUrl.openStream();
            }
        } else {
            throw new Exception("URLConnection[" + urlConnection.getClass().getSimpleName() +
                    "] is not a recognized/implemented connection type.");
        }
 
        return inputStream;
    }
 
    private InputStream readJarResource(JarURLConnection jarConnection) throws Exception {
        InputStream inputStream = null;
        JarFile jarFile = jarConnection.getJarFile();
        if (!jarConnection.getJarEntry().isDirectory()) { //如果jar中内容为目录则不返回inputstream
            inputStream = jarFile.getInputStream(jarConnection.getJarEntry());
        }
        return inputStream;
    }
 
    private void copyResourcesRecursively(URL originUrl, File destination) throws Exception {
 
        URLConnection urlConnection = originUrl.openConnection();
        if (urlConnection instanceof JarURLConnection) {
            copyJarResourcesRecursively(destination, (JarURLConnection) urlConnection);
        } else if (urlConnection instanceof FileURLConnection) {
            FileUtils.copyDirectory(new File(originUrl.getPath()), destination); //如果不是jar则采用目录copy
            if(logger.isDebugEnabled()){
                logger.debug("copy dir '"+originUrl.getPath()+"' --> '"+destination.getPath()+"'");
            }
        } else {
            throw new Exception("URLConnection[" + urlConnection.getClass().getSimpleName() +
                    "] is not a recognized/implemented connection type.");
        }
    }
 
    private void copyJarResourcesRecursively(File destination, JarURLConnection jarConnection) throws IOException {
        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 = new File(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