Статьи

Кэширование с помощью Spring Data Redis

В приведенном ниже примере я покажу вам, как использовать проект Spring Data — Redis в качестве поставщика кэширования для Spring Cache Abstraction, представленной в Spring 3.1. У меня много вопросов о том, как использовать конфигурацию Spring на Java, поэтому я предоставлю для вашего обзора конфигурации на основе XML и Java.

зависимости

В этом примере были использованы следующие зависимости:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version='1.0' encoding='UTF-8'?>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.joshuawhite.example</groupId>
    <artifactId>spring-redis-example</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <name>Spring Redis Example</name>
    <dependencies>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.0.2.RELEASE</version>
        </dependency>       
        <!-- required for @Configuration annotation -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.0.0</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.14</version>
        </dependency>
    </dependencies>
    <build>
        <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>

Код и конфигурация

HelloService ниже очень прост. Как вы увидите в реализации, он просто возвращает строку с «Hello», добавленной перед именем, которое передается.

1
2
3
4
5
6
7
package com.joshuawhite.example.service;
 
public interface HelloService {
 
    String getMessage(String name);
 
}

Глядя на класс HelloServiceImpl (ниже), вы видите, что я использую аннотацию Spring @Cacheable для добавления возможностей кэширования в метод getMessage . Для получения более подробной информации о возможностях этой аннотации ознакомьтесь с документацией Cache Abstraction . Для развлечения я использую язык выражений Spring (SpEL) для определения условия. В этом примере ответ методов будет кэшироваться только в том случае, если передано имя «Иисус Навин».

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package com.joshuawhite.example.service;
 
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
 
@Service('helloService')
public class HelloServiceImpl implements HelloService {
 
    /**
     * Using SpEL for conditional caching - only cache method executions when
     * the name is equal to 'Joshua'
     */
    @Cacheable(value='messageCache', condition=''Joshua'.equals(#name)')
    public String getMessage(String name) {
        System.out.println('Executing HelloServiceImpl' +
                        '.getHelloMessage(\'' + name + '\')');
 
        return 'Hello ' + name + '!';
    }
 
}

Класс App ниже содержит наш метод main и используется для выбора конфигураций на основе XML и Java. Каждый из System.out.println используется для демонстрации того, когда происходит кэширование. Напоминаем, что мы ожидаем, что только кэширование выполнений методов, переданных в «Joshua». Это будет более ясно, когда мы посмотрим на вывод программ позже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.joshuawhite.example;
 
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
 
import com.joshuawhite.example.config.AppConfig;
import com.joshuawhite.example.service.HelloService;
 
public class App {
 
    public static void main(String[] args) {
 
        boolean useJavaConfig  = true;
        ApplicationContext ctx = null;
 
        //Showing examples of both Xml and Java based configuration
        if (useJavaConfig ) {
                ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        }
        else {
                ctx = new GenericXmlApplicationContext('/META-INF/spring/app-context.xml');
        }
 
        HelloService helloService = ctx.getBean('helloService', HelloService.class);
 
        //First method execution using key='Josh', not cached
        System.out.println('message: ' + helloService.getMessage('Josh'));
 
        //Second method execution using key='Josh', still not cached
        System.out.println('message: ' + helloService.getMessage('Josh'));
 
        //First method execution using key='Joshua', not cached
        System.out.println('message: ' + helloService.getMessage('Joshua'));
 
        //Second method execution using key='Joshua', cached
        System.out.println('message: ' + helloService.getMessage('Joshua'));
 
        System.out.println('Done.');
    }
 
}

Обратите внимание, что компонентное сканирование все еще используется при использовании конфигурации на основе XML. Вы можете видеть, что я использую аннотацию @Service в строке 6 HelloServiceImpl.java выше. Далее мы рассмотрим, как настроить jedisConnectionFactory , redisTemplate и cacheManager .

Конфигурирование JedisConnectionFactory

В этом примере я выбрал Jedis в качестве нашего предпочтительного клиента Java, поскольку он указан на сайте Redis как «рекомендуемая» клиентская библиотека для Java. Как видите, настройка очень проста. Хотя я явно устанавливаю use-pool = true, исходный код указывает, что это значение по умолчанию. JedisConnectionFactory также предоставляет следующие значения по умолчанию, если они не установлены явно:

  • Hostname =»локальный»
  • Порт = 6379
  • время ожидания = 2000 мс
  • базы данных = 0
  • usePool = TRUE

Примечание. Хотя индекс базы данных настраивается, JedisConnectionFactory поддерживает подключение только к одной базе данных Redis одновременно. Поскольку Redis является однопоточным, рекомендуется настроить несколько экземпляров Redis вместо использования нескольких баз данных в одном процессе. Это позволяет вам лучше использовать процессор / ресурсы. Если вы планируете использовать redis-cluster, поддерживается только одна база данных. Для получения дополнительной информации о значениях по умолчанию, используемых в пуле соединений, посмотрите на реализацию JedisPoolConfig или JedisPoolConfig Apache Commons Pool org.apache.commons.pool.impl.GenericObjectPool.Config и его приложения. org.apache.commons.pool.impl.GenericObjectPool класс.

Настройка RedisTemplate

Как и следовало ожидать от класса «template» Spring, RedisTemplate заботится о сериализации и управлении соединениями и (при условии, что вы используете пул соединений) является поточно-ориентированным. По умолчанию RedisTemplate использует сериализацию Java ( JdkSerializationRedisSerializer ). Обратите внимание, что сериализация данных в Redis делает Redis «непрозрачным» кешем. В то время как другие сериализаторы позволяют отображать данные в Redis, я обнаружил, что сериализация, особенно при работе с графами объектов, быстрее и проще в использовании. При этом, если у вас есть требование, чтобы другие не Java-приложения могли иметь доступ к этим данным, сопоставление — ваш лучший из готовых вариантов. У меня был большой опыт использования Hessian и Google Protocol Buffers / protostuff . Я поделюсь некоторыми RedisSerializer реализации RedisSerializer в следующем посте.

