搞懂分佈式技術14:Spring Boot使用註解集成Redis緩存

本文內容參考網絡,侵刪java

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到個人倉庫裏查看git

https://github.com/h2pl/Java-Tutorialgithub

喜歡的話麻煩點下Star哈web

文章首發於個人我的博客:面試

www.how2playlife.comredis

該系列博文會告訴你什麼是分佈式系統,這對後端工程師來講是很重要的一門學問,咱們會逐步瞭解常見的分佈式技術、以及一些較爲常見的分佈式系統概念,同時也須要進一步瞭解zookeeper、分佈式事務、分佈式鎖、負載均衡等技術,以便讓你更完整地瞭解分佈式技術的具體實戰方法,爲真正應用分佈式技術作好準備。spring

若是對本系列文章有什麼建議,或者是有什麼疑問的話,也能夠關注公衆號【Java技術江湖】聯繫做者,歡迎你參與本系列博文的創做和修訂。數據庫

本文轉載自 linkedkeeper.comapache

Spring Boot 熟悉後,集成一個外部擴展是一件很容易的事,集成Redis也很簡單,看下面步驟配置:後端

1、添加pom依賴

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-redis</artifactId>
    </dependency>

2、建立 RedisClient.java
注意該類存放的package

package org.springframework.data.redis.connection.jedis;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.exceptions.JedisException;

/**
 * 工具類 RedisClient
 * 由於本類中獲取JedisPool調用的是JedisConnectionFactory中protected修飾的方法fetchJedisConnector()
 * 因此該類須要與JedisConnectionFactory在同一個package中
 *
 * @author 單紅宇(CSDN CATOOP)
 * @create 2017年4月9日
 */
public class RedisClient {

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

    private JedisConnectionFactory factory;

    public RedisClient(JedisConnectionFactory factory) {
        super();
        this.factory = factory;
    }

    /**
     * put操做(存儲序列化對象)+ 生效時間
     * 
     * @param key
     * @param value
     * @return
     */
    public void putObject(final String key, final Object value, final int cacheSeconds) {
        if (StringUtils.isNotBlank(key)) {
            redisTemplete(key, new RedisExecute<Object>() {
                @Override
                public Object doInvoker(Jedis jedis) {
                    try {
                        jedis.setex(key.getBytes(Protocol.CHARSET), cacheSeconds, serialize(value));
                    } catch (UnsupportedEncodingException e) {
                    }

                    return null;
                }
            });
        }
    }

    /**
     * get操做(獲取序列化對象)
     * 
     * @param key
     * @return
     */
    public Object getObject(final String key) {
        return redisTemplete(key, new RedisExecute<Object>() {
            @Override
            public Object doInvoker(Jedis jedis) {
                try {
                    byte[] byteKey = key.getBytes(Protocol.CHARSET);
                    byte[] byteValue = jedis.get(byteKey);
                    if (byteValue != null) {
                        return deserialize(byteValue);
                    }
                } catch (UnsupportedEncodingException e) {
                    return null;
                }
                return null;
            }
        });
    }

    /**
     * setex操做
     * 
     * @param key
     *            鍵
     * @param value
     *            值
     * @param cacheSeconds
     *            超時時間,0爲不超時
     * @return
     */
    public String set(final String key, final String value, final int cacheSeconds) {
        return redisTemplete(key, new RedisExecute<String>() {
            @Override
            public String doInvoker(Jedis jedis) {
                if (cacheSeconds == 0) {
                    return jedis.set(key, value);
                }
                return jedis.setex(key, cacheSeconds, value);
            }
        });
    }

    /**
     * get操做
     * 
     * @param key
     *            鍵
     * @return 值
     */
    public String get(final String key) {
        return redisTemplete(key, new RedisExecute<String>() {
            @Override
            public String doInvoker(Jedis jedis) {
                String value = jedis.get(key);
                return StringUtils.isNotBlank(value) && !"nil".equalsIgnoreCase(value) ? value : null;
            }
        });
    }

    /**
     * del操做
     * 
     * @param key
     *            鍵
     * @return
     */
    public long del(final String key) {
        return redisTemplete(key, new RedisExecute<Long>() {
            @Override
            public Long doInvoker(Jedis jedis) {
                return jedis.del(key);
            }
        });
    }

    /**
     * 獲取資源
     * 
     * @return
     * @throws JedisException
     */
    public Jedis getResource() throws JedisException {
        Jedis jedis = null;
        try {
            jedis = factory.fetchJedisConnector();
        } catch (JedisException e) {
            logger.error("getResource.", e);
            returnBrokenResource(jedis);
            throw e;
        }
        return jedis;
    }

    /**
     * 獲取資源
     * 
     * @return
     * @throws JedisException
     */
    public Jedis getJedis() throws JedisException {
        return getResource();
    }

