JSF2静态资源管理——组合、压缩

JSF2静态资源管理——组合、压缩,jsf,static,jsf-2,minify,Jsf,Static,Jsf 2,Minify,是否有人知道有一种方法可以动态组合/缩小所有h:outputStylesheet资源,然后在渲染阶段组合/缩小所有h:outputScript资源?可能需要使用基于组合资源字符串的密钥或其他内容缓存即将到来/缩小的资源,以避免过度处理 如果这个功能不存在,我想用它。有没有人对实现这样的事情的最佳方式有想法。我认为Servlet过滤器可以工作,但过滤器需要做的工作比需要的更多——基本上检查整个呈现的输出并替换匹配项。在渲染阶段实现某些东西似乎效果更好,因为所有静态资源都可用,而无需解析整个输出 谢

是否有人知道有一种方法可以动态组合/缩小所有h:outputStylesheet资源,然后在渲染阶段组合/缩小所有h:outputScript资源?可能需要使用基于组合资源字符串的密钥或其他内容缓存即将到来/缩小的资源,以避免过度处理

如果这个功能不存在,我想用它。有没有人对实现这样的事情的最佳方式有想法。我认为Servlet过滤器可以工作,但过滤器需要做的工作比需要的更多——基本上检查整个呈现的输出并替换匹配项。在渲染阶段实现某些东西似乎效果更好,因为所有静态资源都可用,而无需解析整个输出

谢谢你的建议

Edit:为了表明我并不懒惰,并将在一些指导下对此进行实际工作,这里有一个存根,它捕获脚本资源名称/库,然后将它们从视图中删除。正如你所看到的,我有一些关于下一步做什么的问题。。。我应该发出http请求并获取要合并的资源,然后合并它们并将它们保存到资源缓存中吗

package com.davemple.jsf.listener;
导入java.util.ArrayList;
导入java.util.List;
导入javax.faces.component.UIComponent;
导入javax.faces.component.UIOutput;
导入javax.faces.component.UIViewRoot;
导入javax.faces.context.FacesContext;
导入javax.faces.event.AbortProcessingException;
导入javax.faces.event.PhaseEvent;
导入javax.faces.event.PhaseId;
导入javax.faces.event.PhaseListener;
导入javax.faces.event.PreRenderViewEvent;
导入javax.faces.event.SystemEvent;
导入javax.faces.event.SystemEventListener;
导入org.apache.log4j.Logger;
/**
*组合CSS/Javascript资源的侦听器
* 
*@作者大卫·马普尔
*
*/
公共类ResourceComboListener实现了PhaseListener、SystemEventListener{
私有静态最终长serialVersionUID=-843094548108169344353L;
私有静态最终记录器Logger=Logger.getLogger(ResourceComboListener.class);
@凌驾
公共PhaseId getPhaseId(){
返回PhaseId.RESTORE_视图;
}
/*
*(非Javadoc)
*@see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
*/
公共无效后阶段(阶段事件事件){
FacesContext.getCurrentInstance().getViewRoot().subscribeToViewEvent(PreRenderViewEvent.class,this);
}
/*
*(非Javadoc)
*@see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
*/
前期公共无效(阶段事件事件){
//这里什么都没有
}
/*
*(非Javadoc)
*@see javax.faces.event.SystemEventListener#isListenerForSource(java.lang.Object)
*/
公共布尔isListenerForSource(对象源){
返回(UIViewRoot的源实例);
}
/*
*(非Javadoc)
*@see javax.faces.event.SystemEventListener#processEvent(javax.faces.event.SystemEvent)
*/
public void processEvent(SystemEvent事件)引发AbortProcessingException{
FacesContext context=FacesContext.getCurrentInstance();
UIViewRoot viewRoot=context.getViewRoot();
List scriptsToRemove=new ArrayList();
如果(!context.isPostback()){
对于(UIComponent组件:viewRoot.getComponentResources(上下文,“head”)){
if(component.getClass().equals(UIOutput.class)){
UIOutput UIOutput=(UIOutput)组件;
if(uiOutput.getRenderType().equals(“javax.faces.resource.Script”)){
String library=uiOutput.getAttributes().get(“库”).toString();
字符串名称=uiOutput.getAttributes().get(“名称”).toString();
//发出https请求以获取资源?
//然后合并并保存到资源缓存?
//插入新的UIOutput脚本?
scriptsToRemove.add(组件);
}
}
}
用于(UIComponent组件:scriptsToRemove){
getComponentResources(上下文,“head”).remove(组件);
}
}
}
}
您可能希望在实施自己的解决方案之前进行评估。我已经在几个项目中使用过它,它是一个巨大的成功。它在JSF1.2项目中使用,但我认为将其扩展到JSF2.0是很容易的。试试看。

这个答案不包括缩小和压缩。缩小单个CSS/JS资源的规模最好被委托构建脚本,如。在每次请求时手动执行此操作成本太高。压缩(我想你是说GZIP?)最好委托给你正在使用的servlet容器。手工操作过于复杂。例如,在Tomcat上,需要向
/conf/server.xml
中的
元素添加
compression=“On”
属性


这已经是很好的第一步(除了一些不必要的
PhaseListener
之外)。接下来,您需要实现一个自定义和。这一部分并非微不足道。如果您想成为独立于JSF实现的人,就需要进行大量的改造

首先,在中,您希望创建表示组合资源的新组件,以便可以使用添加它。您需要将其
library
属性设置为自定义资源处理程序可以理解的唯一属性。您需要根据资源的组合(可能是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);
T
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);
@Override
public Resource createResource(String resourceName, String libraryName) {
    if (RESOURCE_LIBRARY.equals(libraryName)) {
        return new CombinedResource(resourceName);
    } else {
        return super.createResource(resourceName, libraryName);
    }
}
public CombinedResource(String name) {
    setResourceName(name);
    setLibraryName(CombinedResourceHandler.RESOURCE_LIBRARY);
    setContentType(FacesContext.getCurrentInstance().getExternalContext().getMimeType(name));
    this.info = CombinedResourceInfo.getFromCache(name.split("\\.", 2)[0]);
}
@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;
}
@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);
    }
}
@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;
}
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.
            }
        }
    }
}
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;
        }
    }

}