Spring Framework 4.1 RC 发布,支持静态资源处理

jopen 10年前

Spring Framework 是一个开源的Java/Java EE全功能栈(full-stack)的应用程序框架,以Apache许可证形式发布,也有.NET平台上的移植版本。该框架基于 Expert One-on-One Java EE Design and Development(ISBN 0-7645-4385-7)一书中的代码,最初由 Rod Johnson 和 Juergen Hoeller等开发。Spring Framework 提供了一个简易的开发方式,这种开发方式,将避免那些可能致使底层代码变得繁杂混乱的大量的属性文件和帮助类。

本周 Spring Framework 4.1 RC 版本发布了,现在是测试该版本新特性最好的时间,看看到底这个版本能给你的应用带来多少改进。新特性之一便是灵活的解析和转换静态 Web 资源,你可以使用 ResourceHttpRequestHandlers 来处理静态资源。

静态资源处理的流程如下:

  Resource link in a template source file        |        | Resource path (like "/css/main.css")        v     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")        v     Resource link in a rendered template

新的推荐的项目文件布局:

spring-app/  |- build.gradle  |- client/  |  |- src/  |  |  |- css/  |  |  |- js/  |  |     |- main.js  |  |- test/  |  |- build.gradle  |  |- gulpfile.js  |- server/  |  |- src/main/java/  |  |– build.gradle

应用场景:

1. 避免静态资源的缓存
2. 新的项目结构更加便于资源引用
3. 更方便的模板引擎集成
4. 完整的构建工具链

详细介绍如下:

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 and ResourceTransformers

ResourceResolvers and ResourceTransformers are at the very core of this new feature.

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 ResourceHttpRequestHandlers:

  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")        v     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")        v     Resource link in a rendered template 

Now, let's take a look at what ResourceResolvers implementations have to offer:

Resolver Name Goal
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.

And now, ResourceTransformers:

Transformer Name Goal
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.

Yet another asset pipeline?

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.

In Spring Framework 4.1, we've chosen a path that relies on optimizing resources at build time using the best tools out there for your application and leveraging Resolvers and Transformers at runtime. For JavaScript applications, we want to leverage the same toolchains used by JavaScript developers, like grunt and gulp to optimize resources at build time. Same thing about Dart and TypeScript - native tooling always offers the best experience.

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!)

Looking at the upcoming standards, such as HTTP/2 and ECMAScript 6, it makes even more sense - defining changes will happen in this space in the next years.

Resource versioning

Static web resources versioning is a central concern when pushing web apps to production and very much a server-side concern. Spring Framework 4.1 aims to provide first class support through various strategies including content-based hashing (like in Git, also known as fingerprinting) as well as versions that apply to entire sets of files (e.g. required for working with JavaScript module loaders).

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).

Source code layout

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.

We use the same layout in Project Sagan and I discussed the rationale behind this in details in a previous screencast, Project Sagan: client-side architecture.

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?

Spring Resource Handling showcase application

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