这个答案不包括缩小和压缩。最好将单个 CSS/JS 资源的最小化委托给构建脚本,例如YUI 压缩机 Ant 任务 http://code.google.com/p/javaflight-code/wiki/YuiCompressorAntTask。针对每个请求手动执行此操作的成本太高。压缩(我假设您指的是 GZIP?)最好委托给您正在使用的 servlet 容器。手动执行过于复杂。例如,在 Tomcat 上,只需添加一个compression="on"
归因于<Connector>
元素在/conf/server.xml
.
The SystemEventListener http://docs.oracle.com/javaee/6/api/javax/faces/event/SystemEventListener.html已经是一个很好的第一步(除了一些PhaseListener
不必要)。接下来,您需要实现一个自定义ResourceHandler http://docs.oracle.com/javaee/6/api/javax/faces/application/ResourceHandler.html and Resource http://docs.oracle.com/javaee/6/api/javax/faces/application/Resource.html。这部分并不完全是微不足道的。如果您想独立于 JSF 实现,您需要进行大量的重新发明。
首先,在你的SystemEventListener http://docs.oracle.com/javaee/6/api/javax/faces/event/SystemEventListener.html,您想要创建新的UIOutput http://docs.oracle.com/javaee/6/api/javax/faces/component/UIOutput.html代表组合资源的组件,以便您可以使用添加它UIViewRoot#addComponentResource() http://docs.oracle.com/javaee/6/api/javax/faces/component/UIViewRoot.html#addComponentResource%28javax.faces.context.FacesContext,%20javax.faces.component.UIComponent,%20java.lang.String%29。你需要设置它的library
归因于某事unique您的自定义资源处理程序可以理解它。您需要将组合资源存储在应用程序范围的变量中unique基于资源组合的名称(可能是 MD5 哈希?),然后将此密钥设置为name
组件的属性。存储为应用程序范围的变量对于服务器和客户端来说都具有缓存优势。
像这样的事情:
String combinedResourceName = CombinedResourceInfo.createAndPutInCacheIfAbsent(resourceNames);
UIOutput component = new UIOutput();
component.setRendererType(rendererType);
component.getAttributes().put(ATTRIBUTE_RESOURCE_LIBRARY, CombinedResourceHandler.RESOURCE_LIBRARY);
component.getAttributes().put(ATTRIBUTE_RESOURCE_NAME, combinedResourceName + extension);
context.getViewRoot().addComponentResource(context, component, TARGET_HEAD);
然后按照你的习惯ResourceHandler http://docs.oracle.com/javaee/6/api/javax/faces/application/ResourceHandler.html实施,你需要实施createResource() http://docs.oracle.com/javaee/6/api/javax/faces/application/ResourceHandler.html#createResource%28java.lang.String,%20java.lang.String%29相应的方法来创建自定义Resource http://docs.oracle.com/javaee/6/api/javax/faces/application/Resource.html每当库匹配所需值时执行:
@Override
public Resource createResource(String resourceName, String libraryName) {
if (RESOURCE_LIBRARY.equals(libraryName)) {
return new CombinedResource(resourceName);
} else {
return super.createResource(resourceName, libraryName);
}
}
自定义的构造函数Resource http://docs.oracle.com/javaee/6/api/javax/faces/application/Resource.html实现应该根据名称获取组合资源信息:
public CombinedResource(String name) {
setResourceName(name);
setLibraryName(CombinedResourceHandler.RESOURCE_LIBRARY);
setContentType(FacesContext.getCurrentInstance().getExternalContext().getMimeType(name));
this.info = CombinedResourceInfo.getFromCache(name.split("\\.", 2)[0]);
}
这个习俗Resource http://docs.oracle.com/javaee/6/api/javax/faces/application/Resource.html实施必须提供适当的getRequestPath() http://docs.oracle.com/javaee/6/api/javax/faces/application/Resource.html#getRequestPath%28%29方法返回一个 URI,然后该 URI 将包含在渲染中<script>
or <link>
元素:
@Override
public String getRequestPath() {
FacesContext context = FacesContext.getCurrentInstance();
String path = ResourceHandler.RESOURCE_IDENTIFIER + "/" + getResourceName();
String mapping = getFacesMapping();
path = isPrefixMapping(mapping) ? (mapping + path) : (path + mapping);
return context.getExternalContext().getRequestContextPath()
+ path + "?ln=" + CombinedResourceHandler.RESOURCE_LIBRARY;
}
现在,HTML 渲染部分应该没问题了。它看起来像这样:
<link type="text/css" rel="stylesheet" href="/playground/javax.faces.resource/dd08b105bf94e3a2b6dbbdd3ac7fc3f5.css.xhtml?ln=combined.resource" />
<script type="text/javascript" src="/playground/javax.faces.resource/2886165007ccd8fb65771b75d865f720.js.xhtml?ln=combined.resource"></script>
接下来,您必须拦截浏览器发出的组合资源请求。这是最难的部分。首先,按照您的习惯ResourceHandler http://docs.oracle.com/javaee/6/api/javax/faces/application/ResourceHandler.html实施,你需要实施handleResourceRequest() http://docs.oracle.com/javaee/6/api/javax/faces/application/ResourceHandler.html#handleResourceRequest%28javax.faces.context.FacesContext%29相应的方法:
@Override
public void handleResourceRequest(FacesContext context) throws IOException {
if (RESOURCE_LIBRARY.equals(context.getExternalContext().getRequestParameterMap().get("ln"))) {
streamResource(context, new CombinedResource(getCombinedResourceName(context)));
} else {
super.handleResourceRequest(context);
}
}
然后你必须完成实现自定义其他方法的全部工作Resource http://docs.oracle.com/javaee/6/api/javax/faces/application/Resource.html相应实施,例如getResponseHeaders() http://docs.oracle.com/javaee/6/api/javax/faces/application/Resource.html#getResponseHeaders%28%29它应该返回正确的缓存标头,getInputStream() http://docs.oracle.com/javaee/6/api/javax/faces/application/Resource.html#getInputStream%28%29这应该返回InputStream
单一资源中的组合资源InputStream
and userAgentNeedsUpdate() http://docs.oracle.com/javaee/6/api/javax/faces/application/Resource.html#userAgentNeedsUpdate%28javax.faces.context.FacesContext%29它应该正确响应缓存相关的请求。
@Override
public Map<String, String> getResponseHeaders() {
Map<String, String> responseHeaders = new HashMap<String, String>(3);
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN_RFC1123_DATE, Locale.US);
sdf.setTimeZone(TIMEZONE_GMT);
responseHeaders.put(HEADER_LAST_MODIFIED, sdf.format(new Date(info.getLastModified())));
responseHeaders.put(HEADER_EXPIRES, sdf.format(new Date(System.currentTimeMillis() + info.getMaxAge())));
responseHeaders.put(HEADER_ETAG, String.format(FORMAT_ETAG, info.getContentLength(), info.getLastModified()));
return responseHeaders;
}
@Override
public InputStream getInputStream() throws IOException {
return new CombinedResourceInputStream(info.getResources());
}
@Override
public boolean userAgentNeedsUpdate(FacesContext context) {
String ifModifiedSince = context.getExternalContext().getRequestHeaderMap().get(HEADER_IF_MODIFIED_SINCE);
if (ifModifiedSince != null) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN_RFC1123_DATE, Locale.US);
try {
info.reload();
return info.getLastModified() > sdf.parse(ifModifiedSince).getTime();
} catch (ParseException ignore) {
return true;
}
}
return true;
}
我在这里有一个完整的概念证明,但是代码太多,无法作为答案发布。以上只是部分内容,旨在帮助您朝正确的方向发展。我认为缺少的方法/变量/常量声明是不言自明的,足以编写您自己的声明,否则请告诉我。
Update:根据评论,您可以通过以下方式收集资源:CombinedResourceInfo
:
private synchronized void loadResources(boolean forceReload) {
if (!forceReload && resources != null) {
return;
}
FacesContext context = FacesContext.getCurrentInstance();
ResourceHandler handler = context.getApplication().getResourceHandler();
resources = new LinkedHashSet<Resource>();
contentLength = 0;
lastModified = 0;
for (Entry<String, Set<String>> entry : resourceNames.entrySet()) {
String libraryName = entry.getKey();
for (String resourceName : entry.getValue()) {
Resource resource = handler.createResource(resourceName, libraryName);
resources.add(resource);
try {
URLConnection connection = resource.getURL().openConnection();
contentLength += connection.getContentLength();
long lastModified = connection.getLastModified();
if (lastModified > this.lastModified) {
this.lastModified = lastModified;
}
} catch (IOException ignore) {
// Can't and shouldn't handle it here anyway.
}
}
}
}
(上面的方法被调用reload()
方法和 getter 取决于要设置的属性之一)
这是如何CombinedResourceInputStream
看起来像:
final class CombinedResourceInputStream extends InputStream {
private List<InputStream> streams;
private Iterator<InputStream> streamIterator;
private InputStream currentStream;
public CombinedResourceInputStream(Set<Resource> resources) throws IOException {
streams = new ArrayList<InputStream>();
for (Resource resource : resources) {
streams.add(resource.getInputStream());
}
streamIterator = streams.iterator();
streamIterator.hasNext(); // We assume it to be always true; CombinedResourceInfo won't be created anyway if it's empty.
currentStream = streamIterator.next();
}
@Override
public int read() throws IOException {
int read = -1;
while ((read = currentStream.read()) == -1) {
if (streamIterator.hasNext()) {
currentStream = streamIterator.next();
} else {
break;
}
}
return read;
}
@Override
public void close() throws IOException {
IOException caught = null;
for (InputStream stream : streams) {
try {
stream.close();
} catch (IOException e) {
if (caught == null) {
caught = e; // Don't throw it yet. We have to continue closing all other streams.
}
}
}
if (caught != null) {
throw caught;
}
}
}
Update 2:OmniFaces 提供了具体且可重用的解决方案。也可以看看CombinedResourceHandler展示页面 http://showcase.omnifaces.org/resourcehandlers/CombinedResourceHandler and API文档 http://omnifaces.org/docs/javadoc/current/org/omnifaces/resourcehandler/CombinedResourceHandler.html了解更多详情。