Implementing Object Caching with AOP

Object caching has a number of characteristics that make it a prime candidate for implementation as an Aspect. Learn what some of these characteristics are.

Introduction

Object caching provides a mechanism to store frequently accessed data in memory, minimizing the calls to back-end database, and resulting in significant improvement in the application performance. It also gives us the ability to refresh different types of data at different time intervals (based on pre-defined eviction and cache refresh policies). Object caching offers several advantages with fast access to data but it also suffers from some disadvantages like memory overhead and synchronization complexity. By making caching an Aspect we get the flexibility of dynamically adding caching ability in a J2EE application for cached objects. We can also remove caching out of the application whenever it becomes a bottleneck in terms of memory usage. In this article, I will provide an example of object caching as an Aspect in J2EE applications and discuss the steps involved in injecting the caching functionality into a sample web application. I will also explain the flexibility of switching between two different caching frameworks without modifying any application code. The article uses AspectJ, with the caching logic implemented in both JBossCache and OSCache.

Caching As An Aspect

Object caching has a number of characteristics that make it a prime candidate for implementation as an Aspect. Some of these characteristics are:

  • caching code is often duplicated throughout a web application
  • it's not easy to turn caching on or off dynamically when it's part of your business logic
  • caching logic does not provide any business functionality, it's not related to the domain of a business application 

This article assumes that reader has a basic understanding of Aspects and AOP concepts. Those who are new to Aspects should review the Appendix section at the end of the article for a brief overview of AOP and definitions of AOP components.

In the traditional approach to object caching, objects are stored in memory (cache) after first use, and then reused for subsequent data access requests instead of making costly data source calls. The cached data is released from memory (purged), by implementing a pre-defined eviction policy, when the data is no longer needed.

For an Aspect-based approach, we need to first write a pointcut to intercept the actual data load method called by a client program. Then we write an "around" advice to query the cache for a matching data based on a cache key. If no data match is found, the advice uses proceed() (which is similar to calling the load method in cache loader class) to execute the data access operation and inserts the data returned from the datastore into cache. Finally, the advice returns the object obtained from the cache if the data already exists in cache or newly created object if cache didn't have it in the first place.

Compared to traditional caching implementation, a cache loader class is not really necessary in aspect oriented caching solution. This is because with a caching aspect we have access to the calling class and its methods via the pointcut, so we know which method to call to retrieve the data from back-end data store.

Following table summarizes the steps involved in data access calls with and without caching implementation and also using caching as an aspect:

Data Access Scenario 1 Scenario 2 Scenario 3
Type of caching No caching implemented. Traditional caching implementation. Aspect oriented caching implementation.
First request Retrieve data from database and return result to client. Check if data is in cache.
No match is found so retrieve data from database, store the result in cache, and return data to client.
Intercept the data access call using a pointcut.
Using an around advice, check if data is in cache.
No match is found so call proceed() method which in turn executes the data load method.
Store the result in cache and return data to client.
Subsequent requests Retrieve data from database again and return result to client. Check if data is in cache.
A match is found so return the data found in cache to the client.
If data in the cache is expired, retrieve data from database again, store it in cache, and return to client.
Intercept the data access call using the same pointcut.
Check if data is in cache using the around advice.
A match is found so return data to client.
Caching Logic None. Embedded in application code. Encapsulated in Aspects.
Invasive? N/A Yes (Changes in application code are necessary to enable or disable caching) No (Completely separated from the application code. Caching logic is dynamically weaved into the application)

Caching using JBossCache - AOP Style

I used JBossCache as the main framework to demonstrate aspect oriented object caching implementation in the sample web application. JBossCache provides two flavors of object caching implementation, TreeCache and TreeCacheAop. TreeCache is basically a structured object tree with nodes. The data stored in the cache is accessed by specifying a fully qualified name (FQN) which is the concatenation of all node names from root node to the current node. FQN is same as the region name used in other caching frameworks such as Java Caching System (JCS) and OSCache. A cache region is defined as an organizational name space for holding a collection of cache objects with similar characteristics (such as business usage and time to live). TreeCache can be used as a stand-alone cache (local) or a replicated cache (in a multi-server cluster environment). Also, the cache replication can be done asynchronously or synchronously (synchronous means the client request is not returned until all cache changes are replicated in the cluster nodes). JBossCache uses the JGroups framework for the group communication purposes when replicating the modifications in one cache to other caches in the cluster.