    /**
     * 歸還資源
     * 
     * @param jedis
     * @param isBroken
     */
    public void returnBrokenResource(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

    /**
     * 釋放資源
     * 
     * @param jedis
     * @param isBroken
     */
    public void returnResource(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

    /**
     * 操做jedis客戶端模板
     * 
     * @param key
     * @param execute
     * @return
     */
    public <R> R redisTemplete(String key, RedisExecute<R> execute) {
        Jedis jedis = null;
        try {
            jedis = getResource();
            if (jedis == null) {
                return null;
            }

            return execute.doInvoker(jedis);
        } catch (Exception e) {
            logger.error("operator redis api fail,{}", key, e);
        } finally {
            returnResource(jedis);
        }
        return null;
    }

    /**
     * 功能簡述: 對實體Bean進行序列化操做.
     * 
     * @param source
     *            待轉換的實體
     * @return 轉換以後的字節數組
     * @throws Exception
     */
    public static byte[] serialize(Object source) {
        ByteArrayOutputStream byteOut = null;
        ObjectOutputStream ObjOut = null;
        try {
            byteOut = new ByteArrayOutputStream();
            ObjOut = new ObjectOutputStream(byteOut);
            ObjOut.writeObject(source);
            ObjOut.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != ObjOut) {
                    ObjOut.close();
                }
            } catch (IOException e) {
                ObjOut = null;
            }
        }
        return byteOut.toByteArray();
    }

    /**
     * 功能簡述: 將字節數組反序列化爲實體Bean.
     * 
     * @param source
     *            須要進行反序列化的字節數組
     * @return 反序列化後的實體Bean
     * @throws Exception
     */
    public static Object deserialize(byte[] source) {
        ObjectInputStream ObjIn = null;
        Object retVal = null;
        try {
            ByteArrayInputStream byteIn = new ByteArrayInputStream(source);
            ObjIn = new ObjectInputStream(byteIn);
            retVal = ObjIn.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != ObjIn) {
                    ObjIn.close();
                }
            } catch (IOException e) {
                ObjIn = null;
            }
        }
        return retVal;
    }

    interface RedisExecute<T> {
        T doInvoker(Jedis jedis);
    }
}

3、建立Redis配置類
RedisConfig.java

package com.shanhy.example.redis;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.RedisClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置
 * 
 * @author 單紅宇(CSDN catoop)
 * @create 2016年9月12日
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new RedisObjectSerializer());
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public RedisClient redisClient(JedisConnectionFactory factory){
        return new RedisClient(factory);
    }
}

RedisObjectSerializer.java

package com.shanhy.example.redis;

import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

/**
 * 實現對象的序列化接口
 * @author   單紅宇(365384722)
 * @myblog  http://blog.csdn.net/catoop/
 * @create    2017年4月9日
 */
public class RedisObjectSerializer implements RedisSerializer<Object> {

    private Converter<Object, byte[]> serializer = new SerializingConverter();
    private Converter<byte[], Object> deserializer = new DeserializingConverter();

    static final byte[] EMPTY_ARRAY = new byte[0];

    @Override
    public Object deserialize(byte[] bytes) {
        if (isEmpty(bytes)) {
            return null;
        }

        try {
            return deserializer.convert(bytes);
        } catch (Exception ex) {
            throw new SerializationException("Cannot deserialize", ex);
        }
    }

    @Override
    public byte[] serialize(Object object) {
        if (object == null) {
            return EMPTY_ARRAY;
        }

        try {
            return serializer.convert(object);
        } catch (Exception ex) {
            return EMPTY_ARRAY;
        }
    }

    private boolean isEmpty(byte[] data) {
        return (data == null || data.length == 0);
    }

}

4、建立測試方法
下面代碼隨便放一個Controller裏

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
 * 緩存測試
 *
 * @return
 * @author  SHANHY
 * @create  2016年9月12日
 */
