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.

Dynamic MVC binding

A problem for frameworks that depend on XML binding (or some other sort of declarative binding based upon configuration files) is static binding rules. Dynamic variations are not possible, thus the developer generally resolves to use redundant binding information coupled with some decision algorithm that uses the correct bind each time.

To circumvent this problem, the Stamps framework provides two ways to change the binding at runtime. First, views and models can be registered and deregistered with the dispatcher in the same way event listeners are associated with GUI widgets. This allows specific views to be notified only when they are needed. For example, a monitoring console associated with an application, which can be bound to the object it is monitoring, is used only when the user requests it.

Second, the @ModelDependent annotation provides two elements, runtimeModel() and runtimeProperty(), which specify that the binding with a specific model and its dispatched events will be decided at runtime. If one of the two settings is true, then the respective key (modelKey or propertyKey) will be the method invoked on the view to obtain the value to be used. An example: A view responsible for showing the contents of one of a set of news channels (each news channel is a model). The view then depends on the user input to identify the channel to bind to.

An example of such a situation follows:

                        // This method is invoked to display all the messages of one news channel
   @ModelDependent(modelKey = "dynamicChannel", propertyKey = "allmessages" , runtimeModel = true)
   public void setAllMessages(java.util.List<String> messages) {
      // Updates the user interface
   }
   public String getDynamicChannel() {
      // Returns the channel requested by the user
   }
                   

Additional annotations

Since the world is not perfect, additional annotations have been defined to help deal with real-world cases. @Namespace allows the developer to subdivide the model domain into different parts for better manageability of the whole. Since a single dispatcher can handle multiple models, conflicts in model keys may happen. Therefore, it proves useful to isolate groups of models and related views into different domains belonging to a single namespace, so that they will not interfere with each other.

The @Transform annotation allows on-the-fly object transformations from the objects contained in the model events to those accepted by the receiving views. Thus, the framework can be adapted to existing code without modification. The annotation accepts a single parameter that is resolved in a registry of available transformations (defined as implementations of a specific interface).

The @Refreshable annotation allows model properties to be tagged to support the discussed dynamic joining and leaving of views. Using this annotation, the framework is able to handle both static and dynamic MVC layouts, with different views bound to the model at different times.

To understand @Refreshable's usage, we must go back to the monitoring console example. This console (which, in MVC terms, is a view) would be dynamically bound and unbound to the model depending on user needs. The @Refreshable annotation can be used to keep the console informed about the model state when connected to it. When a view connects to the framework, it must be updated on the current model state. Therefore, the dispatcher scans the model for @Refreshable annotations and generates the same events the view would commonly receive from the model itself. Such events are then dispatched using the binding mechanism discussed earlier.

Distributed MVC networks

The dispatcher has a great burden on its shoulders and is responsible for all heavy-duty message-passing involved in the event-propagation cycle:

  • The model causes an event, which specifies some sort of change it has undergone. The dispatcher(s) handling that model is notified.
  • The dispatcher scans all its registered views, looking for @ModelDependent annotations, which specify the changes the view wants notification of, and the methods to be invoked on the view for every model event that has occurred.
  • If needed, transformations are applied to the event data.
  • The view methods are invoked with the parameters extracted from the raised event, and the view updates itself.

On the other side, when a new view registers to the dispatcher:

  • The view tells the dispatcher about the modelKey, which identifies the model it will be attached to (whose events will be responsible for populating the view).
  • If needed, the dispatcher scans the model looking for @Refreshable annotations and uses them to produce fake model events that will bring the view up to date.
  • These events are dispatched using the above sequence, and the view is updated.

All this work involves neither the view nor the model; they both stand at their respective ends of the communication line. It doesn't matter whether the messages are transmitted within a local JVM or between multiple JVMs located on remote hosts. Simply changing the logic within the dispatcher is all that is needed to transform local applications into client-server ones, leaving both the model and the view code unaffected. A sample setup is shown in the following image.

Figure 4. A MVC setup on a distributed network. Click on thumbnail to view full-sized image.

In the above image, the single dispatcher has been replaced with one transmitter (an instance of it.battlehorse.stamps.impl.BroadcastDispatcher), which stays on the same host as the model, and one (or more) receiver, (it.battlehorse.stamps.impl.FunnelDispatcher), located on the same host as the view. The Stamps framework's default implementation uses a messaging layer built upon JGroups, a toolkit for reliable multicast communication that works as a network transport mechanism (but different ones may be implemented and used), obtaining a reliable, multiprotocol, and failure-aware communication.

With a single change in the initial setup of our application (the dispatcher), we have moved from a single-user standalone application into a multiple-user distributed one. The framework can notify numerous listener interfaces when the model joins or leaves the network (think of a communication failure), so that remote views can take the appropriate responses—for example, showing a warning message to the user. The framework also offers utility methods that help to convert local controllers into remote ones.

Conclusions and summary

Many elements remain for exploration, such as the way to design controllers that are as equally generic as the dispatchers—at the moment, the framework assumes the common controller-model coupling, since the former needs to know how to drive the latter. Future developments are directed toward supporting different types of views, such as using a browser, network-aware applets, and Java-to-JavaScript communication

The discussed Stamps library shows how to achieve loose coupling between views and models in an MVC layout and how this framework can leverage Java annotations to separate the binding information from the actual application components. Having isolated the binding logic allows you to physically separate the components and provides both a local and a client-server setup without modifying the application logic or presentation layer. These objectives provide insight into the possibilities offered by combining a solid design pattern like MVC with the powerful metadata offered by annotations.

Riccardo Govonihas been working since 2003 as a J2EE developer for a financial services company in the northern part of Italy. There, he develops Web front ends for legacy banking systems, database management, and heavy-duty data processing tasks. Govoni has strong experience in J2EE multitier applications and detailed knowledge of Java GUI libraries such as Swing and SWT. He has a master's degree in physics. Govoni spends his spare time Googling around, looking for the latest Java news, or discussing new project ideas with friends and (sometimes) his girlfriend's dog.

Learn more about this topic

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