Building a Java servlet framework using reflection, Part 1

Reflective code provides more functionality in fewer lines of code.

Common Gateway Interface (CGI) scripting languages like Perl may offer as many features as the Java Servlet API, but servlets significantly outperform their CGI cousins when compared on any given functionality.

Many developers who have made the transition from CGI scripting to Java servlets have continued the CGI development style in Java, even though that style may not be the most efficient option available to them. Consider this example: programmers typically use servlets to interpret the input of a Web browser, determine an appropriate course of action, and then display results to the user. The process the servlet handles is thus fourfold: control, business logic, data access, and presentation. In the CGI framework, a single CGI script handled this entire process. Those who continue to program in a CGI style often create monolithic servlets to handle all four of these actions.

It doesn't need to be that way. This article will provide a tool which developers can use to create efficient, easy-to-maintain, server-side Java code.

Framework overview

The actions usually performed in a request-response model have four general characteristics:

Controller: Coordinates all of the other components

Business logic: Executes business rules, like validation, in order to satisfy the request

Data logic: Provides access to any necessary databases, CORBA services, EJBs, or other back-office persistence mechanisms

Presentation logic: Displays the results to the client

This framework does not depend on the presence of Java servlets. All it needs is agreement among developers on the contracts among objects. Some developers should be responsible for the controller code, which can be servlets, CORBA or RMI servers, or Netscape Application Server AppLogics. The other development teams need not be bothered about the specific implementation of those controller components; they need only use the methods provided by the controller objects.

How to create a servlet framework

You can create a servlet framework by assigning responsibilities to various system components. This in turn requires establishing a coding interfaces. The boundary lines between component groups must be drawn so that no object of one group can be used by any object outside that group. For example, be sure that no platform-specific object (like

HttpservletRequest

) or method is used by any noncontroller component.

A segmented system can be completely self-contained. For example, the team developing the data components can work to make it satisfy the following interface (a simple case), or something similar:

public interface DataInterface
{
    public void initializeDataObject();
    public void beginTransaction();
    public void commitTransaction();
    public void rollbackTransaction();
    public void executeSQL(String sqlString);
}

This is a simple interface for logging user actions or accepting user inputs to a database. Because all DataObjects must satisfy the DataInterface, it is possible for a single object to perform the duties associated with the database interaction. Again, no component outside of the data functionality should have to deal with connection pooling or database parameters.

Similarly, it is possible to create objects to handle presentation-related issues using an interface similar to the following:

 public interface PresentationInterface
 {
    public void initializePresentation(ProxyInterface proxy) throws IOException;
    public void output(String text) throws IOException;
    public void finalizePresentation() throws IOException;
 }

Notice that this interface is implemented by any object responsible for presentation-related activities. Also notice the difference between the two interfaces. The PresentationInterface lists a method with a parameter called PresentationService. The PresentationServicesInterface is the key to the framework, as it is the controller in the framework.

The implementor of the ProxyInterface is the single point of contact for all platform-dependent functionality. The Presentation object does not need to understand the details of implementing the presentation layer. Whether it is through applets, servlets, or sockets, the methods of the interface must be satisfied.

The ProxyInterface shown below can obtain references for the other objects involved in the request-response pattern.

 public interface ProxyInterface
 {
    public DataInterface getDataObject(String name);
    public PresentationInterface getPresentationObject(String name); 
    public BusinessInterface getBusinessObject(String name);
 }

Because this interface is quite generic, it may be used as a proxy for requests made to servlets or other server-side applications, such as Netscape Application Server's AppLogics. The intention is to create a structure such that all the information specific to the deployment platform is contained in the implementation of the ProxyInterface, or in a small subset of classes. This allows developers to write code independent of proprietary methods or variables, thereby increasing the efficiency of the development team.

The last component of our expandable framework is the BusinessInterface:

 public interface BusinessInterface 
 {
    public void init(ProxyInterface proxy, Hashtable parameters) throws Exception;
 }

An implementation of this interface is spawned for every request made to the framework. This interface contains a method through which the input parameters pass from the servlet to the business object. In addition, the proxy is passed to the business object in order to provide it with the tools necessary to perform its task.

We have created a framework through which we can launch business objects to perform the individual actions of the users -- including all data and presentation issues -- without any knowledge of the individual platform upon which they reside. Now, how do we know which business object to load?

Dynamic business objects loading

The Java Reflection API provides an easy mechanism with which to execute this framework. In this first part of our series, Reflection itself is not used. For now, we will use the

class.forName(...)