TreeCacheAop is an AOP-enabled extension of TreeCache. It allows for plain java objects to be stored in the cache and replicated transactionally between the nodes in a cluster. TreeCache is highly configurable in terms of replication mode (async, sync, or none), transaction isolation levels, eviction policies, and transactional management.

Object Caching Framework

The object caching framework used in this article is based on the object caching framework described in articles Object Caching in a Web Portal Application Using JCS and J2EE object-caching frameworks. Based on the requirements of caching implementation as an aspect, I have made a list of objectives that needed to be accomplished by the proposed Cache AOP framework. Following is the list of these objectives:

  1. Avoid duplicating the caching logic at every execution point in the application where there is a business case for data caching.
  2. Write cache related code in a non-intrusive way so it doesn't affect the core application code. This is because the caching logic has nothing to do with the core functionality of the application. It is used mainly for performance reasons.
  3. Ability to switch between the scenarios of running the application with and without caching, in order to assess the effectiveness of caching in data access. With the caching aspect implementation, if caching proves to be not really effective, we don't have to modify the application code by commenting or removing the caching logic. This approach is very useful during unit testing phase where we want to test the caching feature with different configuration parameters to come up with the best scenario.
  4. Flexibility to easily switch between or replace the caching implementation with a different (and better) framework without affecting the application code. There are many different algorithms and configuration settings so it takes few iterations to get optimum caching configuration and best results out of a caching implementation.
  5. Ability to trace and monitor caching statistics for better management of objects stored in the cache. We need to constantly monitor cache usage to determine how effectively caching is being used (based on cache hit to miss ratio) and make any necessary adjustments in the cache configuration or the expiration policies to keep object caching more effective and efficient. Also, as the business requirements and application functionality change over time we need to make sure we are only storing the expensive-to-access (and frequently requested data) in the cache. By encapsulating tracing and logging functions into aspects as well, we will have a non-intrusive cache monitoring capabilities where we don't have to write the tracing method calls all over in the application code to measure the effectiveness of caching.

Sample Web Application Setup

The web application used to demonstrate the caching implementation using AOP techniques is a bank application used to process home loans. In a typical loan processing application, the customer is given a list of interest rates for a specified product type before the user can actually lock a home loan. These interest rates are a perfect candidate for caching since they only change few times a day but the rate data is frequently accessed by the loan application. So, by storing interest rate details in the cache, we minimize the need to hit the data source every time a loan application is processed. There are two types of loan products used in the web application, Mortgages and Home Equity (HELOC) loans.

The interaction between different tiers in the loan application (client, web, object cache, and back-end HSQL database) is represented in the topology diagram in Figure 1.

Click for larger view

Figure 1. Topology Diagram with Tomcat Cluster and JBossCache (Click on the screen shot to open a full-size view.)

The flow of loan processor web application is shown in the sequence diagram in Figure 2.

Click for larger view
Figure 2. LoanApp Web Application Sequence Diagram (Click on the screen shot to open a full-size view.)

The entry point into the web application is a servlet called LoanAppServlet that gets the interest rates based on product type sent by the client (in HTTP request). The helper class LoanAppHelper has the data access methods to retrieve (using JDBC calls) the interest rates stored in a HSQL database. Following is a code snippet of getInterestRates method to get the rates from INTERESTRATES table in LoanDB database using JDBC calls.

   public List getInterestRates(String prodGroup) {

      List interestRates = new ArrayList();

      Connection conn = null;
      Statement stmt = null;
      ResultSet rs = null;

      try {
         conn = DriverManager.getConnection(jdbcUrl, userName,
               password);
         sLog.debug("conn: " + conn);
         stmt = conn.createStatement();

         String sql = "SELECT * FROM InterestRates WHERE ";
               sql += "productGroup = '" + prodGroup + "'";
               rs = stmt.executeQuery(sql);

         List rates = new ArrayList();
         while (rs.next()) {
            String productType = rs.getString("productType");
            String productGroup = rs.getString("productGroup");
            double rate = rs.getDouble("rate");
            double mtgPoints = rs.getDouble("points");
            double apr = rs.getDouble("apr");

            InterestRate interestRate = new InterestRate();
            interestRate.setProductType(productType);
            interestRate.setRate(rate);
            interestRate.setPoints(mtgPoints);
            interestRate.setApr(apr);

            interestRates.add(interestRate);
         }
      } catch (SQLException e) {
         e.printStackTrace();
      } finally {
         try {
            rs.close();
            stmt.close();
            conn.close();
         } catch (Exception e) {
            //
         }
      }
      return interestRates;
   }

