Pokazywanie postów oznaczonych etykietą cache. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą cache. Pokaż wszystkie posty

niedziela, 29 czerwca 2008

Spring modules cache

Witam ponownie.
Pamiętacie aplikację akwarium? Aplikacja to trochę słowo na wyrost, bo w zasadzie stanowi ją jedna strona, która wylicza ile rybek pluska się w akwarium.

Wszystko działa, ale wyliczanie trochę trwa. Zaraz się tym zajmiemy, korzystając z dodatku do Springa: Spring Modules.
Spring Modules wprowadza między innymi mechanizm pozwalający na zapamiętywanie w pamięci podręcznej wyników wywołań wybranych metod (method cache). W przypadku, gdy któraś z funkcji wykonuje się długo, a dla zadanych parametrów wejściowych jej parametry wyjściowe są zawsze takie same, możemy wyniki jej wykonania przechować w pamięci podręcznej.
Trzeba jednak, trochę uważać. Przy wykorzystaniu mechanizmu method cache, tylko wejście i wyjście danej funkcji się zgadza. Wywołanie funkcji zostaje pominięte, natomiast rezultat jest zwracany z pamięci podręcznej.
Co ciekawe, te wszystkie czary można uruchomić bez napisania jakiejkolwiek linijki kodu w jawie. Trzeba tylko zmienić konfigurację. Ale do rzeczy...
Po uruchomieniu aplikacji akwarium i wpisaniu adresu strony:
http://localhost:8080/aquarium/info.htm
możemy zauważyć, że strona ładuje się kilka sekund. W dodatku zawsze zwraca inny wynik, coś ok 200000.



Odpowiedzialna jest za to klasa

package pl.matt.aquarium.manager.impl;

import pl.matt.aquarium.manager.AquariumManager;

/**
* @author mateusz
*
*/
public class AquariumManagerImpl implements AquariumManager {

/**
* pojemność akwarium w centymetrach sześciennych
*/
private int cubicCentimetres;
/**
* minimalna gęstość rybki
*/
private int minimalDensity;;

@Override
public int getFishCount() {
int count = 0;
for (int index = 0; index < cubicCentimetres; index++) {
if (getDensity(index) > minimalDensity) {
count++;
}
}
return count;
}

/**
* @param centimetreIndex
* @return gęstość wybranego fragmentu akwarium
*/
private int getDensity(int centimetreIndex) {
return (int) (Math.random() * 100);
}

public void setCubicCentimetres(int cubicCentimetres) {
this.cubicCentimetres = cubicCentimetres;
}

public void setMinimalDensity(int minimalDensity) {
this.minimalDensity = minimalDensity;
}

}

a dokładniej jej metoda public int getFishCount().

Pierwsza rzecz, jaką trzeba zrobić, aby dodać Spring Modules do naszego projektu to dodanie nowego repozytorium z którego skorzysta maven2. W tym celu trzeba wprowadzić kilka zmian w pliku settings.xml. Zazwyczaj jest on w katalogu ~/.m2/ lub /etc/maven2/. W sekcji profiles trzeba dopisać nowy profil:

<profiles>
<profile>
<id>standard-extra-repos</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>jboss</id>
<url>http://repository.jboss.com/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>


Następnie dodaję kilka nowych zależności do pliku pom.xml

<dependency>
<groupId>org.springmodules</groupId>
<artifactId>spring-modules-cache</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>apache-oro</groupId>
<artifactId>jakarta-oro</artifactId>
<version>2.0.8</version>
</dependency>


Cały plik pom.xml wygląda następnująco:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.matt</groupId>
<artifactId>aquarium</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>aquarium Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.9</version>
</dependency>
<dependency>
<groupId>org.springmodules</groupId>
<artifactId>spring-modules-cache</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>apache-oro</groupId>
<artifactId>jakarta-oro</artifactId>
<version>2.0.8</version>
</dependency>
</dependencies>
<build>
<finalName>aquarium</finalName>
<directory>/tmp/target</directory>
<outputDirectory>/tmp/target/classes</outputDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>


generalnie nic ciekawego. Czas wziąć na warsztat serce i mózg aplikacji springowej. W moim przypadku jest to plik springapp-servlet.xml.

Dodaję ziarna cacheManager i cacheProviderFacade określając położenie pliku konfiguracyjnego ehcache.xml.

<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml" />
</bean>
<bean id="cacheProviderFacade" class="org.springmodules.cache.provider.ehcache.EhCacheFacade">
<property name="cacheManager" ref="cacheManager" />
</bean>


