A long time ago, before Spring, and even before Struts and Webwork, I worked with a home-grown proprietary web development framework which was based on the Chain of Responsibility pattern. What it did was allow the developer to write small modules and chain them together to handle the request. When the Controller encountered the first module, it would instantiate a Context object and stick it into the Request. Each module would interact with the Context, sometimes adding fresh data and sometimes using data that modules earlier in the chain had written into it. The framework had its warts, but it was very easy to add new functionality to web pages - simply stick a new module into the chain that generates the data you need and add the presentation for the data on the view JSP.
I have been working with Spring for about 2 projects now, and while I think its MVC implementation is by far simpler and more extensible than Struts or Webwork (the other two frameworks I know), I did not find the functionality I described above, so I set about writing one. This article describes the main features of the ChainedController, includes the code, and explains how to set up some typical chaining configurations.
I originally thought that I could use the Apache Commons Chains project to implement the additional functionality, but a quick look through its feature set indicated that it would not work for me. Specifically, Apache Commons Chains has as its unit of chaining a Task object, which would be incompatible with my unit of chaining, a Controller object. Secondly, I wanted to be able to chain tasks to execute in parallel as well as sequentially, while Chains provide for sequential chains only.
My ChainedController implements the Spring Controller interface. The Controller itself takes as a parameter a List of other Controllers it should chain. The second parameter it takes is the chaining policy - can be either parallel or sequential (the default if not specified). The third parameter is the view name the Chained controller should forward to.
The code for the ChainedController is here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | package org.springchains.framework.controllers;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/**
* ChainsController uses the Chain of Responsibility Pattern to delegate
* control to a chain of Controller objects that are passed into it at
* configuration time. A ChainsController also takes an additional
* policy setting that decides whether it should delegate to the
* Controller chain in a serial or parallel manner. A ChainsController
* is itself a Controller, which means that a ChainsController can be
* passed in as a parameter to another ChainsController. This allows for
* very fine-grained control over the entire controller chain.
* @author Sujit Pal
* @version $Revision$
*/
public class ChainedController implements Controller {
private List<Controller> controllers;
private boolean parallel = false;
private String viewName;
private class CallableController implements Callable<ModelAndView> {
private Controller controller;
private HttpServletRequest request;
private HttpServletResponse response;
public CallableController(Controller controller, HttpServletRequest request, HttpServletResponse response) {
this.controller = controller;
}
public ModelAndView call() throws Exception {
return controller.handleRequest(request, response);
}
};
/**
* Set a List of Controller objects that must be chained.
* @param controllers a List of Controllers.
*/
public void setControllerChain(List<Controller> controllers) {
this.controllers = controllers;
}
/**
* Set the policy using which the Controller objects must be invoked.
* By default, parallel is set to false, which means that the
* chain of Controllers will be called serially. If set to true, the
* ChainedController will spawn a thread for each of the Controllers in
* the chain, and execute all threads simultaneously. In either case,
* an exception thrown by one of the Controllers in the chain will
* result in an exception thrown by the ChainsController itself.
* @param parallel if true, serial if false.
*/
public void setParallel(boolean parallel) {
this.parallel = parallel;
}
/**
* Allows declarative setting of the final view name. This may also
* be programatically defined by subclassing the ChainedController.
* @param viewName the name of the view the controller will forward to.
*/
public void setViewName(String viewName) {
this.viewName = viewName;
}
/**
* Handles the request and builds a ModelAndView object to forward to the
* view layer. Delegates to the private handleRequestXXX() methods based
* on whether parallel is set to true or false.
* @param request the HttpServletRequest object.
* @param response the HttpServletResponse object.
* @return a ModelAndView object.
* @exception if one is thrown by the controllers in the chain.
*/
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
return (parallel ? handleRequestParallely(request, response) : handleRequestSequentially(request, response));
}
/**
* Spawns multiple threads, one for each controller in the list of
* controllers, and within each thread, delegates to the controller's
* handleRequest() method. Once all the threads are complete, the
* ModelAndView objects returned from each of the handleRequest()
* methods are merged into a single view. The view name for the model
* is set to the specified view name. If an exception is thrown by
* any of the controllers in the chain, this exception is propagated
* up from the handleRequest() method of the ChainedController.
* @param request the HttpServletRequest object.
* @param response the HttpServletResponse object.
* @return a merged ModelAndView object.
* @throws Exception if one is thrown from the controllers in the chain.
*/
@SuppressWarnings("unchecked")
private ModelAndView handleRequestParallely(HttpServletRequest request, HttpServletResponse response) throws Exception {
ExecutorService service = Executors.newCachedThreadPool();
int numberOfControllers = controllers.size();
CallableController[] callables = new CallableController[numberOfControllers];
Future<ModelAndView>[] futures = new Future[numberOfControllers];
for (int i = 0; i < numberOfControllers; i++) {
callables[i] = new CallableController(controllers.get(i), request, response);
futures[i] = service.submit(callables[i]);
}
ModelAndView mergedModel = new ModelAndView();
int i = 0;
for (Future<ModelAndView> future : futures) {
try {
ModelAndView model = future.get();
if (model != null) {
mergedModel.addAllObjects(model.getModel());
}
i++;
} catch (ExecutionException e) {
throw new Exception(
"Controller: " + controllers.get(i).getClass().getName() +
" threw exception: " + e.getMessage(), e);
}
}
if (StringUtils.isNotEmpty(this.viewName)) {
mergedModel.setViewName(this.viewName);
}
return mergedModel;
}
/**
* Calls the handleRequest controller for each of the Controllers in the
* chain sequentially, merging the ModelAndView objects returned after each
* call and returning the merged ModelAndView object. An exception thrown
* by any of the controllers in the chain will propagate upwards through
* the handleRequest() method of the ChainedController. The ChainedController
* itself does not support any communication between the controllers in the
* chain, but this can be effected by the controllers posting to a common
* accessible object such as the ApplicationContext. Note that this will
* introduce coupling between the controllers and will be difficult to
* arrange into a parallel chain. A controller can stop processing of the
* chain by returning a null ModelAndView object.
* @param request the HttpServletRequest object.
* @param response the HttpServletResponse object.
* @return the merged ModelAndView object for all the controllers.
* @throws Exception if one is thrown by one of the controllers in the chain.
*/
public ModelAndView handleRequestSequentially(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mergedModel = new ModelAndView();
for (Controller controller : controllers) {
try {
ModelAndView model = controller.handleRequest(request, response);
if (model == null) {
// chain will stop if a controller returns a null
// ModelAndView object.
break;
}
mergedModel.addAllObjects(model.getModel());
} catch (Exception e) {
throw new Exception(
"Controller: " + controller.getClass().getName() +
" threw exception: " + e.getMessage(), e);
}
}
if (StringUtils.isNotEmpty(this.viewName)) {
mergedModel.setViewName(this.viewName);
}
return mergedModel;
}
}
|
The chaining policy and the magic of Spring declarative configuration allows the ChainedController to be chained in a variety of interesting ways. To test my implementation, I set up a web application to display a page containing 5 tiles - the Header, Footer, Left and Right sidebars and the Main Body. The examples show the ChainedController configuration to implement a sequential, parallel and hybrid chain.
A sequential configuration looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <bean id="sequentialChainedController" class="org.springchains.framework.controllers.ChainedController">
<property name="controllerChain">
<list>
<ref bean="headerController" />
<ref bean="leftSidebarController" />
<ref bean="rightSidebarController" />
<ref bean="mainBodyController" />
<ref bean="footerController" />
</list>
</property>
<property name="parallel" value="false" />
<property name="viewName" value="main" />
</bean>
|
A parallel configuration with the same set of controllers would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <bean id="parallelChainedController" class="org.springchains.framework.controllers.ChainedController">
<property name="controllerChain">
<list>
<ref bean="headerController" />
<ref bean="leftSidebarController" />
<ref bean="rightSidebarController" />
<ref bean="mainBodyController" />
<ref bean="footerController" />
</list>
</property>
<property name="parallel" value="true" />
<property name="viewName" value="main" />
</bean>
|
Spring also allows you to refer to other bean collections using a reference. Using this feature, one can set up arbitarily complex chains, which are hybrids of sequential and parallel chains. Obviously, you would want to think about these chains when you build them, to make sure they make sense. Here is an example, based on the assumption that the mainBody component will take about the same time to generate and render compared to all the other components put together:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <bean id="nonMainBodyChainedController" class="org.springchains.framework.controllers.ChainedController">
<property name="controllerChain">
<list>
<ref bean="headerController" />
<ref bean="leftSidebarController" />
<ref bean="rightSidebarController" />
<ref bean="footerController" />
</list>
</property>
<property name="parallel" value="false" />
<property name="viewName" value="main" />
</bean>
<bean id="hybridChainedController" class="org.springchains.framework.controllers.ChainedController">
<property name="controllerChain">
<list>
<ref bean="nonMainBodyChainedController" />
<ref bean="mainBodyController" />
</list>
</property>
<property name="parallel" value="true" />
<property name="viewName" value="main" />
</bean>
|
Sometimes it is necessary to decide, based on the contents of the model, which view to forward to. The default ChainedController would not help in this case. You may have to subclass the ChainedController, and make the decision in the handleRequest method, something like this:
1 2 3 4 5 6 7 8 9 10 11 | public ModelAndView handleRequest(
HttpServletRequest request, HttpServletResponse response)
throws Exception {
ModelAndView mav = super.handleRequest(request, response);
if (mav.getModel().get("somedata") != null) {
mav.setViewName("view1");
} else {
mav.setViewName("view2");
}
return mav;
}
|
Why would one use chaining? Well, the answer depends on what you are using to achieve the same functionality currently. If you are using interceptors to put logic before and after your controller logic, then I think that being able to chain your controllers is going to have better performance, since interceptors use AOP, and AOP is quite a performance hog. If you write all your code in a monolithic controller, depending on a service layer to delegate the logic within your controller for each page component, you would still need to add or remove code when components are added or removed from your page. With chained controllers, adding or removing a component involves only configuration changes.
Another advantage to using chaining, especially if you have a Tile or Tile-like setup on your front end, where a page is broken up into distinct components, is the componentization it allows. Tiles already componentizes the page, but chaining allows you to link a single controller with a tile. This controller + tile combo can be dropped into another page without having to write a line of Java code.
Some caveats, though. Unlike the framework from which the ChainedController is inspired by, there is no built-in way for controllers in a chain to communicate via a Context object. For parallel chains, it would not make much sense anyway, since by definition, controllers chained parallely cannot depend on each other. For sequential chains, however, it should be fairly easy to establish a ThreadLocal reference to a specific bean in the ApplicationContext.
Another difference is that the original framework did not allow for parallel chaining. I thought that it may be useful for generating extremely content heavy or portal style pages, where a bunch of functionally unrelated components are thrown together on a single page because it makes sense to have them on the same page from a user's point of view. Generation of these components could happen in parallel, since they dont depend on each other functionally, and be presented to the user quicker. Of course, there is a price to pay - each request generates multiple threads, equal to the number of controllers in the parallel chain, instead of a single thread per request. Of course, the threads will terminate faster (or so we hope). I haven't tried the ChainedController implementation under load, so I do not know how this would scale to heavy traffic.
Finally, I am a little surprised that no one has done this before. It is possible that people have thought of it, and think its a bad idea. If you think this is a bad idea, please do let me know.
During this exercise, I took the opportunity to learn JSTL by doing all my JSPs using JSTL. Here are some links I used for this, hopefully they will be useful to others trying to learn JSTL as well.
10 comments (moderated to prevent spam):
Hi,
I really like this approach. There's one thing that is not quite clear to me: if you chain up multiple controllers, which one gets to select a view? Or is there more than one view involved?
Quirijn
The ChainedController has a settable view name which it will forward to. However, looking back, the idea seems kind of pointless. It is just as easy, probably easier, to implement a chain of services that is run from within a single controller rather than chaining controller objects using this approach.
In my defense, I can only say that at the time I wrote it, I was thinking quite heavily about chains, and the idea seemed good at the time :-).
The reason why I like it, is that it offers a way to combine a Spring web application with a content management system. I have my CMS publish XML definitions, that contain a list of controllers. The ChainedController reads this list and instantiates all the required subcontrollers. For each subcontroller, the handleRequest method is called. Each subcontroller is a normal Spring controller in its own right, complete with model and view.
The total result (including output of the subviews) is passed to a main page view.
The main difference with your idea is that the configuration of controls on a given page comes from outside the Spring application, not from the ApplicationContext.
The beauty of this approach is that it allows java developers to do their thing the way they always do, while web authors continue to use their CMS the way they are accustomed to.
This is quite a cool idea. I guess chaining controllers can be useful when each of the components are capable of rendering pages on their own, as well as be part of another page, like in your case.
I discovered this years later and it helped me approach SpringMVC (despite the fact that it lacked forwarding).
I don't believe that you would want to subclass your chained-controller in order to determine which view to use. I would suggest that you employ the strategy pattern and inject into the chained-controller an instance of a class which realizes interface ViewNameProvider which provides a single method "public String getViewName()".
I wanted to elaborate on the idea of a context which allows each of the controllers to pass reasources to any subsequent controller. I was hoping to come up with some type safe mechanism, but I couldn't. The context will likely stay close to a Map<String, Object>. This is unfortunate, as downcasting is bad.
Anyway, thank you for this little time-capsule truffle. Have a good one.
Sincerely,
Jon Savell
Thanks for the comment, Jon, and yes, your idea of injecting a ViewNameProvider makes lots of sense. Since I wrote this article, I have come to believe that a better approach to chaining controllers idea is to inject and chain multiple services in a single controller instead, and having a controller manage the services in the manner desired, and using a strategy pattern fits very nicely into this approach...ie the controller would know what to do with the services injected into it.
Sujit,
I respect you deeply, but I would like to try to convince you that chaining controllers is the better path.
I would like to tell you my first experience with Spring MVC and see if I can sway you. I worked at a company which had built some infrastructure on top of Spring MVC. The person who architected this is now at Google and he is very smart, but I believe that he accepted the lack of forwarding in Spring MVC and allowed this to dictate his architecture; I also believe that this was a mistake. The result was a collection of HUGE controllers. By HUGE, I mean that the handleXXX methods were very large. Each controller had to execute methods on a LOT of services because there were many distinct pieces in the page and each piece required a decent amount of interaction with a particular set of services. The sheer size of the controllers made me realize that something was wrong.
My belief is that each controller ought to be focussed only on a distinct piece of a page. This is consistent with both the technique of forwarding and the Tiles2 library. Your technique is the only way I can make Spring MVC do what I want to do: make each controller serve at most one tile and participate in a composite assembly.
Some tiles are very simpe and don't require any activity. On the other hand, some tiles represent complex fragments; these are the tiles which merit a dedicated controller.
The aggregate is large and the pieces are small. That is, there are many more controllers in a web application, but each controller is smaller and easier to maintain. A byproduct of this is that deployment is hard, but development is easy.
So, what do you say? Are you going to revisit your position?
Sincerely,
Jon Savell
Hi Jon, I think it depends...I am not married to either idea...I would choose one or the other depending on what I was trying to do...
To provide some background, I got this idea of chaining controllers from a homegrown system which had exactly the same use-case as you mention - lots of little tiles with variable complexity of processing for each.
I began to think about chaining services when I built our RSS based API application -- here lots of little services join together to produce a full feed, but they need to be rendered at one place.
What I did here was something you suggested in your last post - implement strategy. All the controllers do is to implement security and logging wrappers, and it delegates to a List of services that are built into the Spring configuration. So in my case the controllers are actually quite small - and since services don't depend on the request context, I can unit test them quite easily.
In your case, since you depend on tiles, all you can do inside a service layer (ie, without access to the request context) is to build the backing bean for a tile, which would then have to be rendered inside the controller, which will make the controller larger. So perhaps in your use-case chaining controllers is a good idea.
Any updates on this in Spring 3.
I am looking at writing a MasterController that can take care of invoking multiple controller and merge the data coming back , return the response.
On the request , client send widgetId , based on this id , i know what r the other controller i need to invoke locally. Other wise client needs to make multiple http requests to each of these controllers.
i m using MockHttpServletRequest class to set uri path and invoke the other controllers.
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI(uri[i]);
request.setCookies(req.getCookies());
model = handlerAdapter.handle(request, response, controller);
Is there a better way of doing this instead of using mock request object.
Hi, no I haven't bothered to update on Spring3, because with the benefit of hindsight, the idea appears to be somewhat dumb, unless required for legacy reasons. Its far better to have a single controller extract the relevant parameters from the request and then invoke a chain of services instead. The benefits are that each service is testable in a non-web environment and you no longer have to bother about having to forward it. Since this post, I have built a few systems with the chain pattern, but in all cases its been a chain of services.
Post a Comment