|
The Problem
The first thing you need to do is retrieve all your ATM data from an external system through a network. This sounds simple and vague at the same time. To complicate matters, add that your application needs access to this data frequently throughout any given day. You might be saying: "That's not a problem." Which it wouldn't be in cases where the network traffic is low but, when traffic increases it could take anywhere from 10 to 30 seconds to retrieve this data. Requiring end-users to wait 10 or more seconds for a page or dialog to load is simply unacceptable and will eventually result in a poor perception of the application. User perception is everything, right? Tasks that appear to be doing something by showing progress indicators or animations are more likely to be perceived by the end-user as quicker during long-running tasks. Leaving users in the dark regarding what is going on with the application is simply asking for trouble. If a user has to assume that the application is doing something, nine times out of 10, they'll try to re-invoke the same operation thus, causing an even longer wait not only for themselves and for other users. You'll need to account for this while designing the ATM locator system; making calls to the external system is the most critical function to address.
Addressing this problem, the example application uses a data caching approach. This works in most caseshowever, like anything in software development, you should gain a full understanding of the data your application will read and/or write. It's important that you know what questions to ask when dealing with another business' data. When considering whether or not you can cache data, first ask: how much data is there and how often does it get updated? Obviously this is an incomplete list of questions to ask. But for the ATM locator service it is just enough information to move forward with development.
There's a popular saying in the XP community: "Build just enough and not much more." This means that when you're developing a service, you build the core first and then implement solutions when problems arise. This is the strategy employed here.
Take a look at the domain model in Figure 1. This shows the service's data requirements.

