Designing an MVC framework using annotations

Source: Internet
Author: User
Tags define bind event listener final implement integer new set tostring
Designing a clear separation of the different logical components of the program when designing an application is always proven to be beneficial. There are also a number of different patterns to help developers achieve this goal. One of the most famous, and most commonly used, Model-view-controller (MVC) is the ability to divide each application (or part of an application) into three different functional components and define the rules that bind them together. Swing itself is based on this pattern, and everyone who uses struts, the popular development Web application framework, understands the theory behind MVC.

This article describes how to enhance MVC by adding a new component by using annotation to make it easier to remove the coupling between models and views. This article introduces an open source library called stamps, which is based on the MVC component, but it goes beyond all the burden of establishing relationships between models, views, and controllers that are needed to develop MVC.

basic knowledge: MVC and annotations

As the name of MVC points out, the Model-view-controller model suggests dividing an application into the following three components:
· Model: Contains data models and all the information used to determine the state of the application. It is generally organized and independent of other components.
· View: From a different model point of view, it defines how data is stored in models. It is often thought of as the user interface (or GUI) of your application, or as an example of a Web application, the scene is the page you see through the browser.
· Controller: It represents the logical part of the application. Here, it defines how a user interacts with an application and also defines how user behavior is mapped to model changes.

These components are tightly linked: Users affect view, and in turn view notifies controller to update model. The final model updates the view to reflect its new state. This typical MVC structure is shown in Figure 1.


Figure 1. A typical MVC structure

As a new feature provided by J2SE 5.0, annotations allows developers to add metadata to classes,methods,fields and other program elements. Just like the reflection mechanism, many applications later get and use those metadata for some reason at run time. Since J2SE 5 only defines how to write and read annotations, and there is no indication of where to use them (as in the case of an earlier-defined exception like @override), developers have an infinite number of possibilities to use them on many different occasions: Document authoring, object-related mappings, Code generation, etc... Annotations has become so popular that most frameworks and libraries update themselves to support them. For more information on MVC and annotations, see Resources.

Beyond Mvc:dispatcher

As mentioned earlier, some coupling between models and views is necessary because the latter must reflect the state of the former. Ordinary Java programs bind components together using direct or indirect coupling. Direct coupling occurs when there is a direct correlation between view and model, and model contains a list of views that need to be maintained. Indirect coupling usually occurs in a mechanism based on event allocation. Model fires events when its state changes, while some independent views register themselves as event listeners.

Usually we prefer indirect coupling because it makes the model completely unaware of the existence of the view, the opposite view must maintain a certain relationship with model to register itself into model. The framework I will introduce in this article is the use of indirect coupling, but in order to better reduce the coupling between components, view must not know the existence of model, that is, model and view are not bound together.

To achieve this goal, I have defined a new component, the dispatcher, which can be used as a separate layer that exists between views and models. It can handle the registration between the models and the views and assign the events fired by model to the registered views. It uses the Java.beans.PropertyChangeEvent object to represent events that are transferred from model to view, but the design of the framework is open enough to support the implementation of different event types.

The burden of managing the registered views list is then removed from model, and because view is only relevant to the application-independent dispatcher, view does not know the existence of model. If you are familiar with struts, you may be able to see that the controller of struts is fulfilling a task that associates actions with their associated JSP (JavaServer pages) performance pages.

Now, the MVC framework we are designing is as described in Figure 2. Dispatcher in which he played a role commensurate with controller.


Figure 2. An improved MVC framework with additional dispatcher components

Since dispatcher must be independent of the application, you must define some common models and views specifications. We will use annotations to implement this connection, which will be used to annotate views and determine which view is affected by which model, and what the impact is. In this way, annotations is like a postage stamp on a postcard that drives dispatcher to perform the task of passing the model event (which is the origin of the framework name).


Application Examples

We will use a simple timer application to do an application instance of the framework: it allows the user to set a time period to count and start/Stop the timers. Once the specified time has elapsed, the user will be asked whether to cancel or reboot the timer. The full source code for this application can be found on the project home page.


Figure 3. A simple application

This modle is very simple, and it stores only two properties: the cycle and the number of seconds that have elapsed. Note how it uses Java.beans.PropertyChangeSuppor to fire events when one of its properties is changed.

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 timer would count. Propsupport is an instance of Propertychangesupport
* Used to dispatch model state change events.
*
* @param period the period that timer would count.
*/
public void Setperiod (Integer period) {
Propsupport.firepropertychange ("period", this.period,period);
This.period = period;
}

