RSS Feed
15
Oct.
2003

How I have added IoC to Struts Actions

One of the cool features of WebWork2 is the use of the IoC (Inversion of Control) design pattern. I asked myself wether it would be possible to add IoC to Struts Actions as well and after some meditation I have ended up with a solution which I want to discuss now. The class who is responsible for creating Action instances is RequestProcessor or TilesRequestProcessor if you are using Tiles. If you look at RequestProcessor you will notice the method processActionCreate. As the name suggests this method is responsible for creating the Action instances. Therefore I decided to derive from TilesRequestProcessor and overwrite processActionCreate. Inside the method I use reflection in order to find out wether the action returned by super.processActionCreate(...) implements on of the registered awareable interfaces. In case the action does I call the setter method declared in the interface. The registered awareable interfaces are maintained by a class called ComponentRegistry. This class is designed as a singleton. At last I wrote a Struts plugin that performs the initial registration of the awareable interfaces and their corresponding object instances.

Here's a code snippet from my custom RequestProcessor:
[code] protected Action processActionCreate(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws IOException { Action action = super.processActionCreate(request, response, mapping);

// Look for Awareable classes (IoC) and invoke the setters if present ComponentRegistry registry = ComponentRegistry.getSharedInstance(); Set classes = registry.getRegisteredClasses(); Iterator it = classes.iterator(); while (it.hasNext()) { Class awareableIf = (Class)it.next(); if (awareableIf.isAssignableFrom(action.getClass())) { Object instance = registry.findComponent(awareableIf); log.debug("instance for interface [" + awareableIf + "]: " + instance); if (instance != null) { Method[] methods = awareableIf.getMethods(); if (methods.length != 1) throw new IOException("The awareable interface must exactly contain one method"); try { log.debug("Invoking method: " + methods[0]); methods[0].invoke(action, new Object[]{instance}); } catch(Exception ex) { log.error("Could not invoke setter on awareable interface", ex); throw new IOException("Could not invoke setter on awareable interface"); } } } } return action } [/code]

As you can see currently the setter of the awareable interface will be called on every request because the action caching is handled inside super.processActionCreate(...) and there's no way to find out if an action was created or reused as far as I know. Therefore you might want to cache the name of the classes whose setter you called.

Now that I have IoC I am looking into Interceptors :-) ...

Comments

1. Don Brown
As you are more familiar with WW2 probably than I am, how easy do you think it would be to simply use xwork with Struts? Since Struts actions are singletons, you could create a Struts action that would, in turn, invoke the xwork action handling IoC, intercepting, etc. This way, a Struts app could have all the action features of xwork but within the more familiar Struts framework.

2. WoEyE
This is indeed a good question. I believe, however, that this would not be easy an easy task. Each framework for example features its own configuration management for action handling. Combining the two frameworks into one would mean that one had to maintain two configuration files at least. Furthermore I think that each framework has its own philosophy and comibing them could lead into problems? Personally I think that WebWorks 2 feels more modern regarding the architecture and software design. It its very well designed. Struts on the other hand is well documented, well known and known to be working. But I do not want to start a discussion again for it was discussed on other weblogs many times :-)

3. Don Brown
Yeah, I see what you mean. I took a longer look at the actual configuration of xwork and it duplicates more than I had hoped. What I want to add to Struts from xwork: - ActionContext - IoC - Interceptors I'm fine the way Struts maps actions, although I prefer using wildcards (http://www.twdata.org/struts-wildcard). Forms, validation, and resources are all fine too. I see xwork concepts applying to how the action is invoked, like the IoC hack you did. Perhaps the answer is to simply re-implement much of xwork... I always prefer integration to re-implementation. :/

4. Don Brown
Are you planning on open sourcing any of this code? I'd like to work on it to add these xwork features to struts and not have to duplicate your work. Thanks.

5. WoEyE
Yeah, why not. Indeed I am working on bigger project which will be going OpenSource later this year. But perhaps it makes sense to start a little subproject that contains all those nice Struts extensions?

6. Rick Hightower
This is very cool. I will try this out when you OpenSource it.

7. Robert Watkins
Minor nitpick: wouldn't it make more sense to move this method into the ComponentRegistry itself? So your custom request processor simply passes the newly created action off to the register like so: Action action = ... ComponentRegistry registry = ... registry.registerObject(action); This would move a lot of the logic about registered interfaces into the one spot (e.g. the restriction that one and only one method must be on the awareable interfaces). It would also let you re-use this for forms, etc. Finally: I can see a potential performance bottleneck: you may want to end up determing the set of methods to invoke once on a per action class basis; the introspection is reasonably expensive to do, after all. Neat idea, though. I might steal it. :)