Today I’ve integrated Hazelcast with the @Cacheable spring annotation. I’ve chosen Hazelcast over EhCache + Terracotta, because it has a simpler configuration and there’s no need to run another deamon, which facilitates dev-environment setup.
Here is my hazelcast spring configuration, that is included in my main spring configuration:
<?xmlversion="1.0"encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:hz="http://www.hazelcast.com/schema/spring" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.hazelcast.com/schema/springhttp://www.hazelcast.com/schema/spring/hazelcast-spring-2.1.xsd http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/cachehttp://www.springframework.org/schema/cache/spring-cache-3.1.xsd "> <cache:annotation-driven cache-manager="cacheManager" mode="proxy" proxy-target-class="true" /> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" p:systemPropertiesModeName="SYSTEM_PROPERTIES_MODE_OVERRIDE"> <property name="locations"> <list> <value>classpath:/hazelcast-default.properties</value> </list> </property> </bean> <hz:hazelcast id="instance"> <hz:config> <hz:group name="mygroup" password="mypassword" /> <hz:network port="5700" port-auto-increment="false"> <hz:join> <hz:multicast enabled="true" /> <hz:tcp-ip enabled="true"> <hz:interface>127.0.0.1:5700</hz:interface> </hz:tcp-ip> </hz:join> <hz:interfaces enabled="true"> <hz:interface>127.0.0.1</hz:interface> </hz:interfaces> </hz:network> <hz:map name="default"> <hz:map-store enabled="true" write-delay-seconds="0" class-name="com.mufumbo.server.cache.hazelcast.EmptyCacheMapLoader" /> </hz:map> <hz:map name="null-map" /> <hz:map name="app" backup-count="3" async-backup-count="1" time-to-live-seconds="10" max-size="100" eviction-percentage="50" cache-value="true" eviction-policy="LRU" merge-policy="hz.LATEST_UPDATE" /> </hz:config> </hz:hazelcast> <hz:config id="liteConfig"> <hz:lite-member>true</hz:lite-member> </hz:config> <!-- set hazelcast spring cache manager --> <bean id="cacheManager" class="com.hazelcast.spring.cache.HazelcastCacheManager"> <constructor-arg ref="instance" /> </bean> </beans>
Please, notice the mode=”proxy” proxy-target-class=”true”. Without that configuration the beans with a super constructor and @Cacheable haven’t loaded. Notice that this isn’t a Hazelcast issue, it’s a Spring AOP issue, even if you use the SimpleCacheManager instead of Hazelcast one.
I was wondering about using mode=”aspectj”, but it was taking too much time, so maybe another day.
ATTENTION: CGLib proxies requires that the class needs to provide a default constructor, i.e. without any arguments. Otherwise you’ll get an IllegalArgumentException: “Superclass has no null constructors but no arguments were given.” This makes constructor injection impossible.
ATTENTION 2: Be careful if you have a BeanNameAutoProxyCreator matching the class that you flag as @Cacheable. In that case it means that there’s already an Cglib proxy behind, which can’t happen. It’s a hassle because I was using a BeanNameAutoProxyCreator to match all my *Service classes in order to create the JDO transactions on the methods create*, update* and save*. If you had the same problem, replace all your BeanNameAutoProxyCreator configuration with an AOP configuration like:
<tx:annotation-driven transaction-manager="transactionManager" mode="proxy" proxy-target-class="true" /> <aop:config> <!-- http://blog.espenberntsen.net/tag/pointcut/ --> <!-- For all the classes annotated with @Service --> <aop:pointcut id="serviceMethodsCut" expression="within(@org.springframework.stereotype.Service *)" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethodsCut" /> </aop:config> <aop:config> <!-- For all the methods annotated with @Transactional --> <aop:pointcut id="transactionalCut" expression="execution(@org.springframework.transaction.annotation.Transactional * *(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionalCut" /> </aop:config> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="update*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> <tx:method name="insert*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> <tx:method name="create*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> <tx:method name="delete*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> <tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> <tx:method name="store*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/> <tx:method name="get*" propagation="REQUIRED" read-only="true" /> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice>
Also, remember to update your pom.xml with the AspectJ configuration:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${org.springframework-version}</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.5.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.12</version> </dependency> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.4</version> <configuration> <Xlint>warning</Xlint> <complianceLevel>1.7</complianceLevel> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> <aspectLibraries> <aspectLibrary> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </aspectLibrary> </aspectLibraries> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> </plugin>



