Monday, January 3, 2011

JSF beans getter with Spring AOP

Introduction

JSF can call a getter more than once per request.

For example in the following JSF file :

<html>
<body>
<h:dataTable var="car" value="#{carService.cars}">
<h:column>
<f:facet name="header">
Model
</f:facet>
<h:outputText value="#{car.model}"/>
</h:column>

<h:column>
<f:facet name="header">
Year
</f:facet>
<h:outputText value="#{car.year}"/>
</h:column>

<h:column>
<f:facet name="header">
Manufacturer
</f:facet>
<h:outputText value="#{car.manufacturer}"/>
</h:column>
</h:dataTable>
</body>
</html>

JSF will call the carService.cars getter more than once inside the same request.

In this example, JSF calls it three times :

13:39:40 DEBUG : CarService.getCars:17 - qtp17489534-18

13:39:40 DEBUG : CarService.getCars:17 - qtp17489534-18

13:39:40 DEBUG : CarService.getCars:17 - qtp17489534-18

A getter obviously returns a value. This value can be a bean property or a calculated value.

If the value is calculated, this can potentially be a problem.

I googled a bit and found some workarounds.

  1. The first one was to include a check and see if it has already been calculated.
  2. The second one was to use the getter only to access bean properties
  3. The third one was to use Spring AOP and cache the value.


For me, the last workaround is the best one because you do not have to modify the view.

And thanks to Spring, it's quite simple to add AOP with a few annotations.


The workaround

First we create the annotation Cacheable :

@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
}


This annotation will be used to cache the result of a method.

Then we create the annotation matching pointcut :

<bean class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut" id="cachePointCut">
<constructor-arg index="0"><null/></constructor-arg>
<constructor-arg index="1" value="org.nigaju.cache.annotation.Cacheable" />
</bean>


And the method interceptor :

<bean id="cacheInterceptorAdvice" class="org.nigaju.cache.CacheAdvice"/>
With the associated class :
public class CacheAdvice implements MethodInterceptor {

private static Logger logger = LoggerFactory.getLogger(CacheAdvice.class);

@Autowired
private CacheService cacheService;

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {

String key = methodInvocation.getThis() + methodInvocation.getMethod().getName();

String thread = Thread.currentThread().getName();

Object cachedValue = cacheService.getData(thread , key);

if (cachedValue == null){
cachedValue = methodInvocation.proceed();
cacheService.cacheData(thread , key , cachedValue);
logger.debug("Cache miss " + thread + " " + key);
}
else{
logger.debug("Cached hit " + thread + " " + key);
}
return cachedValue;
}


public CacheService getCacheService() {
return cacheService;
}
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
}

The key generator is quite simple (do not use it). The CacheService class embed a ConcurrentHashMap.

And finally the pointcut advisor :

<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<ref bean="cachePointCut"/>
</property>
<property name="advice">
<ref bean="cacheInterceptorAdvice"/>
</property>
</bean>

The cache has to be flushed after each request. For that, I use a JSF lifecycle listener that flush the cache for the current request after the RENDER_RESPONSE phase.

Easy! I have attached a small Maven war project that contains the code of this article.

What is your opinion ?

No comments:

Post a Comment