最近正在編寫TagLib,在開發的過程當中遇到一個資源文件引用問題。由於我開發的TagLib最終是以Jar包的形式提供給項目來用的,因此Jar包中必須包含我開發TagLib所需的JS、CSS與圖片等資源。問題就是Tag是在項目的Web工程中運行,如何訪問到jar中的資源。 javascript
我想了一下,應該有兩種方式: css
好處: html
缺點: java
好處: web
缺點: shell
最終我準備將一、2兩種狀況接合使用,默認會採用1複製文件到Web工程的方式。若是發現Web工程沒法複製文件則採用2流讀取方式。 apache
爲了進行兩種方式的切換定義一個Filter很是適合,能夠攔截請求干擾行爲,又能夠在init初始化時進行資源文件的複製。 api
從下面的目錄結構能夠看出主程序就是一個Filter類,org.noahx.jarresource.resource包下的內容就是我須要的資源目錄。Filter會自動判斷Web工程的部署方式(目錄與War)來決定複製資源目錄仍是直接流讀取。 安全
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。 服務器
<?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第三方類包
做爲Jar文件的使用端,只須要在web.xml中配置一個filter,就能夠訪問到Jar中的資源。
<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>注意:因爲Servlet 3.0如下沒法經過程序得到url-pattern,因此在filter的參數中指定了一個同名路徑來使用。filter會用這個路徑名稱在Web工程下建立資源目錄(目錄部署)。
<%@ 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工程中所提供的資源。(下圖爲顯示效果)
<?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>
[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資源文件的過程
[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目錄)的工程。