1. SpringMVC XML 配置
<!-- 简单URLaction映射 -->
<bean id="simpleUrlHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <!-- 静态资源处理器 --> <entry key="/r/**/**"> <!-- 自己扩展的SpingMVC ResourceHttpRequestHandler 类,增加了类是与淘宝CDN通过逗号(,)隔开 访问多个js的效果 此功能不能压缩JS 如果想实现压缩功能可通过maven 或者 ant 在编译的时候进行压缩 --> <bean class="com.yoro.core.springmvc.ResourceHttpRequestHandler"> <property name="locations"> <list> <!-- 只有相同目录的JS文件才能实现淘宝CDN通过逗号(,)隔开 访问多个js的效果 如 /r/ 目录下的js|css 文件 就能实现 /r/js/ 目录下的js文件也可以有同样的效果 但 /a/css 下面的文件 和 /r/css 就不能实现 有点小遗憾,因为时间关系待以后升级 --> <value>/r/</value> </list> </property> <!-- 启用静态资源浏览器缓存一个月 --> <!-- 通过浏览器进行的缓存 根据可浏览器实现方式不同有所差异 按刷新按扭缓存不会起 当是页面跳转或者地址栏输入则缓存会起作用 --> <!-- 更过浏览器缓存的资料和特效 可 搜索 cachecontrol 设置 cacheSeconds 缓存时间单位是秒 2592000 表示缓存30天 因为我自己每次新版本发布都会都js css 文件增加版本号 所以缓存时间我设置的比较长 --> <property name="cacheSeconds" value="2592000"></property> <property name="useExpiresHeader" value="true"></property> <property name="useCacheControlNoStore" value="true"></property> </bean> </entry> <entry key="/thirdparty/**/**"> <bean class="com.yoro.core.springmvc.ResourceHttpRequestHandler"> <property name="locations"> <list> <value>/thirdparty/</value> </list> </property> <!-- 启用静态资源浏览器缓存一个月 --> <property name="cacheSeconds" value="2592000"></property> <property name="useExpiresHeader" value="true"></property> <property name="useCacheControlNoStore" value="true"></property> </bean> </entry> </map> </property> <property name="order" value="1"></property> </bean>
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 List<Resource> locations; 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<Resource> 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<Resource> 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<Resource> resources, MediaType mediaType) throws IOException { long length = 0; //Calculation of multiple file length Iterator<Resource> 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<Resource> 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<Resource> 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<Resource> rs = new ArrayList<Resource>(); 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. * <p>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
通过逗号(,)分割他们现在就实现了通过一个请求加载多个资源文件的效果了