Huzzah! Watch out Portland ... I'm back.
I actually had an exit row and room to work on the flight back from Phoenix. I decided to take another look at tapestry-spring.
First off, please remember that it is alpha code! So the fact that it completely doesn't work shouldn't be a total surprise. I had packaged the critical hivemodule.xml file under WEB-INF, not META-INF. Chalk it up to force of habit ... that's what you do for Tapestry applications, but this is a standalone library.
The reason I didn't catch that in my integration tests was ... I'm a lazy, lazy man. I need to learn how to write proper integration tests for tapestry-spring using Maven. I've actually had some good luck starting up Jetty inside a (TestNG) test case and that's the likely course I'll pursue.
So, that's half the story. The other half is dealing with Spring's prototype beans. This was someting that completely surprised me about Spring, once people started to complain. If you define a bean as having the prototype lifecycle, it means that you get a new instance everytime you ask the BeanFactory for it.
I really dislike that approach, because it puts the client code in charge of managing the lifecycle of the bean.
By contrast, HiveMind covers similar functionality quite differently. When you get a service from the Registry (the equivalent of getting a bean from the BeanFactory), you are going to get a proxy. Same service interface, different implementation (brewed up on the spot). The lifecycle of the service is hidden inside this proxy. You can share this proxy around, pass it between threads, even serialize and deserialize it (I think ... there's a few issues around that) and it just works.
The rough equivalent of Spring's non-singleton/prototype beans are HiveMind services with the threaded lifecycle. When you invoke a method on the proxy, it finds or creates a per-thread instance of the service. The per-thread instance is cleaned up and discarded at the end of the request (it's effectively built around a servlet request/response cycle).
This is used all over the place inside Tapestry. What's nice is that a piece of code can treat a proxy to a normal service, and a proxy to a per-thread service identically. That concern (in the AOP sense) is buried inside the proxy. Makes testing easier too.
Now, back to Tapestry's @InjectObject annotation. It's just broken for Spring prototype beans, because it's built around HiveMind's design, not Spring's. So it gets the object out of HiveMind just once and holds onto it, passing it into pages and components via a constructor (this is part of Tapestry's enhanced subclass approach to AOP).
For prototype beans, that means that we get the bean just once per component, and are stuck with it for any instances of that component we instantiate. The same bean instance, once aquired from Spring, will be passed into each new instance of a given page or component.
So ... I'm expanding tapestry-spring to add a specialized annotation, just for Spring. @InjectSpring will create a synthetic getter method that always gets a fresh instance of the named bean from the BeanFactory. That should solve the problem in its entirety.
I just need to stuggle with Maven a little bit more; I want to create a single JAR, where part of the code is compiled with JDK 1.3, but the annotation part is compiled with JDK 1.5. I think I can do this, by having multiple source roots (so src/main and src/annotations) and some extra compile executions.
Ultimately, this code may move under the Tapestry top-level project at Apache, once that gets set up. In the meantime, living at JavaForge is perfectly ok.
But, in the meantime, I need to catch up on the new Doctor Who, waiting for me on my Tivo. I've been out of town that long.