Learning to Loathe XML Configuration

I’ve been using JavaServer Faces on a recent project. I’ve been enjoying it, especially because I feel like JSF doesn’t have the same learning cliff that Tapestry has. I also enjoy the fact that JSF has a huge amount of customizability — I can swap out various functions of the Faces implementation to change the way certain features work.

What I’m not enjoying as much is the XML configuration. Not unlike Struts, JSF uses a massive faces-config.xml file to specify all my backing beans, my navigation rules, etc. Me, I can’t stand it. Too much XML.

Here’s an example. Suppose I want a backing bean for login. I might do something like this:

public class LoginController {

  private String user;
  private String password;

  private AuthenticationService authenticationService;

  public String getUser() {
    return this.user;
  }
  public void setUser(String user) {
    this.user = user;
  }
  public String login() {
    User user = this.authenticationService.authenticate(this.user, this.password);
    if (user != null) {
      SecurityContext.setUser(user);
      return "success";
    } else {
      FacesContext.getCurrentInstance().addMessage(null,
        new FacesMessage( FacesMessage.SEVERITY_ERROR, "Go Away!", "Go Away!"));
      return "failure";
    }
  }

...

My backing bean is considered to be a “managed bean” in Faces-speak, and I tell Faces about it by putting a few lines of XML into my faces-config.xml file:

<managed-bean>
  <description>The "backing file" bean that backs up login</description>
  <managed-bean-name>LoginController</managed-bean-name>
  <managed-bean-class>ca.intelliware.example.controller.LoginController</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

This tells JSF that a new instance of my LoginController should be instantiated for every request.

Now, the AuthenticationService is a service in my application, and I hafta figure out how to inject that as a dependency in my backing bean/controller. Since I generally like using Spring, I turn my attention there. How do I inject something from Spring into my backing bean?

If you do a web search, you can find posts like this one that suggest configuring a special Spring “Variable Resolver”. It works like this. I extend my faces-config.xml file:

<managed-bean>
  <description>The "backing file" bean that backs up login</description>
  <managed-bean-name>LoginController</managed-bean-name>
  <managed-bean-class>ca.intelliware.example.controller.LoginController</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <managed-property>
     <property-name>authenticationService</property-name>
     <value>#{AuthenticationService}</value>
  </managed-property>
</managed-bean>

And earlier in the file, I tell Faces that when it’s trying to resolve variables (like, say, #{AuthenticationService}), it should talk to Spring. That looks like this:

<application>
  ...
  <variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
</application>

Now, JSF talks to Spring, and my dependency gets set. But I hate it. It’s too much XML and it’s too many files I need to fiddle with. I’ve been having too much fun with Java annotations to really be happy with that solution. Tapestry’s approach is so much nicer: just annotate the dependency, and it gets resolved. No fuss.

So I wondered how to accomplish something like that in JSF. Well, it’s pretty easy. Faces allows me to override things, so I created a subclass of the MyFaces ApplicationImpl class that changes the way managed beans are resolved. There’s a line in my code that does this:

elResolver.add(new SpringDependencyInjector(new ManagedBeanResolver(), getWebApplicationContext()));

I wrap the standard ManagedBeanResolver, and do a bit of custom work. In this new environment, I annotate my LoginController as follows:

@Spring
public void setAuthenticationService(AuthenticationService authenticationService) {
  this.authenticationService = authenticationService;
}

(I use a pretty trivial naming standard for things in my applicationContext.xml file, so I generally don’t bother providing a bean name).

Then, my SpringDependencyInjector resolves all the annotations before passing the managed bean back to the Faces framework. Now, I do much less fussing with XML files.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply