Add a ResourceResolver implementation for WebJars
Prior to this commit, WebJars users needed to use versioned links within
templates for WebJars resources, such as `/jquery/1.2.0/jquery.js`.
This can be rather cumbersome when updating libraries - all references
in templates need to be updated.
One could use version-less links in templates, but needed to add a
specific MVC Handler that uses webjars.org's webjar-locator library.
While this approach makes maintaing templates easier, this makes HTTP
caching strategies less optimal.
This commit adds a new WebJarsResourceResolver that search for resources
located in WebJar locations. This ResourceResolver is automatically
registered if the "org.webjars:webjars-locator" dependency is present.
Registering WebJars resource handling can be done like this:
```java
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:META-INF/resources/webjars")
.resourceChain(true)
.addResolver(new WebJarsResourceResolver());
}
```
Issue: SPR-12323
polish
This commit is contained in:
@@ -33,6 +33,7 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCache;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.springframework.http.CacheControl;
|
||||
@@ -51,6 +52,7 @@ import org.springframework.web.servlet.resource.ResourceTransformer;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlProvider;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
|
||||
import org.springframework.web.servlet.resource.VersionResourceResolver;
|
||||
import org.springframework.web.servlet.resource.WebJarsResourceResolver;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
|
||||
@@ -78,6 +80,9 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
private static final String RESOURCE_URL_PROVIDER = "mvcResourceUrlProvider";
|
||||
|
||||
private static final boolean isWebJarsAssetLocatorPresent = ClassUtils.isPresent(
|
||||
"org.webjars.WebJarAssetLocator", ResourcesBeanDefinitionParser.class.getClassLoader());
|
||||
|
||||
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
@@ -302,6 +307,12 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
|
||||
}
|
||||
|
||||
if (isAutoRegistration) {
|
||||
if(isWebJarsAssetLocatorPresent) {
|
||||
RootBeanDefinition webJarsResolverDef = new RootBeanDefinition(WebJarsResourceResolver.class);
|
||||
webJarsResolverDef.setSource(source);
|
||||
webJarsResolverDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
resourceResolvers.add(webJarsResolverDef);
|
||||
}
|
||||
RootBeanDefinition pathResolverDef = new RootBeanDefinition(PathResourceResolver.class);
|
||||
pathResolverDef.setSource(source);
|
||||
pathResolverDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCache;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.servlet.resource.CachingResourceResolver;
|
||||
import org.springframework.web.servlet.resource.CachingResourceTransformer;
|
||||
import org.springframework.web.servlet.resource.CssLinkResourceTransformer;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.web.servlet.resource.PathResourceResolver;
|
||||
import org.springframework.web.servlet.resource.ResourceResolver;
|
||||
import org.springframework.web.servlet.resource.ResourceTransformer;
|
||||
import org.springframework.web.servlet.resource.VersionResourceResolver;
|
||||
import org.springframework.web.servlet.resource.WebJarsResourceResolver;
|
||||
|
||||
/**
|
||||
* Assists with the registration of resource resolvers and transformers.
|
||||
@@ -40,6 +42,9 @@ public class ResourceChainRegistration {
|
||||
|
||||
private static final String DEFAULT_CACHE_NAME = "spring-resource-chain-cache";
|
||||
|
||||
private static final boolean isWebJarsAssetLocatorPresent = ClassUtils.isPresent(
|
||||
"org.webjars.WebJarAssetLocator", ResourceChainRegistration.class.getClassLoader());
|
||||
|
||||
private final List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>(4);
|
||||
|
||||
private final List<ResourceTransformer> transformers = new ArrayList<ResourceTransformer>(4);
|
||||
@@ -98,6 +103,9 @@ public class ResourceChainRegistration {
|
||||
protected List<ResourceResolver> getResourceResolvers() {
|
||||
if (!this.hasPathResolver) {
|
||||
List<ResourceResolver> result = new ArrayList<ResourceResolver>(this.resolvers);
|
||||
if(isWebJarsAssetLocatorPresent) {
|
||||
result.add(new WebJarsResourceResolver());
|
||||
}
|
||||
result.add(new PathResourceResolver());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.webjars.MultipleMatchesException;
|
||||
import org.webjars.WebJarAssetLocator;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* A {@code ResourceResolver} that delegates to the chain to locate a resource
|
||||
* and then attempts to find a matching versioned resource contained in a WebJar JAR file.
|
||||
*
|
||||
* <p>This allows WabJar users to use version-less paths in their templates, like {@code "/jquery/jquery.min.js"}
|
||||
* while this path is resolved to the unique version {@code "/jquery/1.2.0/jquery.min.js"}, which is a better fit
|
||||
* for HTTP caching and version management in applications.
|
||||
*
|
||||
* <p>This resolver requires the "org.webjars:webjars-locator" library on classpath, and is automatically
|
||||
* registered if that library is present.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 4.2
|
||||
* @see <a href="http://www.webjars.org">webjars.org</a>
|
||||
*/
|
||||
public class WebJarsResourceResolver extends AbstractResourceResolver {
|
||||
|
||||
private final static String WEBJARS_LOCATION = "META-INF/resources/webjars";
|
||||
|
||||
private final static int WEBJARS_LOCATION_LENGTH = WEBJARS_LOCATION.length();
|
||||
|
||||
private final WebJarAssetLocator webJarAssetLocator;
|
||||
|
||||
public WebJarsResourceResolver() {
|
||||
this.webJarAssetLocator = new WebJarAssetLocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource resolveResourceInternal(HttpServletRequest request, String requestPath,
|
||||
List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||
|
||||
return chain.resolveResource(request, requestPath, locations);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveUrlPathInternal(String resourceUrlPath,
|
||||
List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||
|
||||
String path = chain.resolveUrlPath(resourceUrlPath, locations);
|
||||
if (path == null) {
|
||||
try {
|
||||
int startOffset = resourceUrlPath.startsWith("/") ? 1 : 0;
|
||||
int endOffset = resourceUrlPath.indexOf("/", 1);
|
||||
if (endOffset != -1) {
|
||||
String webjar = resourceUrlPath.substring(startOffset, endOffset);
|
||||
String partialPath = resourceUrlPath.substring(endOffset);
|
||||
String webJarPath = webJarAssetLocator.getFullPath(webjar, partialPath);
|
||||
return chain.resolveUrlPath(webJarPath.substring(WEBJARS_LOCATION_LENGTH), locations);
|
||||
}
|
||||
}
|
||||
catch (MultipleMatchesException ex) {
|
||||
logger.warn("WebJar version conflict for \"" + resourceUrlPath + "\"", ex);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("No WebJar resource found for \"" + resourceUrlPath + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user