1. SpringMVC XML 配置
2. 资源文件目录结构
3. 浏览器缓存效果
4.ResourceHttpRequestHandler 扩展类代码
package com.yoro.core.springmvc; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.activation.FileTypeMap; import javax.activation.MimetypesFileTypeMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestHandler; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.support.WebContentGenerator; public class ResourceHttpRequestHandler extends WebContentGenerator implements HttpRequestHandler, InitializingBean { private static final boolean jafPresent = ClassUtils.isPresent("javax.activation.FileTypeMap", ResourceHttpRequestHandler.class.getClassLoader()); private final static Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class); private Listlocations; public ResourceHttpRequestHandler() { super(METHOD_GET, METHOD_HEAD); } /** * Set a {@code List} of {@code Resource} paths to use as sources * for serving static resources. */ public void setLocations(List locations) { Assert.notEmpty(locations, "Locations list must not be empty"); this.locations = locations; } @Override public void afterPropertiesSet() throws Exception { if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) { logger.warn("Locations list is empty. No resources will be served"); } } @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { checkAndPrepare(request, response, true); // check whether a matching resource exists List resources = getResources(request); if (resources == null || resources.isEmpty()) { logger.debug("No matching resource found - returning 404"); response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // check the resource's media type MediaType mediaType = getMediaType((String)request .getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)); if (mediaType != null) { if (logger.isDebugEnabled()) { logger.debug("Determined media type '" + mediaType + "' for " + resources.get(0)); } } else { if (logger.isDebugEnabled()) { logger.debug("No media type found for " + resources.get(0) + " - not sending a content-type header"); } } for (Resource resource : resources) { // header phase if (!new ServletWebRequest(request, response) .checkNotModified(resource.lastModified())) { logger.debug("Resource not modified - returning 304"); break; } return; } setHeaders(response, resources, mediaType); // content phase if (METHOD_HEAD.equals(request.getMethod())) { logger.trace("HEAD request - skipping content"); return; } writeContent(response, resources); } protected MediaType getMediaType(String filename) { MediaType mediaType = null; String mimeType = getServletContext().getMimeType(filename); if (StringUtils.hasText(mimeType)) { mediaType = MediaType.parseMediaType(mimeType); } if (jafPresent && (mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType))) { MediaType jafMediaType = ActivationMediaTypeFactory.getMediaType(filename); if (jafMediaType != null && !MediaType.APPLICATION_OCTET_STREAM.equals(jafMediaType)) { mediaType = jafMediaType; } } return mediaType; } protected void setHeaders(HttpServletResponse response, List resources, MediaType mediaType) throws IOException { long length = 0; //Calculation of multiple file length Iterator iter = resources.iterator(); while (iter.hasNext()) { length += iter.next().contentLength(); } if (length > Integer.MAX_VALUE) { throw new IOException( "Resource content too long (beyond Integer.MAX_VALUE)"); } response.setContentLength((int) length); if (mediaType != null) { response.setContentType(mediaType.toString()); } } protected void writeContent(HttpServletResponse response, List resourcess) throws IOException { OutputStream out = response.getOutputStream(); InputStream in = null; try { for (Resource resource : resourcess) { try { in = resource.getInputStream(); StreamUtils.copy(in, out); } finally { try { in.close(); } catch (IOException ex) { } } } } finally { try { out.close(); } catch (IOException ex) { } } } protected List getResources(HttpServletRequest request) { String path = (String) request .getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); if (path == null) { throw new IllegalStateException("Required request attribute '" + HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set"); } if (!StringUtils.hasText(path) || isInvalidPath(path)) { if (logger.isDebugEnabled()) { logger.debug("Ignoring invalid resource path [" + path + "]"); } return null; } for (Resource location : this.locations) { try { if (logger.isDebugEnabled()) { logger.debug("Trying relative path [" + path + "] against base location: " + location); } List rs = new ArrayList (); String[] paths = path.split(","); for (String url : paths) { Resource resource = location.createRelative(url); if (resource.exists() && resource.isReadable()) { rs.add(resource); } } return rs; } catch (IOException ex) { logger.debug( "Failed to create relative resource - trying next resource location", ex); } } return null; } /** * Validates the given path: returns {@code true} if the given path is not a valid resource path. * The default implementation rejects paths containing "WEB-INF" or "META-INF" as well as paths * with relative paths ("../") that result in access of a parent directory. * @param path the path to validate * @return {@code true} if the path has been recognized as invalid, {@code false} otherwise */ protected boolean isInvalidPath(String path) { return (path.contains("WEB-INF") || path.contains("META-INF") || StringUtils.cleanPath(path).startsWith("..")); } /** * Inner class to avoid hard-coded JAF dependency. */ private static class ActivationMediaTypeFactory { private static final FileTypeMap fileTypeMap; static { fileTypeMap = loadFileTypeMapFromContextSupportModule(); } private static FileTypeMap loadFileTypeMapFromContextSupportModule() { // see if we can find the extended mime.types from the context-support module Resource mappingLocation = new ClassPathResource("org/springframework/mail/javamail/mime.types"); if (mappingLocation.exists()) { InputStream inputStream = null; try { inputStream = mappingLocation.getInputStream(); return new MimetypesFileTypeMap(inputStream); } catch (IOException ex) { // ignore } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException ex) { // ignore } } } } return FileTypeMap.getDefaultFileTypeMap(); } public static MediaType getMediaType(String filename) { String mediaType = fileTypeMap.getContentType(filename); return (StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null); } } }
5.实现淘宝CDN JS 请求例子
JS例子:http://127.0.0.1/r/js/alert.js,/js/application.js,/js/bootstrap.js
CSS例子:http://127.0.1.1/r/css/activity_style.css,/css/bootstrap_responsive.css
通过逗号(,)分割他们现在就实现了通过一个请求加载多个资源文件的效果了