Note: Using pure JDBC calls in a real world application is not recommended. Instead, object relational modeling (ORM) tools such as Hibernate or JDO and database connection pooling (Commons DBCP) frameworks should be considered for data access requirements.

The LoanApp web application uses TreeCache (org.jboss.cache.TreeCache) class to implement caching. It also uses asynchronous replication to propagate cache changes to all nodes in the cluster. Following section describes how caching is introduced into the application using aspects.

I wrote an abstract aspect called ObjectCache to encapsulate all the cache methods (like putCacheObject, peek etc). The concrete implementations of this abstract aspect are defined in JBossCache and OSCache (for JBossCache and OSCache caching frameworks respectively). I also added two more aspects to take care of logging and tracing requirements in the web application (logging and tracing are two other popular cross-cutting concerns that are weaved into the application code via aspects).

Figure 3 shows the class diagram with the associations between java classes and aspects created in LoanApp application.

Click for larger view
Figure 3. Cache AOP Class Diagram (Click on the screen shot to open a full-size view.)

I initialized the cache objects when the web application context (/CachingAOP) was created by Tomcat server and keep them in memory until the application context is destroyed. This is done by implementing a custom servlet context listener (LoanAppContextListener). I added pointcuts contextInitialized and contextDestroyed to take care of the instantiation and removal of cache objects when the servlet context is initialized and destroyed respectively. Following code shows how TreeCache is initialized when servlet context is loaded.

    abstract pointcut contextInitialized(ServletContextEvent event);
    void around(ServletContextEvent event) : contextInitialized(event) {
        initObjectCache();
    }

And here's the implementation of initObjectCache method in JBossCache aspect:

   private TreeCache cache;

   public void initObjectCache() {
      try {
         cache = new TreeCache();
         PropertyConfigurator config = new PropertyConfigurator();
         config.configure(cache, "jbosscache-config.xml");
         cache.setClusterName(cacheGroupName);
         cache.createService();
         cache.startService();
         sLog.debug("cache : " + cache);
      } catch (Exception e) {
         // Handle cache region initialization failure
         sLog.debug("Error in creating JBossCache.");
         sLog.error(e);
      }
   }

We write a pointcut to intercept getInterestRates() method and then write an "around" advice to implement the caching logic. In the advice, we query the cache for a matching data based on productGroup cache key. Let's look at how this logic is implemented in ObjectCache aspect.

   abstract pointcut getInterestRates(String productGroup);
   List around(String productGroup) : getInterestRates(productGroup) {
      long start = System.currentTimeMillis();

      List rateList = null;
      try {
         if (peek(productGroup) != null)
         {
            sLog.debug("Data found in cache.");
            rateList = getInterestRates(productGroup);
            return rateList;
         }
         sLog.debug("Data not found in cache.");
         rateList = proceed(productGroup);
         putCacheObject(productGroup, rateList);

         long stop = System.currentTimeMillis();
         sLog.debug("Time taken to get interest rates=" + (stop-
               start) + " ms.");

  printCacheStatistics();
      } catch (Exception e) {
         e.printStackTrace();
      }
      return rateList;
   }

If there is a cache miss or stale hit, the advice uses proceed() to execute the data access operation (LoanAppHelper.getInterestRates() method) and inserts the data returned from the database into the cache (using putCacheObject method). Finally, the advice returns the object obtained from the cache if it already exists in the cache or newly created object if cache didn't have it in the first place.

The following table shows the details of the caching aspects and pointcuts.

Aspect PointCut Advice
ObjectCache.aj
JBossCache.aj
OSCache.aj
contextInitialized
contextDestroyed
getInterestRates
around
around
around

The AspectJ project created to run the web application code using aspects is called CacheAOP. I created separate directories under CacheAOP/WEB-INF/src folder (java, web, and aspects) to store java files and aspect files in their own directories. A link to the sample application code is included at the end of this article. To run the application, make sure you have the following JAR files specified in the classpath:

commons-logging-1.0.4.jar, junit.jar, log4j-1.2.8.jar, oscache-2.1.jar, commons-httpclient.jar, aspectjrt.jar, jgroups-2.2.7.jar, concurrent.jar, dom4j-full.jar, hsqldb.jar, jboss-aop.jar, jboss-cache.jar, jboss-common.jar, jboss-j2ee.jar, jboss-remoting.jar, jboss-system.jar, jboss-jmx.jar, and servlet-api.jar.

The config directory (located under WEB-INF) also needs to be in the classpath since all the configuration files (JBossCache, OSCache, and Log4J) are stored in this directory.

You can switch between different caching scenarios using AspectJ build properties file (build.ajproperties) created in the Eclipse project. Just open the properties file in the editor, include or exclude the aspects you would like to weave into the application code, and rebuild the project. This involves no additions or modifications in the web application code. Figure 4 shows the screenshot of build.ajproperties (AJDT) tab in Eclipse IDE with ObjectCache and JBossCache aspects selected for code weaving.

Click for larger view
Figure 4. AspectJ Build Properties Window in Eclipse IDE (Click on the screen shot to open a full-size view.)

  1. Run the web application with no caching implemented.
  2. Caching is implemented using JBossCache.
  3. Caching is implemented using OSCache framework.
To understand how caching is introduced in the web application, I ran the build command for the following three scenarios:

Cluster Details

LoanApp web application was deployed to run in a Tomcat cluster with two server instances running on a single machine. The configuration parameters used in the cluster are listed in the table below.

Parameter Node 1 Node 2
Server Name TC-01 TC-02
Home Directory c:/dev/tomcat51 c:/dev/tomcat52
Web App Directory C:/dev/projects/CachingAspect/node1/CachingAOP C:/dev/projects/CachingAspect/node2/CachingAOP
Server Port 9005 10005
Connector/Port 9080 10080
Coyote/JK2 AJP Connector 9009 10009
Cluster mcastAddr 228.0.0.4 228.0.0.4
Cluster mcastPort 45564 45564
tcpListenAddress 192.168.0.10 192.168.0.20
tcpListenPort 4001 4002
Cache properties file jbosscache-config.xml jbosscache-config.xml
properties file location node1/CachingAOP/WEB-INF/config node2/CachingAOP/WEB-INF/config
Cache mcast_addr 228.1.2.3 228.1.2.3
Cache mcast_port 48866 48866
Cache bind_addr 192.168.0.10 192.168.0.20
Cache bind_port 9080 10080
Database c:/dev/db/loandb c:/dev/db/loandb

Instrumentation Layer

Two aspects called Trace and CacheLog were created to introduce logging and tracing capabilities into the web application to measure response times for accessing cached objects and to log various events in the caching process.

Tracing aspect was used to monitor the effectiveness of caching by calculating the total time taken to get the data (from the database or from cache). It takes longer when the data is accessed for the first time since the data is not in cache and we need to retrieve it from the back-end database. But subsequent calls are faster since data is already present in cache and no need to access the database. This aspect also measures the available JVM memory before and after each call to getInterestRates method.

Logging aspect was used to verify that the data is retrieved from a data source the first time it's requested by the client and from cache for all the subsequent requests. It was also used to keep track of cache hits and misses. All the log messages were saved in a single log file (cachingapp.log) to monitor the cache statistics and cluster details to see how many cluster members are alive and how cache changes (if any) are propagated between the cluster nodes.

Testing setup

To test the implementation of caching aspect and replication of cache changes, I wrote a test client called LoanAppClient. It uses the Commons HttpClient API to simulate the web request calls to LoanAppServlet to process loan applications. Based on a random number generated, the test client issues a different loan request (such as getting interest rates for either mortgage loans or HELOC loans). Depending on the request attributes sent to the server, LoanAppServlet gets the interest rate data for the specified loan type and product.

The hardware/software specifications of the PC used to test the sample web application are listed below.

  • CPU: HP Pavilion Pentium III 800 MHz
  • Memory: 512 MB RAM
  • Hard disk: 40 GB
  • Operating system: Windows 2000 server Service Pack 4
  • JDK version: 1.4.2_05
  • Tomcat version: 5.0.28
  • Tools Used: Tomcat, JBossCache, OSCache, HSQL DB, Eclipse, AspectJ, AJDT, Commons HttpClient

Listed below are the run-time parameters for LoanAppClient:

  • Number of client threads: 2
  • Number of repetitions: 1,000
  • Delay between requests: 1,000 milliseconds
  • Number of test samples: 1,000