@RequestMapping("/redisTest")
public String redisTest() {
    try {
        redisTemplate.opsForValue().set("test-key", "redis測試內容", 2, TimeUnit.SECONDS);// 緩存有效期2秒

        logger.info("從Redis中讀取數據:" + redisTemplate.opsForValue().get("test-key").toString());

        TimeUnit.SECONDS.sleep(3);

        logger.info("等待3秒後嘗試讀取過時的數據:" + redisTemplate.opsForValue().get("test-key"));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    return "OK";
}

5、配置文件配置Redis
application.yml

spring:
  # Redis配置
  redis:
    host: 192.168.1.101
    port: 6379
    password:
    # 鏈接超時時間(毫秒)
    timeout: 10000
    pool:
      max-idle: 20
      min-idle: 5
      max-active: 20
      max-wait: 2

這樣就完成了Redis的配置,能夠正常使用 redisTemplate 了。

atoop/article/details/71275331

1、建立 Caching 配置類

RedisKeys.java

package com.shanhy.example.redis;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.springframework.stereotype.Component;

/**
 * 方法緩存key常量
 * 
 * @author SHANHY
 */
@Component
public class RedisKeys {

    // 測試 begin
    public static final String _CACHE_TEST = "_cache_test";// 緩存key
    public static final Long _CACHE_TEST_SECOND = 20L;// 緩存時間
    // 測試 end

    // 根據key設定具體的緩存時間
    private Map<String, Long> expiresMap = null;

    @PostConstruct
    public void init(){
        expiresMap = new HashMap<>();
        expiresMap.put(_CACHE_TEST, _CACHE_TEST_SECOND);
    }

    public Map<String, Long> getExpiresMap(){
        return this.expiresMap;
    }
}

CachingConfig.java

package com.shanhy.example.redis;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * 註解式環境管理
 * 
 * @author 單紅宇(CSDN catoop)
 * @create 2016年9月12日
 */
@Configuration
@EnableCaching
public class CachingConfig extends CachingConfigurerSupport {

    /**
     * 在使用@Cacheable時,若是不指定key,則使用找個默認的key生成器生成的key
     *
     * @return
     * 
     * @author 單紅宇(CSDN CATOOP)
     * @create 2017年3月11日
     */
    @Override
    public KeyGenerator keyGenerator() {
        return new SimpleKeyGenerator() {

            /**
             * 對參數進行拼接後MD5
             */
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(".").append(method.getName());

                StringBuilder paramsSb = new StringBuilder();
                for (Object param : params) {
                    // 若是不指定,默認生成包含到鍵值中
                    if (param != null) {
                        paramsSb.append(param.toString());
                    }
                }

                if (paramsSb.length() > 0) {
                    sb.append("_").append(paramsSb);
                }
                return sb.toString();
            }

        };

    }

    /**
     * 管理緩存
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate, RedisKeys redisKeys) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        // 設置緩存默認過時時間(全局的)
        rcm.setDefaultExpiration(1800);// 30分鐘

        // 根據key設定具體的緩存時間,key統一放在常量類RedisKeys中
        rcm.setExpires(redisKeys.getExpiresMap());

        List<String> cacheNames = new ArrayList<String>(redisKeys.getExpiresMap().keySet());
        rcm.setCacheNames(cacheNames);

        return rcm;
    }

}

2、建立須要緩存數據的類

TestService.java

package com.shanhy.example.service;

import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.shanhy.example.redis.RedisKeys;

@Service
public class TestService {

    /**
     * 固定key
     *
     * @return
     * @author SHANHY
     * @create  2017年4月9日
     */
    @Cacheable(value = RedisKeys._CACHE_TEST, key = "'" + RedisKeys._CACHE_TEST + "'")
    public String testCache() {
        return RandomStringUtils.randomNumeric(4);
    }

    /**
     * 存儲在Redis中的key自動生成,生成規則詳見CachingConfig.keyGenerator()方法
     *
     * @param str1
     * @param str2
     * @return
     * @author SHANHY
     * @create  2017年4月9日
     */
    @Cacheable(value = RedisKeys._CACHE_TEST)
    public String testCache2(String str1, String str2) {
        return RandomStringUtils.randomNumeric(4);
    }
}

說明一下,其中 @Cacheable 中的 value 值是在 CachingConfig的cacheManager 中配置的,那裏是爲了配置咱們的緩存有效時間。其中 methodKeyGenerator 爲 CachingConfig 中聲明的 KeyGenerator。
另外,Cache 相關的註解還有幾個,你們能夠了解下,不過咱們經常使用的就是 @Cacheable,通常狀況也能夠知足咱們的大部分需求了。還有 @Cacheable 也能夠配置表達式根據咱們傳遞的參數值判斷是否須要緩存。
注: TestService 中 testCache 中的 mapper.get 你們不用關心,這裏面我只是訪問了一下數據庫而已,你只須要在這裏作本身的業務代碼便可。

3、測試方法

下面代碼,隨便放一個 Controller 中

package com.shanhy.example.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.jedis.RedisClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.shanhy.example.service.TestService;

/**
 * 測試Controller
 * 
 * @author 單紅宇(365384722)
 * @myblog http://blog.csdn.net/catoop/
 * @create 2017年4月9日
 */
@RestController
@RequestMapping("/test")
public class TestController {

    private static final Logger LOG = LoggerFactory.getLogger(TestController.class);

    @Autowired
    private RedisClient redisClient;

    @Autowired
    private TestService testService;

    @GetMapping("/redisCache")
    public String redisCache() {
        redisClient.set("shanhy", "hello,shanhy", 100);
        LOG.info("getRedisValue = {}", redisClient.get("shanhy"));
        testService.testCache2("aaa", "bbb");
        return testService.testCache();
    }
}

至此完畢!

最後說一下,這個 @Cacheable 基本是能夠放在全部方法上的,Controller 的方法上也是能夠的(這個我沒有測試 ^_^)。

相關文章
相關標籤/搜索