Using Controllers in Induction
This tutorial covers controllers, building on our introduction in the Getting Started Tuturial. Let's start by restating some key aspects of controllers introduced previously:
- a controller must implement Induction's Controller interface
- the Controller interface is a marker interface and as such does not enforce any methods
- a controller must define one or more methods that serve as handler methods. A controller invocation in response to a web request gets targeted to a handler method
- if a handler method declares a formal parameter list, then each parameter in the list receives a value via type-based dependency injection. The table below shows the types available for dependency injection:
Type | Description |
Request | facade to the servlet request, extends javax.servlet.http.HttpServletRequest |
Response | facade to the servlet response, extends javax.servlet.http.HttpServletResponse |
Form | provides access to the HTML form (if any) submitted with this request |
<model_class_name> | instance of a developer provided model class, the instance is managed to conform to a user specified lifecycle (also referred to as scope) |
ControllerResolver.Resolution | provides access to the controller resolution object (useful for writing generic parameterized controllers) |
Shown below is a sample controller that illustrates some of the above features.
/**
* An example of a multi-action controller
*/
public class UserProfileController implements Controller
{
/**
* Action to open a user profile
*/
public void open( UserProfileApp userProfileApp, Form form ) throws IOException
{
// some sample code follows ...
userProfileApp.open( form.getString( "userId" ) );
...
}
/**
* Action to save a user profile
*/
public void save( UserProfileApp userProfileApp, Form form ) throws IOException
{
// some sample code follows ...
userProfileApp.setFirstName( form.getString( "firstName" ) );
userProfileApp.setLastName( form.getString( "lastName" ) );
...
userProfileApp.save( );
...
}
/**
* Action to close a user profile
*/
public void close( UserProfileApp userProfileApp ) throws IOException
{
// some sample code follows ...
userProfileApp.close( );
...
}
}
In the example above, first note that the handler methods (open, save and close) have names meaningful to our application (and not the framework!). In the above example UserProfileApp is an example of a developer provided model class, the Form is an Induction provided model class. Values for both the userProfileApp and form are injected by Induction.
Lifecycle Management
For a given controller class, Induction maintains no more than one instance of the controller class (we say "no more" since there would be zero instances for a controller to which no calls have been made).
Since a single controller instance is used to handle requests, the handler methods of a controller are expected to be thread-safe (this is similar to the service() method of the javax.servlet.Servlet interface). Next let's take a closer look at how Induction instantiates a controller.
Instantiation
To illustrate our discussion we will describe the lifecycle semantics in the context of a controller class named CartController. When Induction receives a request directed to CartController it first checks in the controller pool if there is already an instance of CartController.
If this is the first request to the CartController then there will be no CartController in the pool. Induction creates an instance of our CartController class as follows:
- Load the CartController class using the configured class path (using the reloading classloader if so configured).
- Check to see that the CartController class implements the com.acciente.induction.controller.Controller interface (com.acciente.induction.controller.Controller is a marker interface, and its purpose is security).
- Check that the CartController class has exactly one public constructor (it does not have to be a no-arg constructor). If there is more than one public constructor an error is returned, otherwise processing continues.
- Create the CartController class using the single public constructor. The constructor is called with automatic type based dependency injection as follows:
- If the CartController constructor has a formal parameter of type javax.servlet.ServletConfig, Induction will inject the servlet config instance (retrieved from the Servlet container) into the formal parameter.
- For parameters of type other than javax.servlet.ServletConfig Induction will inject the null value.
- Check to see if CartController defines a public method named init. An init method is completely optional. If an init method exists, the method is called with same parameter injection semantics described for the constructor in the previous step.
- We now have an instance of CartController class, this instance is placed in the controller cache.
After instantiation, the controller is used to handle the web request. Next we look at how Induction reloads controllers.
Reloading
On every call to the CartController class, if the CartController class was initially loaded using Induction's reloading classloader, a check is made to see if the CartController's underlying class file has changed. If the reloading classloader was not used to load the CartController class this check gets bypassed.
If the reloading classloader reloads CartController class as a result of the underlying class file changing:
- The CartController instance in the pool is dellallocated and removed from the pool.
- A new instance of the CartController is created 1 using the newly reloaded class and added to the controller pool.
Next lets take a look at the deallocation semantics.
Deallocation
When it comes time to deallocate an instance of our CartController controller, Induction checks if the CartController class has a public no-arg method named destroy. A destroy method is completely optional. If a destroy() method is defined, Induction calls the method on the controller instance after removing the controller from the pool.
Conclusion
This tutorial described how Induction instantiates and deallocates controllers. It also introduced how you may optionally provide init and/or destroy methods to contain any initialization and tear down code 2.
__
1. Ofcourse the new controller instance is created using the same steps we discussed for creating the first controller instance.
2. It turns out that the object lifecycle semantics discussed in this tutorial are not unique to controllers. In fact it is very close to a discussion of the semantics used by Induction to instantiate several user-provided classes used as configuration plug-ins. These will be covered in a future tutorial. In general, the parameter values injected by Induction into the public constructor (and the optional init method) depend on the context in which the object is instantiated.
For example, in the case of controllers only a value for the javax.servlet.ServletConfig class is injected. But for a templating engine plug-in many more values are injected (for now these are documented in the javadoc documentation).