The command used to run the test client with the required arguments is as follows:

java -Dlog4j.configuration=log4j.xml com.loanapp.test.LoanAppClient 1 192.168.0.10 9080 192.168.0.20 10080 2 1000

Test Results

The results from the web application test runs are listed in the following table:

Data Access Calls Without Caching
(ms)
With Caching
(ms)
First request 140 180
Subsequent requests (avg) 50 5

Conclusion

We looked at how fast the data access can be when we use a cache to store frequently accessed objects. Even though it took little longer to get the interest rates data the first time from the database, it took almost no time to get data from cache compared to the time it took to get it from the database (5 milliseconds with caching compared to 50 milliseconds without caching). We also looked at how easy and flexible it is to make caching part of the web application by encapsulating caching logic in aspects.

One advantage of using aspects compared to traditional object oriented programming is that if something doesn't turn out to be as effective as originally expected, the aspect can be seamlessly un-weaved from the application code without any impact on the actual code. Aspects provide plug-n-play capabilities in the application development.

We have achieved the following design goals by implementing object caching as an aspect:

  • loosely coupled architecture.
  • separation of logic.
  • dynamic adaptation of caching and tracing cross-cutting concerns.
  • modularized design.

Currently, all the cache related code is written in the aspect files. This can be further refactored to separate cache logic into separate java classes and keep just the AOP related code (pointcuts, advices etc) in the aspect files. A good AOP system should involve collaboration between aspects that provide the key cross-cutting concerns and java classes that implement all the helper methods.

A JMX based Cache monitoring tool using aspect oriented design will be a good complementary tool to the CacheAOP framework described in this article. We can use this tool to monitor the effectiveness of caching by collecting the details such as hit/miss ratio, # of accesses, last access time, object size, time to load data from data source etc.

Appendix: Aspects

This section provides a brief overview of AOP. For more details on AOP and AspectJ topics, refer to AspectJ In Action book.

Aspect-oriented programming allows developers to add behavior to objects in a non-obtrusive, clean, and modularized manner through the use of static and dynamic crosscutting. The main goal of using aspects in application development is to code the cross-cutting concerns in separate modules and apply them in a flexible and declarative way. AOP is light weight compared to OOP in the sense that in OO frameworks we have to engineer utility functions and non-business related modules and we really can't completely separate these functions from the core application code, so they end up coupled together. But in AO programming we have the flexibility of achieving this goal of total separation of concerns.

Following are some of the advantages aspect oriented software development (AOSD) offers to complement J2EE application design, development, and deployment tasks:

  • Dynamic application of adaptations:
    Aspects are a great choice for implementing an autonomic system since we can apply code retrospectively to a running application without having to modify any of the existing application code.
  • Easy removal of adaptations:
    AOP frameworks allow easy removal of previously woven aspects returning the application code to its original state. Using AOP approach, it's very easy to remove previously introduced aspects by removing the advice attached to a particular join-point. Since the application code itself is not modified, we can safely rebuild and deploy the application without having to go through extensive unit testing and QA testing phases every time an aspect is removed from the application.
  • Encapsulation of Adaptations:
    AOP gives us the advantage of encapsulating the cross-cutting concerns such as caching, security, persistence, etc and thus provides the true separation of these modules from the core application resulting in greater reuse of code. The aspects are encapsulated meaning no external references or changes are required to the base application code. The same aspect could be introduced to any other application with only changes made to the pointcuts.
  • Specify inter-aspect relationships:
    We can specify relationships between the aspects themselves to achieve even more code reuse and flexibility.
  • Implement fine grained changes.
  • Apply adaptations to various points in a system:
    AOP models allow us to attach code to a wide variety of execution points, not just one place, in the program. This helps in avoiding writing the same code in multiple places in the application.

The various modules in a typical J2EE application that are good candidates for aspects are caching, pooling, logging, tracing, security, persistence, exception handling, unit testing using virtual mock objects, business rules, state management, and async/sync communication.

Other applications of aspects are database, operating systems, and J2EE design patterns. Awais Rashid, in his presentation on Aspect-Oriented Programming for Database Systems, discusses the cross-cutting concerns in Object Oriented Database (OODB) systems such as keeping persistent and transient space consistent, version management policy and locking scheme. Kiczales and others explain using AOP to improve operating system structure by implementing a prefetching aspect in Structuring Operating Systems Aspects.

