Improve Spring Resource Handling support

This commit improves support of the Resource Handling features
introduced in Spring Framework 4.1. Those features add new ways to
resolve and transform static resources in applications.
See [this blog
post](https://spring.io/blog/2014/07/24/spring-framework-4-1-handling-static-web-resources)
for more details.

The `ResourceUrlEncodinFilter` is added for compatible template engines:
Velocity and Thymeleaf. It assists them with rewriting the URLs of
static resources when rendering templates.

New keys are added in the `ResourceProperties` in order to configure
the Resource Handling chain. `ResourceResolvers` and
`ResourceTransformers` are registered accordingly in
`WebMvcAutoConfiguration`.

Here is an example of enabling a `ContentVersionStrategy` on all
static resources, meaning their names will be changed for cache
busting purposes by adding a content hash at the end of the file name.
Like "/js/jquery.js -> /js/jquery-872ca6a9fdda9e2c1516a84cff5c3bc6.js".

```
spring.resources.chain.enabled:true
spring.resources.chain.strategy.content.enabled:true
spring.resources.chain.strategy.content.paths:/**
```

Closes gh-1604
Closes gh-3123
This commit is contained in:
Brian Clozel
2015-06-25 15:05:43 +02:00
committed by Stephane Nicoll
parent e34bdcdd9a
commit dd561d15cf
9 changed files with 354 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-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.
@@ -31,6 +31,7 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
import org.springframework.web.servlet.support.RequestContext;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
@@ -42,6 +43,7 @@ import org.thymeleaf.templateresolver.TemplateResolver;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -180,4 +182,12 @@ public class ThymeleafAutoConfigurationTests {
}
}
@Test
public void registerResourceHandlingFilter() throws Exception {
this.context.register(ThymeleafAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(ResourceUrlEncodingFilter.class));
}
}

View File

@@ -37,6 +37,7 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
import org.springframework.web.servlet.support.RequestContext;
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
@@ -45,6 +46,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
/**
@@ -183,6 +185,12 @@ public class VelocityAutoConfigurationTests {
assertThat(resolver, instanceOf(EmbeddedVelocityViewResolver.class));
}
@Test
public void registerResourceHandlingFilter() throws Exception {
registerAndRefreshContext();
assertNotNull(this.context.getBean(ResourceUrlEncodingFilter.class));
}
private void registerAndRefreshContext(String... env) {
EnvironmentTestUtils.addEnvironment(this.context, env);
this.context.register(VelocityAutoConfiguration.class);

View File

@@ -60,10 +60,21 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.resource.AppCacheManifestTransformer;
import org.springframework.web.servlet.resource.CachingResourceResolver;
import org.springframework.web.servlet.resource.CachingResourceTransformer;
import org.springframework.web.servlet.resource.ContentVersionStrategy;
import org.springframework.web.servlet.resource.CssLinkResourceTransformer;
import org.springframework.web.servlet.resource.FixedVersionStrategy;
import org.springframework.web.servlet.resource.PathResourceResolver;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
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.view.AbstractView;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
@@ -82,6 +93,7 @@ import static org.junit.Assert.assertThat;
* @author Dave Syer
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Brian Clozel
*/
public class WebMvcAutoConfigurationTests {
@@ -124,6 +136,10 @@ public class WebMvcAutoConfigurationTests {
assertThat(mappingLocations.get("/webjars/**").size(), equalTo(1));
assertThat(mappingLocations.get("/webjars/**").get(0),
equalTo((Resource) new ClassPathResource("/META-INF/resources/webjars/")));
assertThat(getResourceResolvers("/webjars/**").size(), equalTo(1));
assertThat(getResourceTransformers("/webjars/**").size(), equalTo(0));
assertThat(getResourceResolvers("/**").size(), equalTo(1));
assertThat(getResourceTransformers("/**").size(), equalTo(0));
}
@Test
@@ -151,6 +167,54 @@ public class WebMvcAutoConfigurationTests {
assertThat(mappingLocations.size(), equalTo(0));
}
@Test
public void resourceHandlerChainEnabled() throws Exception {
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, "spring.resources.chain.enabled:true");
this.context.register(Config.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertThat(getResourceResolvers("/webjars/**").size(), equalTo(2));
assertThat(getResourceTransformers("/webjars/**").size(), equalTo(1));
assertThat(getResourceResolvers("/**").size(), equalTo(2));
assertThat(getResourceTransformers("/**").size(), equalTo(1));
assertThat(getResourceResolvers("/**"), contains(instanceOf(CachingResourceResolver.class),
instanceOf(PathResourceResolver.class)));
assertThat(getResourceTransformers("/**"), contains(instanceOf(CachingResourceTransformer.class)));
}
@Test
public void resourceHandlerChainCustomized() throws Exception {
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context,
"spring.resources.chain.enabled:true", "spring.resources.chain.cache:false",
"spring.resources.chain.strategy.content.enabled:true",
"spring.resources.chain.strategy.content.paths:/**,/*.png",
"spring.resources.chain.strategy.fixed.enabled:true",
"spring.resources.chain.strategy.fixed.version:test",
"spring.resources.chain.strategy.fixed.paths:/**/*.js",
"spring.resources.chain.html5AppCache:true");
this.context.register(Config.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertThat(getResourceResolvers("/webjars/**").size(), equalTo(2));
assertThat(getResourceTransformers("/webjars/**").size(), equalTo(2));
assertThat(getResourceResolvers("/**").size(), equalTo(2));
assertThat(getResourceTransformers("/**").size(), equalTo(2));
assertThat(getResourceResolvers("/**"), contains(
instanceOf(VersionResourceResolver.class), instanceOf(PathResourceResolver.class)));
assertThat(getResourceTransformers("/**"), contains(instanceOf(CssLinkResourceTransformer.class),
instanceOf(AppCacheManifestTransformer.class)));
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers("/**").get(0);
assertThat(resolver.getStrategyMap().get("/*.png"), instanceOf(ContentVersionStrategy.class));
assertThat(resolver.getStrategyMap().get("/**/*.js"), instanceOf(FixedVersionStrategy.class));
}
@Test
public void noLocaleResolver() throws Exception {
load(AllResources.class);
@@ -220,6 +284,18 @@ public class WebMvcAutoConfigurationTests {
return getMappingLocations(mapping);
}
protected List<ResourceResolver> getResourceResolvers(String mapping) {
SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context.getBean("resourceHandlerMapping");
ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler.getHandlerMap().get(mapping);
return resourceHandler.getResourceResolvers();
}
protected List<ResourceTransformer> getResourceTransformers(String mapping) {
SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context.getBean("resourceHandlerMapping");
ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler.getHandlerMap().get(mapping);
return resourceHandler.getResourceTransformers();
}
@SuppressWarnings("unchecked")
protected Map<String, List<Resource>> getMappingLocations(HandlerMapping mapping)
throws IllegalAccessException {