This week, Juergen announced the Spring Framework 4.1 release candidate. Now is the time to test those new features and see how they can make your applications better!
One of those new features is the flexible resolution and transformation of static web resources. Spring framework already allows you to serve static resources using
ResourceHttpRequestHandlers. This feature gives you more power and new possibilities.
ResourceResolvers can resolve resources, given their URL path. They can also resolve the externally facing public URL path for clients to use, given their internal resource path.
ResourceTransformers can modify the content of resolved resources.
Here’s a diagram illustrating what happens when serving static resources with
Request for Resource | | HTTP request v Resolvers chain: FirstResolver, SecondResolver, ThirdResolver (each resolver can return the resource or delegate to the next one) | | Resolved Resource v Transformers chain: FirstTransformer, SecondTransformer (each transformer can transform the resource or just pass it along without modification) | | Transformed Resource v HTTP Response with Resource content
Here’s another one showing how a chain of
ResourceResolvers can update links to resources for HTTP client’s use:
Resource link in a template source file
| Resource path (like “/css/main.css”)
Resolvers chain: FirstResolver, SecondResolver, ThirdResolver
(each resolver can modify the resource path or delegate to the next one)
| Updated resource path (like “/css/main-0e37f12.css”)
Resource link in a rendered template
Now, let’s take a look at what
ResourceResolvers implementations have to offer:
|PathResourceResolver||finds resources under configured locations (classpath, file system…) matching to the request path|
|CachingResourceResolver||resolves resources from a Cache instance or delegates to the next Resolver in the chain|
|GzipResourceResolver||finds variations of a resource with a “.gz” extension when HTTP clients support gzip compression|
|VersionResourceResolver||resolves request paths containing a version string, i.e. version information about the resource being requested. This resolver can be useful to set up HTTP caching strategies by changing resources’ URLs as they are updated.|
|CssLinkResourceTransformer||modifies links in a CSS file to match the public URL paths that should be exposed to clients|
|CachingResourceTransformer||caches the result of transformations in a Cache or delegates to the next Transformer in the chain|
|AppCacheManifestTransfomer||helps handling resources within HTML5 AppCache manifests for HTML5 offline applications|
The key goal of those new additions to
ResourceHttpRequestHandlers is to make it easy to optimize and work with optimized static resources for front-end performance.
Many libraries and frameworks address those issues with full, integrated assets pipelines which often offer strong, opinionated solutions about the programming languages, technologies and project structure to use. Those asset pipelines take care of resources optimization when creating the deployable application and/or while the application is running.
Those ecosystems are rich (actually much richer than the options available in Java) and constantly evolving. We believe that relying on those ecosystems and on a flexible solution in the Framework is the best approach here.
So your application should find the right balance and leverage:
* transpiling, minifying, concatenating… tasks at build time using native tools for your client side application
* resolvers and transformers provided with the framework (and also create your owns!)
Underlying all this is the idea of “cache busting” where resources are served with aggressive HTTP cache directives (e.g. 1 year into the future) and relying on version-related changes in the URL to “bust” the cache when necessary. This could be a content-based hash version that changes whenever the content of the file changes or a version determined through some other means (e.g. simple property, git commit sha, etc).
Another very important question is where your sources are located and how your application is organized - as Java developers, we’re used to find those in
src/main/webapp. But is it really the best location?
Nowadays, most web applications are made of a client application running in the browser and a server application, both communicating over HTTP or websockets. Each of those can have its own dependencies, tests, build tools, etc. So why can’t we decouple those and reflect that separation of concerns in our codebase?
Breaking your web application in modules - a client module and a server module - can dramatically improve your development experience and gives the freedom your application needs.
Here’s an example of project layout:
spring-app/ |- build.gradle |- client/ | |- src/ | | |- css/ | | |- js/ | | |- main.js | |- test/ | |- build.gradle | |- gulpfile.js |- server/ | |- src/main/java/ | |– build.gradle
Both Resolvers/Transformers and build tools can offer similar features around resource handling. So which one should we use?
In the Spring Resource Handling showcase application, we are demonstrating several key features:
- Cache busting static resources in HTML responses, CSS files, and HTML5 appcache manifests
- A new project layout as mentioned earlier
- Template engine integrations, such as Groovy markup templates and Handlebars
- Using LESS as a CSS alternative, with the client side pre-processor during development and a build processor for production
- A complete build tool chain, using Gradle and gulp; future examples can demonstrate the same features using grunt, maven, etc
Note that this new project layout has two key advantages:
1. Better developer experience, since resources are served unoptimized, directly from the disk at development time
2. Optimal performance in production, since static resources are optimized by the build and packaged in a webJAR - so they are ultimately served from the classpath in production
The Spring Resource Handling showcase application is still work in progress, and we’re preparing enhancements for easier configuration (see SPR-11982); of course, the feedback of the community will be very useful here.