Ziarno cachingInterceptor pozwala wskazać metody, których wyniki wywołań będą przechowywane w pamięci podręcznej. Wskazuję metody zaczynające się od liter get interfejsu pl.matt.aquarium.manager.AquariumManager. Czyli de facto tylko metodę getFishCount().

<bean id="cachingInterceptor"
class="org.springmodules.cache.interceptor.caching.MethodMapCachingInterceptor">
<property name="cacheProviderFacade" ref="cacheProviderFacade" />
<property name="cachingModels">
<props>
<prop key="pl.matt.aquarium.manager.AquariumManager.get*">cacheName=testCache</prop>
</props>
</property>
</bean>

Wyniki będą przechowywane we fragmencie pamięci nazwanym testCache. Zaraz go zdefiniuję, ale najpierw jeszcze jedno ziarno konfigurujące przechowywanie wyników wywołań metod w pamięci podręcznej:

<bean
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<idref local="aquariumManager" />
</list>
</property>
<property name="interceptorNames">
<list>
<value>cachingInterceptor</value>
</list>
</property>
</bean>


Cały plik springapp-servlet.xml wygląda tak:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!--
the application context definition for the springapp DispatcherServlet
-->
<bean name="/info.htm" class="pl.matt.aquarium.InfoController"
autowire="byName" />

<!-- freemarker config -->
<bean id="freemarkerConfig"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/freemarker/" />
</bean>
<!--
View resolvers can also be configured with ResourceBundles or XML
files. If you need different view resolving based on Locale, you have
to use the resource bundle resolver.
-->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".ftl" />
<!--
if you want to use the Spring FreeMarker macros, set this property to
true
-->
<property name="exposeSpringMacroHelpers" value="true" />
</bean>
<bean id="aquariumManager" class="pl.matt.aquarium.manager.impl.AquariumManagerImpl">
<property name="cubicCentimetres" value="20000000" />
<property name="minimalDensity" value="98" />
</bean>

<!-- cache -->
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml" />
</bean>
<bean id="cacheProviderFacade" class="org.springmodules.cache.provider.ehcache.EhCacheFacade">
<property name="cacheManager" ref="cacheManager" />
</bean>
<bean id="cachingInterceptor"
class="org.springmodules.cache.interceptor.caching.MethodMapCachingInterceptor">
<property name="cacheProviderFacade" ref="cacheProviderFacade" />
<property name="cachingModels">
<props>
<prop key="pl.matt.aquarium.manager.AquariumManager.get*">cacheName=testCache</prop>
</props>
</property>
</bean>
<bean
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<idref local="aquariumManager" />
</list>
</property>
<property name="interceptorNames">
<list>
<value>cachingInterceptor</value>
</list>
</property>
</bean>
</beans>


Pozostaje jeszcze skonfigurować Ehcache, który będzie u mnie zarządzał pamięcią podręczną. Plik ehcache.xml tworzę w katalogu resources.

<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd">
<diskStore path="java.io.tmpdir/cache" />

<defaultCache
maxElementsInMemory="9999"
eternal="false"
timeToIdleSeconds="777"
timeToLiveSeconds="777"
overflowToDisk="true"
diskSpoolBufferSizeMB="30"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="600"
memoryStoreEvictionPolicy="LRU"
/>

<cache name="testCache"
maxElementsInMemory="9999"
eternal="false"
timeToIdleSeconds="777"
timeToLiveSeconds="777"
overflowToDisk="true"
diskSpoolBufferSizeMB="30"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="600"
memoryStoreEvictionPolicy="LRU"
/>

</ehcache>


Plik zawiera konfigurację dwóch fragmentów pamięci podręcznej. Nie wchodząc w szczegóły, konfiguracja fragmentu defaultCache jest obowiązkowa. Natomiast testCache będzie pamiętał wywołania metody getFishCount().
I to tyle. Czas sprawdzić, czy działa. Buduję aplikację, wchodzę na stronę:
http://localhost:8080/aquarium/info.htm
Ładuje się długo. Wchodzę ponownie. Tym razem strona załadowała się błyskawicznie. W dodatku liczba rybek pozostała niezmieniona. Miodzio - i to wszystko bez ani jednej linijki kodu w jawie. Całą aplikację akwarium znajdziesz tutaj.

Oczywiście do konfiguracji mechanizmu method cache można wykorzystać adnotacje, a zamiast Ehcache, JBoss Cache, Java Caching System (JCS) albo OSCache. Takie uroki Springa.