以Jar形式为Web项目提供资源文件(JS、CSS与图片)
来源:互联网 发布:c语言100以内的素数for 编辑:程序博客网 时间:2024/06/06 20:30
目录[-]
一、背景
最近正在编写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(程序内逻辑详见注释)
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
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
project
xmlns
=
"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
<
web-app
xmlns
=
"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
>
2、index.jsp(资源使用样例,JS、CSS与图片)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<
html
>
<
head
>
<
title
></
title
>
<
link
rel
=
"stylesheet"
type
=
"text/css"
href
=
"tagres/css.css"
/>
<
script
type
=
"text/javascript"
src
=
"tagres/example.js"
></
script
>
</
head
>
<
body
>
<
img
src
=
"tagres/imgs/star-hover4.png"
/>star-hover4.png<
br
/>
<
button
onclick
=
"example();"
>example.js (example)</
button
><
br
/>
<
div
class
=
"redbox"
>css.css redbox</
div
>
</
body
>
</
html
>
tagres/中的内容就是Jar工程中所提供的资源。(下图为显示效果)
3、pom.xml
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
project
xmlns
=
"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、目录部署方式
[JAR-RES] 2013-06-26 13:11:13,132 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - create
dir
'/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] - create
dir
'/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] - create
dir
'/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 to
file
'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 to
file
'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 to
file
'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包部署方式
[JAR-RES] 2013-06-26 13:12:25,287 DEBUG [org.noahx.jarresource.TagLibResourceFilter] - ResourceMode:Jar (TagLibResourceFilter.java:111)
七、总结
这个Filter很好的解决了我在开发TagLib时遇到的资源引用问题,对我来说应该够用了。
我们项目中一般采用目录方式部署,我也更希望通过Web服务器来直接访问资源。
并没有采用maven来组织资源,因为我需要提供给非maven工程使用。
一些流行的mvc中也有类似的手法,可能是采用流方式读取(猜测),感兴趣的朋友可以查看这些mvc的代码。
八、源程序下载
下载包中提供了源代码以及打包后(target目录)的工程。
http://sdrv.ms/11Mp5gF
- 以Jar形式为Web项目提供资源文件(JS、CSS与图片)
- 以Jar形式为Web项目提供资源文件(JS、CSS与图片)
- web项目中url-pattern改成'/'后,js、css、图片等静态资源(404)无法访问问题解决办法
- web 项目中资源下载出错,或者下载的文件对应的形式不对应,web 项目下载的文件自动打包为一个压缩包
- Node.js压缩web项目中的js,css和图片
- jxl在web项目中以IO流的形式写入excel文件
- Spring boot项目以jar包形式启动中文乱码
- jsp远程调用资源图片,以附件形式下载
- java web项目 图片资源与部署目录分离
- Asp.net 组件开发中web资源文件(图片、js)的调用问题
- 读取jar包中的资源文件(图片等)
- 读取jar包中的资源文件(图片)
- web项目js css静态文件缓存解决
- 关于WEB-INF目录不提供外部访问及JSP引用 js,css 文件路径问题
- js以字符串的形式将图片上传到服务器
- Eclipse将web项目以war包形式导出
- Web项目中 Tomcat 与 资源文件的一些处理
- matlab中读取Mat文件,以图片形式保存
- 关于indexOf
- Openwrt make出现的错误
- BIOS 的 recovery 机制
- Alpha、Beta、RC、GA版本的区别
- WinDbg重建堆栈
- 以Jar形式为Web项目提供资源文件(JS、CSS与图片)
- Zend API 二(重要) (自用备注)
- 实例构造器是不是静态方法?
- Linux磁盘检测工具smartctl的使用和分析
- COPY NAV导航网格寻路(1)
- vmstat命令详解
- C++基础知识--static成员和const成员函数
- Java类加载原理解析
- linux——(4)openstack安装配置手册