Since 3.0.4.RELEASE, Spring MVC provides help in managing web page static resources (images, CSS, javascripts). The most important for me was to make browser cache resources for a long time while loading the new version when the content changes. This is a best practice recommendation by YSlow and others. You just need to set a far future Expires header and change the resource URL each time the content changes.
The feature description in Spring documentation looks simple, but in reality it requires some additional work:
To introduce versioning, we need to add some string into image URLs. It could be a (Maven) version number or just whatever unique identifier. I decided to use timestamp - it's better in development environment where I have the same Maven version (1.2.3-SNAPSHOT) but the content changes all the time.
in pom.xml:
make sure your Spring configuration file is filtered by Maven, so that ${buildTimestamp} gets replaced by the corresponding Maven property:
The only thing left is to replace all (or some) image URLs. One possibility (as suggested by the Spring doc) is to use <spring:eval> and <spring:url> tags on each JSP page. But I very much dislike such duplicating boilerplate. So I introduced an interceptor for all pages I need:
And the interceptor itself is very simple:
Finally, we can use image links like:
which will translate to something like:
Being an old fan of Tapestry, I cannot restrain myself from demonstrating its superiority :)
In Tapestry, all you need to do is:
The feature description in Spring documentation looks simple, but in reality it requires some additional work:
- in web.xml, you should map the DispatcherServlet to root path (<url-pattern>/</url-pattern>). It was mapped to *.do previously (as usual in Spring MVC projects)
- the location attributes relates to the root category (same level as WEB-INF). If you used to have images directory there and want to create mapping using mvc:resource, it should be like this: <mvc:resources mapping="/images/**" location="/images/" />
- don't forget the trailing slash in the location attribute (<mvc:resources mapping="/images/**" location="/images/" />)
To introduce versioning, we need to add some string into image URLs. It could be a (Maven) version number or just whatever unique identifier. I decided to use timestamp - it's better in development environment where I have the same Maven version (1.2.3-SNAPSHOT) but the content changes all the time.
<mvc:resources mapping="/assets/#{properties['buildTime']}/**" location="/" cache-period="31556926" /> <util:map id="properties"> <entry key="buildTime" value="${buildTimestamp}"/> </util:map>
in pom.xml:
<properties> <buildTimestamp>${maven.build.timestamp}</buildTimestamp> <maven.build.timestamp.format>yyyyMMddHHmmss</maven.build.timestamp.format> </properties>
make sure your Spring configuration file is filtered by Maven, so that ${buildTimestamp} gets replaced by the corresponding Maven property:
<resources> <resource> <directory>src/main/resources</directory> </resource> <resource> <filtering>true</filtering> <directory>src/main/resources</directory> <includes> <include>*.xml</include> </includes> </resource> </resources>
The only thing left is to replace all (or some) image URLs. One possibility (as suggested by the Spring doc) is to use <spring:eval> and <spring:url> tags on each JSP page. But I very much dislike such duplicating boilerplate. So I introduced an interceptor for all pages I need:
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!-- You servlet mappings go here ...--> </props> </property> <property name="interceptors" > <list> <ref bean="resourceUrlInterceptor"/> </list> </property> </bean> <bean id="resourceUrlInterceptor" class="com.example.interceptor.ResourceUrlInterceptor"> <property name="properties" ref="properties" /> </bean>
And the interceptor itself is very simple:
public class ResourceUrlInterceptor extends HandlerInterceptorAdapter { private Map<String, String> properties; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { request.setAttribute("resourcesPath", response.encodeURL(request.getContextPath() + "/resources/" + properties.get("buildTime"))); return true; } public void setProperties(Map<String, String> properties) { this.properties = properties; } }
Finally, we can use image links like:
<img src="${resourcesPath}/images/icons/myicon.png" />
which will translate to something like:
<img src="/myapp/resources/20110725101003/images/icons/myicon.png" />
Being an old fan of Tapestry, I cannot restrain myself from demonstrating its superiority :)
In Tapestry, all you need to do is:
<img src="${context:images/icons/myicon.png}" />
No comments:
Post a Comment