Figure 1. Domain Model
The core of this service is the AtmDao interface, shown below in its entirety:
package jbriscoe.article.spring.caching.dao;
import java.util.Set;
import jbriscoe.article.spring.caching.model.Atm;
public interface AtmDao {
public Set<Atm> getAllAtms() throws Exception;
}
Designing to an interface allows you to scale the application to different areas of need in the future with minimal code change. In this example, the AtmDao interface is implemented with a static spring managed class, shown in Listing 1.
Pretty simple, huh? The real code for accessing an external system is outside the scope of this article. What is important is that you have a long-running method. In this case, the method is always going to run at least 10000 milliseconds, or 10 seconds, caused by the Thread.sleep(..) static method call. The ATM set is injected into the class by the Spring Framework at application context startup. The following is a snippet from the amtlocator-atms-beans.xml file (see Listing 2).
Click here to see the entire atmlocator-atms-beans.xml file.
Once you have your static data beans defined in Spring, the next step is to add an instance of the AtmDao implementation. The atmlocator-core-beans.xml file contains all core service beans, but, at this point, you only need to add the atmDao bean:
<bean id="atmDao"
class="jbriscoe.article.spring.caching.dao.impl.StaticAtmDaoImpl">
<property name="atms" ref="atms" />
</bean>
For those of you new to Spring, the ref attribute in the atms property simply means "Find a bean named atms in any xml context file." Now that you've got some actual code, it's time to run a test. The Spring framework makes it very easy to unit test your beans. And testing them within Spring allows you to verify that they are integrated properly and perform as expected.
I prefer to build an abstract base class named AbstractSpringTest for Spring test cases. This class will typically extend the Spring-provided org.springframework.test.AbstractDependencyInjectionSpringContextTests class. By extending this class, you can allow your test cases to participate in dependency injection. The new AbstractSpringTest class should look like this:
package jbriscoe.article.spring.caching;
import jbriscoe.article.spring.caching.dao.AtmDao;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
public abstract class AbstractSpringTest extends
AbstractDependencyInjectionSpringContextTests {
protected AtmDao atmDao;
public AbstractSpringTest() {
setPopulateProtectedVariables(true);
}
@Override
protected String[] getConfigLocations() {
return new String[] {
"classpath:jbriscoe/article/spring/caching/atmlocator-core-beans.xml",
"classpath:jbriscoe/article/spring/caching/atmlocator-atms-beans.xml" };
}
}
Notice that the atmDao field is protected in the AbstractSpringTest class. Also, a call has been added to setPopulateProtectedVariables(true) within the constructor. This removes the need for getters and setters within this class. This method could be substituted by a private atmDao with a matching setter within this class. That would have worked in just the same way. The getConfigLocations() method simply tells the Spring test class what beans files are to be included within your application context.
The next step is to create an actual test case that extends from the class you just built making a call to the AtmDao implementation. The implementation of the test case for getting all of the ATM objects is relatively simple and should look like Listing 3.
For those of you that noticed, Listing 3 calls the getAllAtms method twice in the larger method. This will come in handy later, when you want to test the performance of the method on subsequent calls (as will be made clearer in a moment). Once you have your unit test built, go ahead and run the test. Your test results, barring any compile errors, should pass successfully and look similar to the following test output:
INFO AbstractSingleSpringContextTests - Loading context for:
classpath:jbriscoe/article/spring/caching/atmlocator-core-beans.xml,
classpath:jbriscoe/article/spring/caching/atmlocator-atms-beans.xml
INFO StaticAtmDaoImpl - Actually performing all atms lookup.
INFO StaticAtmDaoImpl - Actually performing all atms lookup.
This unit test is slow, but how slow? To find out, you'll build a spring AOP interceptor that will time the method call execution from start to finish. It's always good to reuse existing code, so the interceptor will use the commons-lang StopWatch class for all timing needs. Listing 4 is the implementation of the method execution timer interceptor.
The method interceptor works like this: you create a new instance of StopWatch, then start the timer. Next, execute the method that you're timing and log the results for analysis. Now that you've got an interceptor, you need to register it with Spring for use. Add the following beans in atmlocator-core-beans.xml:
<aop:config>
<aop:pointcut id="getAllAtmsPointcut"
expression="execution(* *..StaticAtmDaoImpl.getAllAtms())" />
<aop:advisor id="methodTimingAdvisor"
advice-ref="methodTimingAdvice" pointcut-ref="getAllAtmsPointcut" />
</aop:config>
<bean id="methodTimingAdvice"
class="jbriscoe.article.spring.caching.interceptor.MethodTimingInterceptor" />
Before you run the test, there are few new beans worth discussing:
-
methodTimingAdvice interceptor: This bean will simply be instantiated once and then you'll use the reference to apply with a given pointcut.
-
pointcut: This bean is a simple expression that communicates your desire to attach method timing to a class called StaticAtmDaoImpl with a method called getAllAtmswith no arguments.
-
advisor: This bean simply joins the pointcut and interceptor so they can be applied within the ATM locator service.
Go ahead and run the test case. You should receive the following log output:
INFO AbstractSingleSpringContextTests - Loading context for:
classpath:jbriscoe/article/spring/caching/atmlocator-core-beans.xml,
classpath:jbriscoe/article/spring/caching/atmlocator-atms-beans.xml
INFO StaticAtmDaoImpl - Actually performing all atms lookup.
INFO MethodTimingInterceptor - Method Invocation
[jbriscoe.article.spring.caching.dao.impl.StaticAtmDaoImpl.getAllAtms] Total Time:
10(seconds) 10000(millis)
INFO StaticAtmDaoImpl - Actually performing all atms lookup.
INFO MethodTimingInterceptor - Method Invocation
[jbriscoe.article.spring.caching.dao.impl.StaticAtmDaoImpl.getAllAtms] Total Time:
10(seconds) 10000(millis)
No real surprises with the test results. The getAllAtms method takes 10 seconds each and every time. So it's clear: you need to improve this method's performance!
Keep in mind that this method has been deliberately slowed downthis could very well have been a real call to an external system that took 10 seconds to complete!
New on the Java Boutique:
New Review:
Time Management Made Easy with the Quartz Enterprise Job Scheduler
Why not just use the Java timer API? This open source scheduling
API boasts simplicity, ease-of-integration, a well-rounded feature
set, and it's free!
New Applet:
Reverse Complement
Reverse Complement is a simple applet that converts DNA or RNA
sequences into three useful formats.
Elsewhere on internet.com:
WebDeveloper Java
Lots of Java information on webdeveloper.com
WDVL Java
Thorough Java resource at the Web Developer's Virtual Library.
ScriptSearch Java
Hundreds of free Java code files to download.
jGuru: Your View of the Java Universe
Customizable portal with online training, FAQs, regular news updates, and tutorials.
|