The other area of using aspects is the design patterns. In the book Aspect-Oriented Software Development there is a discussion about aspect oriented alternatives to popular design patterns such as Visitor, Observer etc. Also, Russ Miles in his book AspectJ Cookbook talks about implementing creational object-oriented design patterns including Singleton, Prototype, Abstract Factory, Factory Method, and Builder patterns using AOP techniques.

The main components of AOP are aspects, join points, pointcut, advice and introduction. Following are the brief description of these AOP components:

Aspect:
Aspects are similar to java classes; they are units of modularity for cross-cutting concerns. Aspects define pointcuts and advices to dynamically weave cross-cutting concerns (such as logging, tracing, security etc) into existing objects in the program.

Join-Points:
These are well-defined points of execution in a program. Examples of joinpoints include calls to a method in a class, access to class members, conditional checks, variable assignments, and exception handler blocks. Join points have the context of target object and its arguments associated with them. A join point can sometimes contain other join points.

An example of join point is the call to method contextInitialized in LoanAppContextListener class in our sample web application. Join point is an abstract notion meaning we don't really define a join point in an aspect.

Point-Cut:
A pointcut is a program construct to capture join points and apply the cross-cutting concern. We defined a pointcut called contextInitialized in ObjectCache aspect in the sample web application.

Point-Cut Example:

    abstract pointcut contextInitialized(ServletContextEvent event);
          

Advice:
An advice is an additional block of code that we want to execute at specific points (pointcuts) in the program flow. There are different ways to associate an advice with a joinpoint: before (the advice runs just before the joinpoint), after (advice runs just after the joinpoint; after normal return or after throwing an exception in the method) and around (advice surrounds the joinpoint and we have the control when joinpoint execution proceeds) the execution of a joinpoint.

In our sample application, we define around advices for contextInitialized and contextDestroyed pointcuts. We also define before and after advices for calls to loan processor methods.

Advice Example:

    void around(ServletContextEvent event) : contextInitialized(event) {
       initObjectCache();
    }
          

Introduction:
Introduction allows us to add new state (attributes) and behavior (methods) to existing java classes without having to modify any code in the classes.

AOP: Static and Dynamic

AOP languages can be either static or dynamic based on the way an aspect is weaved into the application code. A static AOP language uses build-time weaving of aspects into the application. AspectJ framework is a popular example of static AOP implementation. AspectJ language is used in writing the aspects in the sample web application described in this article.

On the other hand, dynamic AOP languages offer the advantage of dynamically (at run-time) weaving the aspects and allow the application's behavior to be modified while the application is still running. Even though dynamic AOP provides a powerful tool to manipulate the application characteristics at run-time, it does have some disadvantages. The disadvantages of dynamic AOP approach are the performance hit taken to check whether or not an aspect should be woven at the current point of execution. There could also be some issues related to safety, security and compatibility when introducing additional code at run-time.

There are currently many dynamic AOP frameworks available. Some of the examples of dynamic AOP implementations are AspectWerkz, Java Aspect Components (JAC), Dynaop, Nanning, and Prose. Also, there are many J2EE application servers and frameworks that are using AOP concepts. JBoss Application Server and Spring framework are two examples of AOP implementations.

AspectJ and AJDT

AspectJ is an aspect oriented extension of Java language that enables creating aspects and integrating them into existing or new java applications. We can write aspects in the same way as we write the java classes with some new key words provided by AspectJ. Both an aspect and a java class have the same constructs so we can easily integrate them together without having to use different syntax and tools when we work with these two languages. AspectJ Development Tools for Eclipse (AJDT) is an Eclipse plugin that provides the tools to develop, build, and deploy J2EE applications using Java and AspectJ languages.

Resources


Author Bio

Srini Penchikala presently works as Information Systems Subject Matter Expert at Flagstar Bank. His IT career spans over 9 years with systems architecture, design, and development experience in client/server and Internet applications. Srini holds a master's degree from Southern Illinois University, Edwardsville and a bachelor's degree (Sri Venkateswara University, India) in Engineering. His main interests include researching new J2EE technologies and frameworks related to Web Portals. He has also contributed to ONJava, DevX Java and JavaWorld online journals. Srini's spare time is filled with spending time with his wife Kavitha and 3-month old daughter Srihasa and watching Detroit sports teams. Go Pistons.

Dig Deeper on Software development best practices and processes