Design an MVC framework using annotations

Annotations can help decouple your application components

When designing an application, clearly separating the different logic components that define it always proves useful, and many different paradigms help developers achieve that goal. One of the most famous and most used is surely Model-View-Controller (MVC), which divides each application (or part of it) into three different fundamental elements and states the rules for linking them together. Swing itself is based on this pattern, and everyone who has worked with Struts, a popular framework for building Web applications, knows the theory behind MVC.

This article shows how to enhance MVC by adding a new component to the game that uses annotations to provide an easier decoupling between models and views. It introduces an open source library, named Stamps, which is based upon the component and removes the burden of writing all the plumbing between models, views, and controllers when developing MVC applications.

The basics: MVC, and annotations

As the name indicates, the Model-View-Controller pattern suggests the division of an application into the following elements:

  • Model: Contains the data model and all information that identifies the state of the application. It is generally self-consistent and independent of the other elements.
  • View: Stands on the other side in respect to the model and defines the representation of the data stored in the model. The view is commonly identified as your application's user interface (or GUI) or, in case of Web applications, the browser Webpage.
  • Controller: Represents the application logic. Here, it is defined how the user can interact with the application and how user actions are mapped to model changes.

These components are tied together: The user affects the view, which, in turn, notifies the controller, which then updates the model. Finally, the model updates the view to reflect its new state. Figure 1 represents the typical MVC setup.

Figure 1. The typical MVC setup

As one of the new features provided with J2SE 5.0, annotations allow the developer to add metadata to classes, methods, fields, and other language components. Just like reflection, any application can then retrieve and use that metadata at runtime for whatever reason. Since J2SE 5.0 defines only how to write and read annotations, but not what to do with them (with the exception of the predefined ones, like @Override), the developer has endless possibilities in using them for many different jobs: documentation, object-relational mapping, code-generation, and so on. Annotations have become quite popular as most frameworks and libraries are being updated to support them. See Resources for more information on MVC and annotations.

Surpassing MVC: The dispatcher

As mentioned previously, some sort of coupling between models and views proves necessary since the latter must reflect the former's state. Common Java programs use direct or indirect coupling to bind the components together. Direct coupling occurs when the view has a direct reference to the model—or vice versa, the model contains a list of the views to be maintained. Indirect coupling is generally achieved with the adoption of an event-based dispatching mechanism. The model fires events whenever its state changes, and an independent number of views register themselves as listeners.

Indirect coupling is generally preferred since it leaves the model completely unaware of the view, which, on the contrary, must know the model in some way to register itself to it. The framework I introduce in this article uses indirect coupling, but, to achieve greater decoupling between components, the view is unaware of the model; that is, the model and view are not bound together.

To achieve this goal, I have defined a new component, the dispatcher, which acts as a separation layer between views and models. It handles registration for both models and views and dispatches events fired by the model to the registered views. It uses java.beans.PropertyChangeEvent objects to represent the events transported from the model to the view; however, the framework design is open enough to support different implementations for the event types.

The burden of managing the list of registered views then shifts from the model, and, since the view deals only with this application-independent dispatcher, the view is unaware of the model. If you are familiar with the Struts internals, you may recognize that the Struts controller performs such a task by relating Actions with their associated JSP (JavaServer Pages) presentation pages.

Our MVC setup now resembles the one depicted in Figure 2. The dispatcher performs a role that is symmetric in respect to the controller.

Figure 2. Modified MVC setup with added dispatcher component

Since the dispatcher must be application-independent, some sort of generic specification that connects models and views must be defined. We will implement such a connection using annotations, which will be used to tag the views and to identify which view is affected by which model and how. In this way, annotations will act like stamps attached to postcards, driving the dispatcher in the task of delivering model events (hence the name of the framework).

Sample application

We will use a simple seconds-counter application as an example for the framework: it allows the user to set the time period to count and to start/stop the timer. Once the specified time has elapsed, the user is asked to cancel or restart the timer. The application's full source code is available at the project homepage.

Figure 3. The sample application

