The view continued to shift in the direction of the user on day 3; the topics for today were the Spring MVC web framework (with a diversion into Spring Web Flow since the instructor is heavily involved in that) and the Spring Security system, aka Acegi.
Spring MVC’s big advantage over other MVC frameworks (and model 1 setups where the request handling and business logic live in the JSP) is that there are tons of extension points for plugging in different behaviors. Don’t like the way your URLs get mapped to controllers? No problem, just swap in another HandlerMapping that does whatever you want. Want to handle internationalization in an application-specific way? Plug in a custom LocaleResolver (subclassing one of the existing ones if you just want to tweak the behavior a bit) and you’re good to go. Views can be just about anything: there is built-in support for JSPs, FreeMarker templates, Excel spreadsheets, PDFs, and a bunch of other view types, and the Spring team is eager to incorporate support for other view types people want.
And of course all the parts of the MVC framework use the same configuration format as the rest of Spring, so you get the benefits of dependency injection such as ease of unit testing. In most cases, if your site is using Spring MVC it is possible to unit-test all the behavior of your web tier without running in a servlet container. On the other hand, it’s in the web tier where you lose the ability to write your code independently of the Spring APIs; you pretty much have to tie your code tightly to the framework to use any of the features of Spring MVC. But Spring is no worse than any other MVC framework in that regard, so I don’t think that’s a big deal.
The first lab of the day involved implementing a controller for a simple data display page in the game-rental web application we started working on in day two. Most of the lab involved setting up the controller configuration; the controller itself was all of three lines of code, literally. Unfortunately some of the other people in the class had a bit of trouble getting Tomcat to run, so the lab stretched on a bit longer than I would have liked.
Afterwards, Keith dove into more detail about Spring MVC, focusing on the tools it provides for handling user input. Especially interesting, though still a bit too verbose in the current 1.2 release, is the support for form validation. The SimpleFormController class and its associated <spring:bind> JSP taglib tag provides a clean framework for displaying a form and interpreting the results, with fully-configurable (and easily localizable) error messages. Keith went over the lifecycle of a SimpleFormController form in minute detail.
At lunch, I talked to him briefly and mentioned that some hands-on experience with Web Flow would be great, and sure enough, after we returned from lunch he had us all install a sample Web Flow project on our machines.
But before Web Flow, we had another lab. This one involved writing a SimpleFormController servlet to do some basic input processing, just enough to demonstrate some basic validation and data binding concepts and the way the controller passes error information to the view. I would have found it easier to digest if the lab notes had had a screenshot of what the form in question was supposed to look like; as I tried to implement the controller I felt like I was kind of flying blind (in real life I at least have some idea of what the UI looks like when I’m working on the web tier of an application!)
The lab did demonstrate a pretty nifty feature of the validation and error handling framework: you can customize the error messages for generic validation failures (e.g. entering non-numeric data into a numeric field) using resource bundles in a really easy way. To use that example, if you have an “account number” field that’s supposed to be numeric, and the user types a non-number into it, the data binding classes will generate a “typeMismatch” error. Your resource bundle can translate that into a human-readable error message in a generic way:
typeMismatch=Sorry, I don’t understand that input.
Or it can get a bit more specific, since a type mismatch is parameterized according to the type that was expected:
typeMismatch.java.lang.Integer=You must input a number.
Or it can get extremely specific, referring to a given field on a given page:
typeMismatch.loginPage.accountNumber=You can only log in with an account number containing the digits 0 through 9.
To avoid repeating yourself, you can provide the same error message for a given field name on any page that doesn’t have a more specific message:
typeMismatch.accountNumber=Account numbers may only contain the digits 0 through 9.
All of that is tweakable without modifying either the code or the view, and the messages are all per-locale so it’s trivial to support multiple languages. Oddly, this feature doesn’t seem to be mentioned too much — I don’t see an example of it in either of my Spring books (Pro Spring and Spring in Action) and I had to spend a while looking through the Spring documentation before I found it buried in the Javadocs for the DefaultMessageCodesResolver class.
One thing that surprised me about SimpleViewController is that it uses the request method (GET vs. POST) to determine whether an invocation is the initial display of a form or a submission, and it will act differently in those two cases. I have done lots of sites where a “display this item” request parameter gets POSTed to a page. I wonder why they chose to do it that way — in my opinion the behavior of the controller should be independent of the request method.
Automatic data binding is another nice feature: if you name your form fields using the same syntax you use to pull values out of your model object, Spring will automatically do data conversion and populate the backing object that represents the contents of the form. Depending on the form, that can just be a domain data object. But if you’re using Hibernate and the “session-per-view” model (where the Hibernate session stays open for the duration of a request, to allow the view to use Hibernate’s lazy loading) then you need to be careful to roll back the transaction if you get a validation error, because otherwise the data binder might have called your object’s setters a few times before running into the error, and Hibernate will end up writing a half-updated object back to the database.
After covering a bunch of other details involving Spring MVC, Keith moved on to Spring Web Flow, which is a much more sophisticated relative of the SimpleFormController and WizardFormController. You can think of it as an XML representation of a traditional flowchart: there are nodes for actions, decision points, and user interaction.
Web Flow is needed because in a traditional Web application, even using Spring MVC, there is no first-class notion of a bunch of pages that have to be accessed in order. A flow, as Keith put it, is something that’s longer than a request but shorter than a session, and in traditional models, there’s no good way to represent it.
Most sites just push the user from URL #1 to URL #2 to URL #3 as the user walks through whatever process he’s navigating. But manual flows are bad for a number of reasons. They usually tie the conversation to particular URLs, which the user can bookmark and hit later without going through the earlier steps. They require the application programmer to repeatedly code “how far along in the process is the user?” logic. Interaction with the browser Back button and “New window with this URL” commands usually cause messy things to happen. Keith had other bad things to say about traditional-style web flows, but those were the most compelling to me.
Spring Web Flow is a state machine that’s completely opaque to the user. When the user hits a flow URL for the first time (with no existing flow identifier passed in) a new FlowExecution state machine starts up. When the state machine hits a “need user input” state, it is stored somewhere (typically as a session variable) along with all the data objects that have been bound to it, and a view is returned to the user. The view includes an opaque key that identifies a particular version of the state machine. When the user supplies whatever input is required (which could just be clicking on an “OK” button to confirm an action) the state machine and its data are restored and the user’s input is evaluated. That continues until an endpoint state is reached, at which point the state machine is destroyed.
Each state can have entry actions as well as actions that get executed when the machine transitions to other states. Transitions are conditional, of course (”if the user clicked the ‘yes’ button, go to state X, otherwise go to state Y.”) Transition conditions can be complex expressions like “if the user clicked ‘yes’ and his account balance is over $50, go to state X. Or the conditions can be evaluated by Java code, for cases where the decision about where to go next is too complex to cleanly handle in the web flow definition.
Keith walked us through putting a sample web flow together. Unfortunately, since Spring Web Flow isn’t normally part of this training course, the PCs in the classroom weren’t set up with all the dependencies we would have needed to deploy the flow locally and play with it; it wasn’t worth spending the time to get everything set up, since we still had one more subject to cover and time was running short.
The final subject of the day was the Spring Security system, also known as Acegi. It provides a rich set of security services with all the pluggability and customizability you’d expect from a Spring component.
In fact, if anything, it’s almost too customizable. There is a heck of a lot of boilerplate configuration that’s required before you can do anything useful with Acegi. Perhaps it was simply because we were running short on time, but I didn’t feel like I left with a very clear understanding of how all the pieces of Acegi fit together. Most of its configuration is still a bit opaque to me — there are clearly a lot of details there but I’m not sure which ones I should care about.
However, it’s not like we got no information at all; Keith went over the conceptual model behind Acegi, and gave some examples 0f how to plug in different behaviors in a couple of places. Acegi has some nice features, no doubt about it: it’s not coupled at all to servlets or HTTP, so you can use it as a general security mechanism no matter what the client is. It can apply access controls at as fine or as coarse a level as you like, down to ACLs on specific instances of data objects.
But I felt like I still didn’t really understand it; so far I think I can go off and apply all the other things we’ve covered, but that’s not the case with Acegi. With the reference manual in hand I could write a Spring MVC or Spring Web Flow application right now, all the way down to the JDBC using Spring’s helpers. But I would pretty much have to cut-and-paste an Acegi configuration and I would not be too confident I’d gotten it right, since it’s so complex that I’m not sure how to go about testing it in any comprehensive way.
Keith recommended taking a look at the Contact sample application that ships with Acegi; it demos all of Acegi’s features and walks you through how they work. I wish there’d been time to do that in class.
I don’t want to sound too critical; for the most part today was packed with good information. It’s just the last hour of the eight-hour day that fell short for me. Tomorrow we will cover some more esoteric topics: Spring’s support for remote object access, aspect-oriented programming, and the JMX management framework. I don’t have a whole lot of experience with any of those, so I’m expecting to get some good new information tomorrow.