I’m using Maven2 to get the jars and Ant to build the project. To fetch Spring 3.0 binaries, you have to add the following repository if you don’t have it:
<repository> <id>SpringSource Enterprise Bundle Repositorys</id> <url>http://repository.springsource.com/maven/bundles/milestone</url> </repository>
and the spring packages that you need since the packaging has changed from 2.5.x. Instead of a single spring.jar file, now there is one per feature so you have to sort out what jars to include in the project. I ended up with the following:
<properties> <spring.version>3.0.0.M2</spring.version> </properties> <dependencies> ... <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.transaction</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.web.servlet</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.test</artifactId> <version>${spring.version}</version> </dependency> </dependencies>
Once the jars are brought, I’ve replaced my old jarfiles with the all-new M2s then fired the build target.
The first issue I found was with the asm version.
Spring 2.5 uses a repackaged asm version 2.2.3 (more info here). The new Spring 3.0 M2 doesn’t package it anymore ( see comment) so I had to add it manually. I had to add the two asm 2.2.3 jars in order to resolve this dependency:
<dependency> <groupId>asm</groupId> <artifactId>asm-commons</artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>asm</groupId> <artifactId>asm</artifactId> <version>2.2.3</version> </dependency>
After refreshing the jars and redeployed, I hit the first roadblock, a ClassNoDefFoundError:
Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class net.sf.cglib.proxy.Enhancer ... Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.CodeVisitor
Odd, I didn’t have this error before but as it turns out, I was using a cglib version 2.1.3 which has a hard dependency on asm and I actually had an asm jar version 1.5.3 packaged along with the other jar dependencies. On top of this, there is a problem with an antlr version conflict. Different versions of antlr are required by Spring 3.0 and Hibernate 3.3.1 which I’m also using. This is Jar hell! So I put together a version requirment matrix to sort-out the dependencies between these libraries:
antlr | cglib | asm | |
Spring 3.0 M2 | 3.0.1 | 2.1.x +, depends on asm 1.5.3 | 2.2.3 |
Hibernate 3.3.1 | 2.7.x | none | 2.2.3 |
Fortunately the Hibernate folks have changed the default bytecode generation library to javassist starting with v3.2.2. The bummer is the cglib dependency on asm 1.5.3 since this version is in conflict with asm 2.2.3 required by both Spring and Hibernate. The solution is to use a repackaged cglib as explained here.
To get the nodep version of cglib, I added the following snippet in my pom.xml file:
<dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>2.2</version> </dependency>
I’m taking the opportunity and upgrade to the newer version 2.2 of cglib.
Next is antlr. Spring requires a different version of antlr than Hibernate but fortunately antlr has changed the package notation from antlr.* in v2.x to org.antlr.* in v3.x so it’s safe to include both jars. Let’s get both versions through Maven:
<dependency> <groupId>antlr</groupId> <artifactId>antlr</artifactId> <version>2.7.7</version> </dependency> <dependency> <groupId>org.antlr</groupId> <artifactId>antlr</artifactId> <version>3.0.1</version> </dependency>
Here’s the mix of jars that works for me with Spring & Hibernate:
antlr | cglib | asm | |
Spring 3.0 M2 & Hibernate 3.3.1 |
antlr-2.7.7.jar & antlr-3.0.1.jar |
cglib-nodep-2.2.jar | asm-2.2.3.jar |
I can’t help but notice the compatibility problems with using the Spring & Hibernate mix. It wasn’t easy to fix them and it still feels more of a hack than an actual solution. In Spring’s defense, this is a milestone version so I should expect some wrinkles. I had similar issues when upgrading Hibernate a while back. Hibernate was the first to change their packaging to use feature-centric jars (I think). I recall having issues around antlr and asm. I remember being reluctant at first about the new dependency to javassist but it worked just fine.
Next on the list is refactoring the controllers to use the new REST support of Spring. The majority of my controllers are extending from org.springframework.web.servlet.mvc.AbstractController so I’ll upgrade them to use @Controller stereotype. I’m already familiar with @Controller, I’m using it to route all top URLs found on Spincloud’s header (i.e. features). I use a simple approach: drop the inheritance to AbstractController and change handleRequestInternal:
@Controller public class WeatherDetailsController extends AbstractController { ... @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { //parse URI to extract stationIDs ... } }
with the much cooler:
@Controller public class WeatherDetailsController { ... @RequestMapping(value="/weather/{stationIds}", method = RequestMethod.GET) public ModelAndView displayWeather(@PathVariable("stationIds") String idz, HttpServletRequest request, HttpServletResponse response) { ... } }
Last, remove the carbonfive HandlerMapping bean in the spring servlet XML which used to look like this:
<bean class="carbonfive.spring.web.pathparameter.ParameterizedUrlHandlerMapping"> <property name="alwaysUseFullPath" value="true"/> <property name="mappings"> <props> <prop key="/weather/*">weatherDetailsController</prop> </props> </property> </bean>
Deployed and started the container successfully but when I hit a URL that was supposed to be routed through the new controller, I got the following error:
2009-03-07 20:55:49,072: WARN PageNotFound:1017 No mapping found for HTTP request with URI [/weather/15150] in DispatcherServlet with name 'weather'
Bummer: the URL is not routed to any controller. I initially suspected the DefaultAnnotationHandlerMapping I was using was to blame but after searching on the interweb and finding nothing else worthy and a couple of hours of research in my own code, I discovered that the problem was in web.xml. I had fine grained servlet-mapping URLs, including one that should have routed all URLs starting with /weather that looked like this:
<servlet-mapping> <servlet-name>weather</servlet-name> <url-pattern>/weather/*</url-pattern> </servlet-mapping>
which is not good anymore. After checking the web.xml file in the Petstore example, I noticed that the servlet mappings can use the greedy <url-pattern>/</url-pattern> to route to one controller then more specific patterns for routing to other controllers. Since I have more than one controller, I changed the mappings like below:
<servlet-mapping> <servlet-name>weather</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>tag</servlet-name> <url-pattern>/tag/*</url-pattern> </servlet-mapping>
Tested again the URL and it correctly routed to the right controller.
I was home-free after this bump. Cleaned-up the code, removed lots of XML (always popular), dropped the -now obsolete- URL parsing and validation and now I’m contemplating a much cleaner and more concise code.
There are other features that come with this Spring upgrade, detailed here and here. I especially like the sound of the promised scheduling namespace since I currently have to maintain long XML files to configure the timers that trigger the fetching of weather data. This will be one of the few remaining pieces of XML that I still have. A nice touch is the new @CookieValue.
Moving forward, Spring promises to deliver a host of exciting features:
– Declarative validation (JSR 303). I’m really excited about this one having used Hibernate’s JSR 303 implementation. I love this JSR, it’s a step in the right direction!
– JPA 2.0 support and early JEE 6 support. Details about what JEE6 will bring are available here.
– Spring Portlet MVC, based on the Portlet 2.0 API (JSR-286)
– Annotation based factory methods (working towards XML configuration obliteration). I use factory methods so I like this one too.
– A rumor that a JSON view will be available. I’ve written my own JSONServiceExporter and a JSONView for Spincloud (I use JSON for all front-end data pipes) but I’d switch to a leaner solution if available.
…and much more.
I’d like to see the annotation-based bean scopes (already available through Spring’s @ScopedProxy, part of the JavaConfig project). Also, better support for multiple data sources in a non-JTA environment (I’ll detail my problems with it in another post).
For now, I’m content. Except for a few (expected) wrinkles, Spring delivers again and it succeeds in keeping things exciting in the Java space. Way to go guys!