Using the Spring AOP Framework with EJB Components
Pages: 1, 2, 3, 4, 5

Advising Components Managed by Spring

In the previous section, you refactored the service entry point to use a Spring-managed bean. Now I'll show you how that could help you improve your component and implement new features.

First of all, imagine that users would like to see the prices for symbols that are not managed by your TradeManager component. In other words, you would need to hook up to an external service for retrieving the current market price for requested symbols that you don't currently handle. As an example, you can use a free HTTP-based service from Yahoo portal, though a real application would hook up to the data feed from vendors that serve real-time data, such as Reuters, Thomson, Bloomberg, NAQ, and a number of others.

Begin by creating a new YahooFeed component that implements the same TradeManager interface, and that pulls the price information from the Yahoo finance portal. A naive implementation can send an HTTP request using HttpURLConnection and then parse the response using regular expressions. For example:

public class YahooFeed implements TradeManager {
  private static final String SERVICE_URL = "http://finance.yahoo.com/d/quotes.csv?f=k1&s=";

  private Pattern pattern = Pattern.compile("\"(.*) -  
                        
(.*)\"");
  
  public BigDecimal getPrice(String symbol) {
    HttpURLConnection conn;
    String responseMessage;
    int responseCode;
    try {
      URL serviceUrl = new URL(SERVICE_URL+symbol);
      conn = (HttpURLConnection) serviceUrl.openConnection();
      responseCode = conn.getResponseCode();
      responseMessage = conn.getResponseMessage();
    } catch(Exception ex) {
      throw new RuntimeException("Connection error", ex);
    }
    
    if(responseCode!=HttpURLConnection.HTTP_OK) {
      throw new RuntimeException("Connection error "+responseCode+" "+responseMessage);
    }
      
    String response = readResponse(conn);
    Matcher matcher = pattern.matcher(response);
    if(!matcher.find()) {
      throw new RuntimeException("Unable to parse response ["+response+"] for symbol "+symbol);
    }
    String time = matcher.group(1);
    if("N/A".equals(time)) {
      return null;  // unknown symbol
    }
    String price = matcher.group(2);
    return new BigDecimal(price);
  }

  public void setPrice(String symbol, BigDecimal price) {
    throw new UnsupportedOperationException("Can't set price of 3rd party trade");
  }

  private String readResponse(HttpURLConnection conn) {
    // ...
    return response;
  }

}
                      

After this implementation is finished and tested (outside of container!), you can integrate it with other components. Traditionally, one would add some code to the TradeManager2Impl to check the value returned from the getPrice() method. This would at least double the number of tests and will require you to set additional preconditions for each test case. However, using the Spring AOP framework, you can do this in a nice way. You can implement an advice that will use the YahooFeed component to retrieve the price if the original TradeManager returns no value for the requested symbol (in this case it is just null, but you may as well catch a UnknownSymbol exception).

To apply advice to a concrete method you'll need to declare an Advisor in Spring's bean configuration. A convenient NameMatchMethodPointcutAdvisor class allows you to select methods by their names, and in this case, you need a getPrice:

  <bean id="yahooFeed" class="org.javatx.spring.aop.YahooFeed"/>

  <bean id="foreignTradeAdvisor" 
        class="
                        
org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
    <property name="mappedName" value="
                        
getPrice"/>
    <property name="advice">
       
                        
<bean class="org.javatx.spring.aop.ForeignTradeAdvice">
        <constructor-arg index="0" ref="yahooFeed"/>
      </bean>
    </property>
  </bean>
                      

As you can see, the above advisor assigns a ForeignTradeAdvice to the getPrice() method. The Spring AOP framework uses the AOP Alliance API for advice classes, which means that your ForeignTradeAdvice around advice should implement the MethodInterceptor interface. For example:

public class ForeignTradeAdvice implements  
                        
MethodInterceptor {
  private TradeManager tradeManager;
  
  public ForeignTradeAdvice(TradeManager manager) {
    this.tradeManager = manager;
  }
  
  public Object invoke(MethodInvocation invocation) throws Throwable {
    Object res = invocation.proceed();
    if(res!=null) return res;

     
                        
Object[] args = invocation.getArguments();
    String symbol = (String) args[0];
    return tradeManager.getPrice(symbol);
  }

}
                      

The above code invokes an original component using invocation.proceed(), and if it returns null, it will call another tradeManager injected as a constructor parameter on advice creation. See the declaration of the foreignTradeAdvisor bean above.

Now you can rename the tradeManager bean defined in Spring's bean configuration to the baseTradeManager and declare tradeManager as a proxy using ProxyFactoryBean. The new baseTradeManager will be a target you will advise with the foreignTradeAdvisor defined above:

  <bean id="
                        
baseTradeManager" class="org.javatx.spring.aop.TradeDao">
     
                        
... same as tradeManager definition in the above example
  </bean>

  <bean id="tradeManager" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="org.javatx.spring.aop.TradeManager"/>
    <property name="target" ref="
                        
baseTradeManager"/>
    <property name="interceptorNames">
      <list>
        <idref local="
                        
foreignTradeAdvisor"/>
      </list>
    </property>
  </bean>
                      

Basically, this is it. You implemented additional functionality without changing the original component, using only the Spring application context to reconfigure dependencies. To implement similar change in a classic EJB component without the Spring AOP framework, you would have to either add additional logic to the EJB (making it more difficult to test) or use a decorator pattern (effectively increasing the number of EJBs, which will also increase complexity of the tests, as well as increase deployment time). You can see in the above example that with Spring it became very easy to attach additional logic to the existing components without changing them. Instead of tightly coupled beans you now have several lightweight components that can be tested in isolation and assembled using the Spring Framework. Note that with this approach your ForeignTradeAdvice is a self-contained component, which implements its own piece of functionality and which can be tested as a standalone unit outside of an application server as I'll show you in the next section.

Pages: 1, 2, 3, 4, 5

Next Page ยป