/**
* Returns the period that timer would count.
*
* @return The period that timer would 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 this timer has
* Counted all of the seconds defined in the period.
*
* @param answer the user answer.
*/
public void QuestionAnswer (Boolean answer) {
if (answer) {
Timer = new timer ();
Timer.schedule (The new Secondstask (this), 1000,1000);
running = true;
}
}

/**
* Starts/stop the timer. This are invoked by the controller on user input.
*/
public void SetTimer () {
if (running) {
Timer.cancel ();
Timer.purge ();
}
else {
Setseconds (0);
Timer = new timer ();
Timer.schedule (The 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.
*/

}
}



Controller defines only actions that a user can perform and that can be abstracted from the following interfaces.

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 use your favorite GUI editor to draw this view. In our own case, we only need a few public methods to provide enough functionality to update the view's fields, as shown in the following example:

/**
* 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 ());
}
});
}



Here we note that we are using POJOs (Plain-old Java objects), and we do not have to follow any coding conventions or implement specific interfaces (except for event firing code). The only thing left is to define the bindings between the components.

Event Dispatch annotations

The core of the binding mechanism is the definition of @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 used on the methods of view, and dispatcher also uses the supplied parameters (i.e. Modelkey and PropertyKey) to determine which model event this view will respond to. This view uses both the Modelkey parameter to specify the available models it is interested in and uses the PropertyKey parameter to match the attribute name of the assigned java.beans.PropertyChangeEvents.

View Method Setscnfld () is therefore labeled with the following information (here, Timemodel provides the key to register the model with 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 ());
}
});
}



Because dispatcher knows both the event that model fires and the event itself-for example, it knows the associated Modelkey and propertykey-, which is the only information that needs to be used to bind views and models. Model and view do not even need to share a communication interface or a shared database.

With our discussion of the binding mechanism, we can easily change the potential view without changing anything else. The following code is based on the same method that is 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 completely decoupled system has the following advantages: View can be more easily adapted to model changes, although model is usually stable and the opposite view is often changed. Plus the system can be designed by using a GUI editor or other source generator to avoid mixing the generated code with the Model-view communication code. And because Model-view binding information is metadata associated with the source code, it is also relatively easy to apply it to the IDE-generated GUIs or to convert an existing application into this framework. In addition to having a separate base code, view and model can be considered as stand-alone components to develop, which is likely to simplify the application development process. Component testing can also be simplified, because each component can be tested individually, and for debugging purposes, we can replace the real component with fake model and view.

However, there are many drawbacks. Because now when using interfaces and public classes to bind model and view, we can no longer provide security at compile time, and possible typographical errors will result in an omission of a binding between components, resulting in run-time errors.

By using @modeldependent's discussed modelkey and PropertyKey elements, you can define static relationships between model and view. However, real-world applications demonstrate that view must be able to dynamically adapt to changing models and application states: Considering that different parts of the user interface can be created and deleted within the life cycle of the application. So I'll explain how to use this framework to work with other common technologies to handle such situations.

Dynamic MVC binding

One problem with frameworks that rely on XML bindings (or some other declarative bindings based on configuration files) is the static binding rules. In these frameworks, dynamic changes are not possible, so developers often decide to couple redundant binding information with some decision algorithms that use the correct bindings each time.

To skillfully solve this problem, the Stamps framework provides two ways to change the bindings during runtime. The first is that views and models can be registered and logged on dispatcher using the event listener in conjunction with the GUI widgets. This allows specific views to be notified only when they are needed. For example, a monitoring console that is associated with an application can bind to objects that it monitors only when the user requests it.

The second approach is to use the two elements Runtimemodel () and Runtimeproperty () provided by @modeldependent annotation. They indicate that a certain model and its assigned events will be determined at runtime. If one of these two settings is correct, then the respective key (Modelkey or PropertyKey) is called by method on the view to get the value that needs to be used. For example, a view that displays a new set of channels (each channel is a model) relies on the input of the user to determine the channel that needs to be bound.

Examples of such situations are as follows:

This are invoked to display the messages of one news channel
@ModelDependent (Modelkey = "Dynamicchannel", PropertyKey = "Allmessages", Runtimemodel = True)
public void Setallmessages (Java.util.List messages) {
Updates the user interface
}

Public String Getdynamicchannel () {
Returns the channel requested by the user
}



additional Annotations

As the world is not perfect, some additional annotations are defined to help solve real-world cases. @Namespace allows developers to split them into different parts for better management of the model domain. Because a single dispatcher can handle conflicts that will occur in multiple Models,model keys. Therefore, it can divide the models and related views into different but namespace under the same domains, so that they will not interfere with each other.

@Transform annotation provides On-the-fly object conversions, from objects contained in the model event to objects accepted by the receiving views. As a result, the framework can be adapted to the stored code without any changes required. This annotation accepts a single parameter (defined as an implementation of a special interface) that is registered for effective conversion.

@Refreshable annotation can support the dynamic connection and detached views discussed above by tagging the model's properties. Using this annotation, the framework can handle both static and dynamic MVC layouts, binding different views to model at different times.

To understand the use of @refreshable, we have to go back to the previous example of the monitoring console. This console, which is a view in MVC terms, can dynamically bind and leave model, depending on the needs of the user. When the controller is connected to model @refreshable annotation can be used to keep the controller informed of its model status. When a view is connected to the frame, it must be updated in the state of the current model. Therefore, the dispatcher scan model looks for the @refreshable annotations and generates the same events as the view itself is normally accepted from model. These events are then assigned by the binding mechanism discussed earlier.

Distributed MVC Network

Dispatcher has a heavy burden. It is responsible for handling the delivery of all heavy information in the transmission cycle of the event:
· Model fires an event to determine the changes it has undergone, dispatcher processing notification model.
· Dispatcher scans all the views that are registered in it, looking for @modeldependent annotations, which define the changes in the views desired notification and when each model event occurs, method that needs to be invoked on the views.
· If necessary, the transformation will be used on the event data.
· View method extracts parameters from an event that is fired when it is invoked, and then the view updates itself.

On the other hand, when a new view is registered on dispatcher:
· View tells dispatcher about Modelkey, Modelkey can determine which model it will be connected to (the model event will be responsible for assembling view)
· If necessary, dispatcher scan model looks for @refreshable annotations and uses them to produce model events that will update view fake in a timely manner
· These events are assigned by using the above order, and the view is updated.

All of this does not involve view or model work, they stand at the ends of their respective channels of communication. It doesn't matter whether this information is transferred within a local JVM or between JVMs on multiple remote hosts. If you want to convert your local application to client/ What the server application needs is simply to change the logic within the dispatcher, while model and view are unaffected. The following illustration is an example:


Figure 4. A distributed network based MVC, click on the thumbnail to view the full picture

As shown above, a single dispatcher is a transmitter (It.battlehorse.stamps.impl.BroadcastDispatcher instance) on the same host as the model. Replaces one (or more) with a receiver (it.battlehorse.stamps.impl.FunnelDispatcher) on the same host as the view. The default implementation of the Stamps framework uses a messaging layer that is created on jgroups, and JGroups is a reliable, multicast-communication toolkit that works like a network transport mechanism (but different implementations and uses). By using it you can gain a reliable, multi-protocol, failure-alert communication.

A preliminary change to our application (dispatcher) allows us to move from a single user interface-independent running application to a multi-user distributed application. When model enters or leaves the network (imagining a communication failure), the framework can notify countless listening interfaces , so remote views can take appropriate responses. For example, a warning message is displayed to the user. This framework can also provide useful methods to help convert local controllers to remote.

Summary and Summary

There are still a number of elements that need to be explored, just as in the design of controllers, which has a consistent universality at present and dispatchers. The framework assumes common Controller-model bindings, Because the former needs to know how to drive the latter. Future development direction will be to support different types of views, such as using a Web browser, network alert applets, and Java and JavaScript communication.

The Stamps library that has been discussed explains how to reduce the coupling between views and models in an MVC architecture, and this framework can effectively utilize Java Annotations separates the binding information from the actual development program component. The binding logic with isolation allows you to separate components from the physical and provide a local and a client/server structure without changing the application logic or presentation layer. These goals provide insight into the possibilities offered by a design pattern as strong as MVC and the powerful metadata provided by annotations.



Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.