method to load the appropriate business object.

    private static BusinessInterface loadBusinessObject(String actionName)
    {
        BusinessInterface bi = null;
        
        try
        {
            // attempt to retrieve the class from the class loader
            Class c = Class.forName(actionName + BUSINESS_OBJECT);
            
            // Initialize a class array with no elements (used in the constructor)
            Class[] parameterTypes = new Class[0];
            
            // Locate the constructor with no arguments
            Constructor constructor = c.getConstructor(parameterTypes);
 
            // Initialize an object array with no elements (used in the newInstance method)
            Object[] initargs = new Object[0];
            
            // Load the new instance of the business interface implementor
            bi = (BusinessInterface) constructor.newInstance(initargs);
 
        }
        catch (Exception e)
        {
            // If there is an error, create an ErrorBusinessObject (for now)
            bi = (BusinessInterface) new ErrorBusinessObject();
 
            e.printStackTrace();
        }
        
        return bi;
    }

This method is contained in the BusinessObjectFactory class and dynamically loads the business object based upon the action name provided by the proxy. This method attempts to load the business object and, if any exceptions occur, will create an object to handle the error conditions. It assumes a default constructor for this example, but can be tailored to suit any development efforts as needed.

The two main benefits to loading business objects dynamically with this scheme are that you can avoid a registration step and can add functionality to the application on the fly. Both of these advantages stem from the fact that there is no need to specifically hardcode the possible actions contained in the servlet.

In Part 2 of this series, I'll expand on the benefits of the Reflection API and show some more benefits to this framework. Reflection will make it possible to further reduce the amount of code while greatly increasing the functionality contained within.

A framework example

To complete the explanation of this framework, I have constructed a simple example using the interfaces and

Factory

class discussed above. In this example, the servlet is used as the controller, data interface, and presentation interface. The example uses extremely simple business objects that show the dynamic behavior contained in the servlet, but not the complete functionality of the system, as that is beyond the scope of the article.

The Proxyservlet service method is the heart of the framework:

public void service(HttpservletRequest req, HttpservletResponse res) throws servletException, IOException { // convert the request into a hashtable Hashtable requestHT = getHashtableFromRequest(req);

// retrieve the actionName from the hashtable actionName = getCurrentAction(requestHT); // Create a business Object BusinessInterface bi = getBusinessObject(actionName); try { // Initialize the business object bi.init( (ProxyInterface) this, requestHT); } catch (Exception e) { // Any exception handling code goes here } }

In the code above, the servlet converts the HttpservletRequest into a simple hashtable containing the name-value pairs. It then retrieves the actionName from the hashtable and creates the BusinessInterface implementation. The last step is to initialize the BusinessInterface and let it perform any necessary processing.

This simple example ignores some of the session handling that can be in the service method. This processing can either be handled by the servlet, or in the business object through methods in the proxy interface. It is up to the developer to make the design decision as necessary.

Error handling

One key thing to notice in the above code snippet is the try-catch block surrounding the

init

method of the

BusinessInterface

object. This try-catch actually ensures that all error handling can be performed in the same location within the controller. This allows the developers to handle minor exceptions within their business objects and to pass major exceptions up to a single point in the code to ensure that all errors are handled together.

In the second of part this series, I'll expand on some other error-handling additions we can make to this framework to increase its power and expandability.

Conclusions

This article has presented the benefits of using a framework to develop server-side code that supports the request-response pattern associated with the servlet API. In doing so, I have presented the importance of a platform-independent framework and a single location for error handling. I have also alluded to the benefits -- which I will expand upon in Part 2 -- available through true Reflection.

Using this framework allows developers to efficiently design an object-oriented, server-side solution without tying it into the platform upon which it was developed. This provides key benefits if you want to add new features to the system, or if you later decide to port it to another server product.

As new features are added to the system, there is nothing hardcoded that needs to be kept in sync with the code base. The servlet will pick up any additional business logic upon its next execution due to the dynamic class loading that I have demonstrated.

A common error-handling framework is essential to servlet development because it ensures that a user's experience on a Web site is consistent and common to all functionality contained on the site. A single error-handling component is also a benefit to developers because it allows them to avoid building error-handling into every object.

This framework, while simple, does suffer from some overhead due to the isolation of the components, but this also ensures that there are no cross dependencies. The second part of this series will further demonstrate the utility of this framework by expanding on the use of reflective code to enable server-side validation, error handling, data access, and output.

Michael Cymerman is a consultant specializing in Java/Internet software solutions. Michael provides Java/Internet-based architecture, design, and development solutions to Fortune 500 companies.

Learn more about this topic

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