Настройка RedisCacheManager

Настроить RedisCacheManager просто. Напоминаем, что RedisCacheManager зависит от RedisTemplate который зависит от фабрики соединений, в нашем случае JedisConnectionFactory , которая может одновременно подключаться только к одной базе данных. В качестве обходного пути RedisCacheManager имеет возможность установить префикс для ваших ключей кеша.

Предупреждение. При работе с другими решениями для кэширования Spring CacheManger обычно содержит карту реализаций Cache (каждая реализует карту, аналогичную функциональности), которые поддерживаются отдельными кэшами. При RedisCacheManager конфигурации RedisCacheManager по умолчанию это не так. Основываясь на комментарии RedisCacheManager к RedisCacheManager , неясно, является ли это ошибкой или просто неполной документацией.

«… По умолчанию ключи сохраняются путем добавления префикса (который действует как пространство имен)».

Хотя DefaultRedisCachePrefix, настроенный в RedisCacheManager безусловно, поддерживает это, он не включен по умолчанию. В результате, когда вы запрашиваете в RedisCacheManager Cache с заданным именем, он просто создает новый экземпляр Cache который указывает на ту же базу данных. В результате экземпляры Cache все одинаковы. Один и тот же ключ будет получать одинаковое значение во всех экземплярах Cache .

Как упоминается в комментарии javadoc, префиксы можно использовать для настройки управляемых клиентом (Redis не поддерживает эту функцию изначально) пространств имен, которые по существу создают «виртуальные» кэши в одной и той же базе данных. Вы можете включить эту функцию, вызвав redisCacheManager.setUsePrefix(true) используя конфигурацию Spring XML или Java.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version='1.0' encoding='UTF-8'?>
<beans
    xsi:schemaLocation='
 
 
 
    <context:component-scan base-package='com.joshuawhite.example.service' />
    <context:property-placeholder location='classpath:/redis.properties'/>
 
    <!-- turn on declarative caching -->
    <cache:annotation-driven />
 
    <!-- Jedis ConnectionFactory -->
    <bean
        id='jedisConnectionFactory'
        class='org.springframework.data.redis.connection.jedis.JedisConnectionFactory'
        p:host-name='${redis.host-name}'
        p:port='${redis.port}'
        p:use-pool='true'/>
 
    <!-- redis template definition -->
    <bean
        id='redisTemplate'
        class='org.springframework.data.redis.core.RedisTemplate'
        p:connection-factory-ref='jedisConnectionFactory'/>
 
    <!-- declare Redis Cache Manager -->
    <bean
        id='cacheManager'
        class='org.springframework.data.redis.cache.RedisCacheManager'
        c:template-ref='redisTemplate'/>
 
</beans>

Конфигурация Java ниже эквивалентна конфигурации XML выше. Люди обычно зацикливаются на использовании PropertySourcesPlaceholderConfigurer . Для этого необходимо использовать аннотацию @PropertySource и определить bean-компонент PropertySourcesPlaceholderConfigurer . PropertySourcesPlaceholderConfigurer не будет достаточно сам по себе.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.joshuawhite.example.config;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
 
@Configuration
@ComponentScan('com.joshuawhite.example')
@PropertySource('classpath:/redis.properties')
public class AppConfig {
 
 private @Value('${redis.host-name}') String redisHostName;
 private @Value('${redis.port}') int redisPort;
 
 @Bean
 public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
     return new PropertySourcesPlaceholderConfigurer();
 }
 
 @Bean
 JedisConnectionFactory jedisConnectionFactory() {
     JedisConnectionFactory factory = new JedisConnectionFactory();
     factory.setHostName(redisHostName);
     factory.setPort(redisPort);
     factory.setUsePool(true);
     return factory;
 }
 
 @Bean
 RedisTemplate<Object, Object> redisTemplate() {
     RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
     redisTemplate.setConnectionFactory(jedisConnectionFactory());
     return redisTemplate;
 }
 
 @Bean
 CacheManager cacheManager() {
     return new RedisCacheManager(redisTemplate());
 }
 
}

Вот файл свойств, который используется обеими конфигурациями. Замените приведенные ниже значения хостом и портом, которые вы используете.

1
2
redis.host-name=yourHostNameHere
redis.port=6379

Выход

Наконец, вот вывод из нашего краткого примера приложения. Обратите внимание, что независимо от того, сколько раз мы вызываем getHelloMessage('Josh') , ответ методов не кэшируется. Это потому, что мы определили условие (см. HelloServiceImpl.java , строка 13), в котором мы HelloServiceImpl.java ответ методов только тогда, когда имя равно «Джошуа». Когда мы вызываем getHelloMessage('Joshua') в первый раз, метод выполняется. Второй раз, однако, это не так.

1
2
3
4
5
6
7
8
9
Executing HelloServiceImpl.getHelloMessage('Josh')
message: Hello Josh!
Executing HelloServiceImpl.getHelloMessage('Josh')
message: Hello Josh!
Executing HelloServiceImpl.getHelloMessage('Joshua')
message: Hello Joshua!
Executing HelloServiceImpl.getHelloMessage('Joshua')
message: Hello Joshua!
Done.

На этом мы завершаем краткий обзор кэширования с помощью Spring Data Redis.

Ссылка: Кэширование с помощью Spring Data Redis от нашего партнера JCG Джошуа Уайта в блоге