概述
一、背景
最近正在编写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 MINE_TYPE_MAP;
static {
MINE_TYPE_MAP = new HashMap();
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 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
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">
4.0.0
org.noahx.jarresource
resource
1.0-SNAPSHOT
jar
commons-io
commons-io
2.4
org.slf4j
slf4j-api
1.7.5
commons-lang
commons-lang
2.6
javax.servlet
servlet-api
2.5
provided
使用了commons-io与commons-lang第三方类包
五、核心代码开发(Web端)
作为Jar文件的使用端,只需要在web.xml中配置一个filter,就可以访问到Jar中的资源。
1、web.xml
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">
jar resource web
tagLibResourceFilter
org.noahx.jarresource.TagLibResourceFilter
resourcePath
/tagres
tagLibResourceFilter
/tagres/*
index.jsp
注意:由于Servlet 3.0以下无法通过程序获得url-pattern,所以在filter的参数中指定了一个同名路径来使用。filter会用这个路径名称在Web工程下创建资源目录(目录部署)。2、index.jsp(资源使用样例,JS、CSS与图片)
star-hover4.png
example.js (example)
tagres/中的内容就是Jar工程中所提供的资源。(下图为显示效果)
3、pom.xml
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">
4.0.0
org.noahx.jarresource
web
1.0-SNAPSHOT
war
org.noahx.jarresource
resource
1.0-SNAPSHOT
org.slf4j
slf4j-log4j12
1.7.5
runtime
六、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) 从Jar中直接读取,并没有copy资源的过程。
七、总结
这个Filter很好的解决了我在开发TagLib时遇到的资源引用问题,对我来说应该够用了。
我们项目中一般采用目录方式部署,我也更希望通过Web服务器来直接访问资源。
并没有采用maven来组织资源,因为我需要提供给非maven工程使用。
一些流行的mvc中也有类似的手法,可能是采用流方式读取(猜测),感兴趣的朋友可以查看这些mvc的代码。
八、源程序下载
下载包中提供了源代码以及打包后(target目录)的工程。
最后
以上就是深情水池为你收集整理的引入jar包里的css样式_以Jar形式为Web项目提供资源文件(JS、CSS与图片)的全部内容,希望文章能够帮你解决引入jar包里的css样式_以Jar形式为Web项目提供资源文件(JS、CSS与图片)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复