本项目目标是针对cn.dyaoming.common:common-cache包进行使用展示。
项目地址:https://github.com/dym270307872/demo-parent/tree/master/demo-common-cache
1、首先准备一个springmvc的项目作为基础框架。
由于项目目的比较简单,所有我选用的项目没有连接数据库的逻辑,使用了随机数逻辑模拟数据操作。随机数每次都不一样,等同于数据库数据变化。
随机数有一个参数num是随机数的最大值[或者理解成随机范围],相当于数据查询的参数。
2、引入jar包
<dependency>
<groupId>cn.dyaoming.common</groupId>
<artifactId>common-cache</artifactId>
<version>0.0.1</version>
</dependency>
3、编写CacheInterface实现类RedisTemplateImp。
package cn.dyaoming.demo.dao;
import java.util.*;
import cn.dyaoming.cache.interfaces.CacheInterface;
import cn.dyaoming.errors.AppDaoException;
import cn.dyaoming.utils.AesUtil;
import cn.dyaoming.utils.SerializeUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
// @Component("cacheDao") TODO为了便于理解,此处单独配置到spring文件中,使用自动注解效果一致
/**
* <p>
* 使用redis的实现类
* </p>
*
* @author DYAOMING
* @since 2019-05-15
* @version V1.0
*/
public class RedisTemplateImp implements CacheInterface {
private static final Logger LOGGER = LogManager.getLogger(RedisTemplateImp.class);
@Autowired
private RedisTemplate redisTemplate;
public RedisTemplate getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 功能描述:判断是否存在键值。
*
* @param key String类型 键
* @return boolean类型 返回结果
*/
@Override
public boolean exists(Object key) throws AppDaoException {
boolean rv = false;
try {
if (!StringUtils.isEmpty(key)) {
final byte[] finalKey = key.toString().getBytes("utf-8");
Object obj = redisTemplate.execute(new RedisCallback<Boolean>(){
@Override
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
return connection.exists(finalKey);
}
});
rv = (Boolean) obj;
}
} catch(Exception e) {
LOGGER.error("异常:exists()方法出现异常,异常详细信息:" + e.getMessage() + "。");
throw new AppDaoException("判断缓存内容是否存在异常!", e);
}
return rv;
}
/**
* 功能描述:设置缓存对象类型内容。
*
* @param key String类型 键
* @param value Object类型 内容
* @return boolean类型 返回结果
*/
@Override
public boolean setCacheObjectData(Object key, Object value) throws AppDaoException {
return this.setCacheObjectData(key, value, DEFALUTTIME, DEFALUTSECRET);
}
/**
* 功能描述:设置缓存对象类型内容。
*
* @param key String类型 键
* @param value Object类型 内容
* @param validTime long类型 有效时间(单位:秒)
* @return boolean类型 返回结果
*/
@Override
public boolean setCacheObjectData(Object key, Object value, final long validTime)
throws AppDaoException {
return this.setCacheObjectData(key, value, validTime, DEFALUTSECRET);
}
@Override
public boolean setCacheObjectData(Object key, Object value, boolean secret)
throws AppDaoException {
return this.setCacheObjectData(key, value, DEFALUTTIME, secret);
}
@Override
public boolean setCacheObjectData(Object key, Object value, long validTime,
boolean secret) throws AppDaoException {
boolean rv = false;
try {
if (!StringUtils.isEmpty(key)) {
final byte[] finalKey = key.toString().getBytes("utf-8");
byte[] valueByte = SerializeUtil.serialize(value);
if (secret) {
valueByte = AesUtil.encrypt(valueByte);
int length_byte = DEFALUTHEAD.length + valueByte.length;
byte[] all_byte = new byte[length_byte];
System.arraycopy(DEFALUTHEAD, 0, all_byte, 0,
DEFALUTHEAD.length);
System.arraycopy(valueByte, 0, all_byte, DEFALUTHEAD.length,
valueByte.length);
valueByte = all_byte;
}
final byte[] finalValue = valueByte;
redisTemplate.execute(new RedisCallback<Boolean>(){
@Override
public Boolean doInRedis(RedisConnection connection) {
connection.set(finalKey, finalValue);
// 设置超时间
if (validTime > 0L) {
connection.expire(finalKey, validTime);
}
return true;
}
});
}
} catch(Exception e) {
LOGGER.error("异常:setCacheObjectData()方法出现异常,异常详细信息:" + e.getMessage() + "。");
throw new AppDaoException("缓存对象类型内容出现异常!", e);
}
return rv;
}
/**
* 功能描述:删除缓存内容。
*
* @param key String类型 键
* @return boolean类型 返回结果
*/
@Override
public boolean deleteCacheData(Object key) throws AppDaoException {
boolean rv = false;
try {
if (!StringUtils.isEmpty(key)) {
final byte[] finalKey = key.toString().getBytes("utf-8");
redisTemplate.execute(new RedisCallback<Long>(){
@Override
public Long doInRedis(RedisConnection connection)
throws DataAccessException {
return connection.del(finalKey);
}
});
rv = true;
}
} catch(Exception e) {
LOGGER.error("异常:deleteCacheData()方法出现异常,异常详细信息:" + e.getMessage() + "。");
throw new AppDaoException("删除缓存内容出现异常!", e);
}
return rv;
}
/**
* 功能描述:获取缓存内容。
*
* @param key String类型 键
* @return Object类型 返回结果
*/
@Override
public Object getCacheData(Object key) throws AppDaoException {
Object rv = null;
try {
if (!StringUtils.isEmpty(key)) {
final byte[] finalKey = key.toString().getBytes("utf-8");
final Object object = redisTemplate.execute(new RedisCallback<Object>(){
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
byte[] value = connection.get(finalKey);
if (value == null){
return null;
}
byte[] head = new byte[DEFALUTHEAD.length];
System.arraycopy(value, 0, head, 0, DEFALUTHEAD.length);
if (Arrays.equals(head, DEFALUTHEAD)) {
byte[] body = new byte[value.length - DEFALUTHEAD.length];
System.arraycopy(value, DEFALUTHEAD.length, body, 0,
value.length - DEFALUTHEAD.length);
body = AesUtil.decrypt(body);
return SerializeUtil.unSerialize(body);
}
return SerializeUtil.unSerialize(value);
}
});
rv = object;
}
} catch(Exception e) {
LOGGER.error("异常:getCacheData()方法出现异常,异常详细信息:" + e.getMessage() + "。");
throw new AppDaoException("获取缓存内容出现异常!", e);
}
return rv;
}
/**
* 功能描述:获取缓存内容。
*
* @param key String类型 键
* @param type Class<T>类型 内容类型
* @return T类型 返回结果
*/
@Override
public <T> T getCacheTData(String key, Class<T> type) throws AppDaoException {
if (StringUtils.isEmpty(key) || null == type) {
return null;
} else {
final String finalKey;
final Class<T> finalType = type;
if (key instanceof String) {
finalKey = key;
} else {
finalKey = key.toString();
}
final Object object = redisTemplate.execute(new RedisCallback<Object>(){
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
byte[] key = (finalKey).getBytes();
byte[] value = connection.get(key);
if (value == null){
return null;
}
return SerializeUtil.unSerialize(value);
}
});
if (finalType != null && finalType.isInstance(object) && null != object) {
return (T) object;
} else {
return null;
}
}
}
/**
* 描述:清空缓存
*
*/
@Override
public void clear() throws AppDaoException {
redisTemplate.discard();
}
}
4、增加配置文件
增加配置文件redis-config.properties
#redis\u670D\u52A1\u5730\u5740
redis.host=127.0.0.1
#redis\u7AEF\u53E3\u53F7
redis.port=6379
#\u5176\u4ED6\u9644\u5C5E\u53C2\u6570
redis.pool.maxTotal=2048
redis.pool.maxActive=2048
redis.pool.maxIdle=256
redis.pool.minIdle=128
redis.pool.maxWait=10000
redis.pool.timeout=30000
redis.pool.maxWaitMillis=10000
redis.pool.testOnBorrow=true
redis.pool.testOnReturn=true
redis.pool.timeBetweenEvictionRunsMillis=30000
redis.pool.testWhileIdle=true
redis.pool.numTestsPerEvictionRun=50
spring-cache.xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.2.xsd">
<cache:annotation-driven key-generator="cacheKeyGenerator"/>
<bean id="cacheKeyGenerator" class="cn.dyaoming.cache.CacheKeyGenerator"/>
<!-- 缓存管理器 -->
<bean id="cacheManager" class="cn.dyaoming.cache.CacheManager">
<property name="timeout" value="60" />
<property name="caches">
<set>
<bean class="cn.dyaoming.cache.SystemCache">
<property name="name" value="publicInfo" />
<property name="timeout" value="6000" />
<property name="cacheDao" ref="cacheDao" />
</bean>
<bean class="cn.dyaoming.cache.SystemCache">
<property name="name" value="userInfo" />
<property name="timeout" value="6000" />
<property name="secret" value="true" />
<property name="cacheDao" ref="cacheDao" />
</bean>
<bean class="cn.dyaoming.cache.SystemCache">
<property name="name" value="businessInfo" />
<property name="timeout" value="6000" />
<property name="secret" value="true" />
<property name="cacheDao" ref="cacheDao" />
</bean>
</set>
</property>
</bean>
</beans>
此配置中,publicinfo、userinfo、businessinfo是自定义命名空间,可以随意添加或删除。timeout是当前命名空间缓存时长。timeout输入0,表示不缓存,-1表示永久缓存。
secret是加密标识,如果为true,则系统缓存时自动进行加密。
cacheDao是实现类,可以参考下面的配置文件。
spring-redis.xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- redis Spring 配置(YCL)-->
<!-- jedis 连接池配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大活动对象数 -->
<property name="maxTotal" value="${redis.pool.maxTotal}"/>
<!-- 最大能够保持idel状态的对象数 -->
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<!-- 最小能够保持idel状态的对象数 -->
<property name="minIdle" value="${redis.pool.minIdle}"/>
<!-- 当池内没有返回对象时,最大等待时间 -->
<property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}"/>
<!-- 当调用borrow Object方法时,是否进行有效性检查 -->
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
<!-- 当调用return Object方法时,是否进行有效性检查 -->
<property name="testOnReturn" value="${redis.pool.testOnReturn}" />
<!-- 向调用者输出“链接”对象时,是否检测它的空闲超时; -->
<property name="testWhileIdle" value="${redis.pool.testWhileIdle}" />
<!-- 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3. -->
<property name="numTestsPerEvictionRun" value="${redis.pool.numTestsPerEvictionRun}" />
<!-- 空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1. -->
<property name="timeBetweenEvictionRunsMillis" value="${redis.pool.timeBetweenEvictionRunsMillis}" />
</bean>
<!-- redis 连接工厂 -->
<bean id="redisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="poolConfig" ref="poolConfig" />
<property name="port" value="${redis.port}" />
<property name="hostName" value="${redis.host}" />
<!-- <property name="password" value="${redis.password}" /> -->
<property name="timeout" value="${redis.pool.timeout}"></property>
</bean>
<!-- redis 操作模版 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="redisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
</bean>
<!-- spring RedisBaseDcoImp 配置 -->
<bean id="cacheDao" class="cn.dyaoming.demo.dao.RedisTemplateImp">
<property name="redisTemplate">
<ref bean="redisTemplate" />
</property>
</bean>
</beans>
至此,项目结构完整。
5、如果需要使用临时缓存命名空间的话,需要增加上扫描配置
<!-- 自动扫描且只扫描@Component-->
<context:component-scan
base-package="cn.dyaoming" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Component" />
</context:component-scan>
用于扫描common-util包里边的springUtil工具类,实现spring的静态控制。
6、修改代码,启动项目。
在业务层方法上加上缓存配置。
/**
* <p>默认缓存业务层</p>
* @param num 随机范围
* @return 返还值
*/
@Cacheable("default")
public String defaultService(int num) {
return "默认缓存方法返回随机值:" + new Random().nextInt(num);
}
括号里是命名空间的名称,可以输入配置的命名空间,也可以随便填写。
不在配置中的命名空间视为临时命名空间,采用配置是default的配置,记缓存时长和加密标识。
其他例子:
/**
* <p>用户缓存业务层</p>
* @param num 随机范围
* @return 返还值
*/
@Cacheable("userInfo")
public String userService(int num) {
return "用户缓存方法返回随机值:" + new Random().nextInt(num);
}
如此,开发项目时可以快速完成,增加一行注解即可,方便快捷,不需要每个业务都控制缓存时长,键值设置等逻辑。
7、测试访问
浏览器输入:http://localhost:8080/demo-common-cache/demo/business?num=101
返回结果:
再次刷新页面,返回结果不变。
浏览器输入:http://localhost:8080/demo-common-cache/demo/default?num=101
返回结果:
默认缓存方法返回随机值:75
查看redis保存的内容。
可以看到TTL时间小于设置的缓存时间,这是因为我截图操作慢了,实际上缓存时间跟设置时间一致。
返回的内容是明文,中文是编码后的值,通过字符处理,可以全部变成明文。
在测试下userinfo类型的缓存,由于设置是加密的,所有结果应该是加密的内容。
至此,演示项目完成,证明相关common-cache的确可以快速实现缓存。如果有特殊情况需要优化缓存时长或者关闭缓存时,只需要修改配置文件即可,不需要修改源代码。