The model is extremely simple, and stores only two properties: period and elapsed seconds. Notice how it uses java.beans.PropertyChangeSupport to fire events when one of its properties changes:

                        public class TimeModel {
   public static final int DEFAULT_PERIOD = 60;
   private Timer timer;
   private boolean running;
   private int period;
   private int seconds;
   private PropertyChangeSupport propSupport;
   /**
    * Getters and setters for model properties.
    */
   /**
    * Returns the number of counted seconds.
    *
    * @return the number of counted seconds.
    */
   public int getSeconds() {
      return seconds;
   }
   /**
    * Sets the number of counted seconds. propSupport is an instance of PropertyChangeSupport
    * used to dispatch model state change events.
    *
    * @param seconds the number of counted seconds.
    */
   public void setSeconds(int seconds) {
      propSupport.firePropertyChange("seconds",this.seconds,seconds);
      this.seconds = seconds;
   }
   /**
    * Sets the period that the timer will count. propSupport is an instance of PropertyChangeSupport
    * used to dispatch model state change events.
    *
    * @param period the period that the timer will count.
    */
   public void setPeriod(Integer period){
      propSupport.firePropertyChange("period",this.period,period);
      this.period = period;
   }
   /**
    * Returns the period that the timer will count.
    *
    * @return the period that the timer will count.
    */
   public int getPeriod() {
      return period;
   }
   /**
    * Decides if the timer must restart, depending on the user answer. This method
    * is invoked by the controller once the view has been notified that the timer has
    * counted all the seconds defined in the period.
    *
    * @param answer the user answer.
    */
   public void questionAnswer(boolean answer){
      if (answer) {
         timer = new Timer();
         timer.schedule(new SecondsTask(this),1000,1000);
         running = true;
      }
   }
   /**
    * Starts/stop the timer. This method is invoked by the controller on user input.
    */
   public void setTimer(){
      if (running) {
         timer.cancel();
         timer.purge();
      }
      else {
         setSeconds(0);
         timer = new Timer();
         timer.schedule(new SecondsTask(this),1000,1000);
      }
      running = !running;
   }
   /**
    * The task that counts the seconds.
    */
   private class SecondsTask extends TimerTask {
      /**
       * We're not interested in the implementation so I omit it.
       */
   }
}
                   

The controller defines the only actions the user is allowed to perform and can be abstracted with the following interface:

                        public interface TimeController {
   /**
    * Action invoked when the user wants to start/stop the timer
    */
   void userStartStopTimer();
   /**
    * Action invoked when the user wants to restart the timer
    */
   void userRestartTimer();
   /**
    * Action invoked when the user wants to modify the timer period
    *
    * @param newPeriod the new period
    */
   void userModifyPeriod(Integer newPeriod);
}
                   

You can draw the view with your preferred GUI editor. For our needs, just a couple of public methods prove sufficient to update the view's fields, as in the following sample:

                        /**
    * Updates the GUI seconds fields
    */
   public void setScnFld(Integer sec){
      // scnFld is a Swing text field
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            scnFld.setText(sec.toString());
         }
      });
   }
                   

Notice that we are using just POJOs (plain-old Java objects), and we do not have to respect any coding conventions or implement specific interfaces (with the exception of the event-firing code). What is left to define is the binding between the components.

Event-dispatching annotations

The core of the binding mechanism resides in the definition of the @ModelDependent annotation:

                        @Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface ModelDependent {
   String modelKey() default "";
   String propertyKey() default "";
   boolean runtimeModel() default false;
   boolean runtimeProperty() default false;
}
                   

This annotation can be applied to the view's methods, and the dispatcher will use the supplied parameters (namely modelKey and propertyKey) to identify the model events this view will respond to. The view uses both the modelKey parameter to specify which of the available models it is interested in and the propertyKey to match the property names of the dispatched java.beans.PropertyChangeEvents.

The view method setScnFld() is therefore tagged with the following information (here, timeModel represents the key used to register the model to the dispatcher):

                        /**
    * Updates the GUI seconds fields
    */
   @ModelDependent(modelKey = "timeModel", propertyKey = "seconds")
   public void setScnFld(final Integer sec){
      // scnFld is a Swing text field
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            scnFld.setText(sec.toString());
         }
      });
   }
                   

Since the dispatcher knows both the model firing the event and the event itself—i.e., it knows the associated modelKey and propertyKey—this is the only information needed to bind views and models together. The model and view do not even need to share communication interfaces or a common codebase.

With our discussed binding mechanism, we can easily change the underlying view without changing anything else. The code below follows the same method implemented using SWT (Standard Widget Toolkit) instead of Swing:

                        @ModelDependent(modelKey = "timeModel", propertyKey = "seconds")
   public void setScnFld(final Integer sec){
      Display.getDefault().asyncExec(new Runnable() {
         public void run() {
            secondsField.setText(sec.toString());
         }
      });
   }
                   

A totally decoupled system has some advantages: The view can adapt more easily to model changes, even if the model is generally stable, while the view changes more frequently. Plus the system can be designed with a GUI editor or another source code generator without merging the generated code with the model-view communication code. Since the model-view binding information is metadata associated with the source code, it is relatively easy to apply it to IDE-generated GUIs or to convert existing applications to this framework. Additionally, having separate codebases allows the view and model to be developed as standalone components, possibly simplifying the application development process. Component testing is also simplified, since each one can be tested separately, and fake models and views can replace the real ones for debugging needs.

However, there are also some disadvantages. Because the compile-time safety available when using interfaces and common classes to bind model and view together is now unavailable, possible typing errors can result in a missed binding between components and runtime errors.

With @ModelDependent's discussed modelKey and propertyKey elements, you can define static relations between models and views. However, real-world applications demonstrate that the view must often be able to dynamically adapt to varying models and application states: think about the different parts of a user interface that can be created and deleted during application lifetime. I will therefore show how the framework handles such situations in relation to other common technologies.

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more