1、实现自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TsingRedisCache {
}
2、创建切面配置类
/**
* @ClassName : AopConfig
* @Description: 横切面配置
* @Configuration 声明配置类
* @ComponentScan("com.tsingsoft.common.*") 扫描包
* @EnableAspectJAutoProxy //开启 Spring 对 AspectJ 的支持
* @author :
* @Company :
* @date 2019年7月11日 下午1:41:22
*/
@Configuration
@ComponentScan("com.tsingsoft.common.*")
@EnableAspectJAutoProxy
public class AspectConfig {
}
3、创建Redis缓存配置及执行器类文件
- 创建spring-data-redis 和 jedis整合xml的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
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-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 配置JedisPoolConfig实例 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接数 -->
<property name="maxTotal" value="30" />
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="10" />
<!-- 每次释放连接的最大数目 -->
<property name="numTestsPerEvictionRun" value="1024" />
<!-- 释放连接的扫描间隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<!-- 连接最小空闲时间 -->
<property name="minEvictableIdleTimeMillis" value="1800000" />
<!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
<property name="softMinEvictableIdleTimeMillis" value="10000" />
<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
<property name="maxWaitMillis" value="1500" />
<!-- 在获取连接的时候检查有效性, 默认false -->
<property name="testOnBorrow" value="true" />
<!-- 在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="true" />
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="false" />
</bean>
<!-- 配置JedisConnectionFactory -->
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:usePool="true"
p:poolConfig-ref="poolConfig"
p:hostName="192.168.0.46"
p:port="6379"
p:timeout="200"
p:password="123456" />
<!-- 配置RedisTemplate -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory" >
<!--以下针对各种数据进行序列化方式的选择-->
<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"/>
</property>
</bean>
<!-- cache配置 -->
<bean id="redisCacheExecutor" class="com.tsingsoft.common.redis.RedisCacheExecutor">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
</beans>
- 说明一下,这个基于spring-data-redis中RedisTemplate实现的常用方法。
@Component
public class RedisCacheExecutor {
private Logger logger = LoggerFactory.getLogger(RedisCacheExecutor.class);
/**
* sping-data-redis提供的Redis模板
*/
@Resource
private RedisTemplate<Serializable, Object> redisTemplate;
/**
* @Description: set方式注入
* @author 李继伟
* @param redisTemplate
* @date 2019年7月11日 上午11:12:29
*/
public void setRedisTemplate(RedisTemplate<Serializable, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* @Description: 批量删除对应的value
* @author 李继伟
* @param keys
* @date 2019年7月11日 下午3:17:27
*/
public void del(final String... keys) {
for (String key : keys) {
del(key);
}
}
/**
* @Description: 批量删除对应的value
* @author 李继伟
* @param keys
* @date 2019年7月11日 下午3:17:27
*/
public void del(final List<String> keys) {
for (String key : keys) {
del(key);
}
}
/**
* @Description: 通过模糊查询的方式查找所有key,然后批量删除key。
* Redis模糊查询的通配符是"*"
* 可以是:"*","key*","*key","*key*"
* 192.168.0.46:6379> keys *
* 1) "aba"
* 2) "abc"
* 192.168.0.46:6379> keys a*
* 1) "aba"
* 2) "abc"
* 192.168.0.46:6379> keys *c
* 1) "abc"
* 192.168.0.46:6379> keys *b*
* 1) "aba"
* 2) "abc"
* @author 李继伟
* @param pattern
* @date 2019年7月11日 下午3:15:43
*/
public void delByPattern(final String pattern) {
Set<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size() > 0) {
redisTemplate.delete(keys);
}
}
/**
* 删除对应的value
*
* @param key
*/
public void del(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* @Description: 写入缓存
* @author 李继伟
* @param key
* @param value
* @return
* @date 2019年7月11日 下午3:11:52
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
logger.error("Redis缓存异常,set方法出错:", e);
}
return result;
}
/**
* @Description: 写入缓存,带失效时间
* @author 李继伟
* @param key
* @param value
* @param expireTime 失效时间
* @return
* @date 2019年7月11日 下午3:11:25
*/
public boolean set(final String key, Object value, Long expireTime) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
logger.error("Redis缓存异常,set方法出错:", e);
}
return result;
}
}
4、创建Redis缓存横切面
@Aspect
@Component
public class RedisCacheAspect {
private Logger log = Logger.getLogger(RedisCacheAspect.class);
@Autowired
private RedisCacheExecutor redisCacheExecutor;
@Autowired
private ConfigProperties configProperties;
private Long defaultCacheExpireTime; // 缓存默认的过期时间
/**
* @Description: 创建《查询操作》切点。
* 对所有以select/find/get/query/search/init/append/load开头的方法,
* 并且同时使用@TsingRedisCache注解的方法进行横切
* @author
* @date 2019年7月11日 下午1:18:39
*/
@Pointcut(value="((execution(* com.tsingsoft.**.service.*.select*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.find*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.query*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.search*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.init*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.append*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.load*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.get*(..)))) "
+ "&& @annotation(com.tsingsoft.common.annotation.TsingRedisCache)")
public void selectPointCut(){};
/**
* @Description: 创建《修改操作》切点。
* 对所有以insert/update/delete/modify/save/change/edit/remove/repair/del开头的方法,
* 并且同时使用@TsingRedisCache注解的方法进行横切
* @author
* @date 2019年7月11日 下午1:18:39
*/
@Pointcut(value="((execution(* com.tsingsoft.**.service.*.insert*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.update*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.delete*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.modify*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.save*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.edit*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.remove*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.repair*(..))) "
+ "|| (execution(* com.tsingsoft.**.service.*.del*(..)))) "
+ "&& @annotation(com.tsingsoft.common.annotation.TsingRedisCache)")
public void modifyPointCut(){};
/**
* @Description: 《查询操作》切点操作。
* 存在缓存,则返回缓存值。
* 不存在,则从DB中获取,并放入缓存,然后返回
* 必须采用环绕通知方式,注意必须要有ProceedingJoinPoint参数传入。
* @author
* @param joinPoint
* @date 2019年7月11日 下午2:27:31
*/
@Around(value="selectPointCut()")
public Object aroundSelectPointCut(ProceedingJoinPoint joinPoint){
if(!configProperties.isOpenRedisCache()){
try {
return joinPoint.proceed();
}
catch (Throwable e) {
log.debug(e);
}
}
//全限类名(指向父类,如果使用自动生成包的mapper方法,会指向tk包下的Mapper类)
String clazzName = joinPoint.getSignature().getDeclaringTypeName();
clazzName = clazzName.substring(clazzName.lastIndexOf(".")+1);
//方法名
String methodName = joinPoint.getSignature().getName();
//参数
Object[] args = joinPoint.getArgs();
//生成KEY
String key = generateCacheKey(clazzName, methodName, args);
//检查是否有缓存,存在,则取出返回
if (redisCacheExecutor.exists(key)) {
return redisCacheExecutor.get(key);
}
//不存在,则正常执行相关方法,从数据库中查询数据
Object obj = null;
try {
obj = joinPoint.proceed();
}
catch (Throwable e) {
log.debug(e);
}
defaultCacheExpireTime = configProperties.defaultCacheExpireTime();
if (obj != null) {
final String tempKey = key;
final Object tempValue = obj;
new Thread(new Runnable() {
@Override
public void run() {
redisCacheExecutor.set(tempKey, tempValue, defaultCacheExpireTime);
}
}).start();
}
return obj;
}
/**
* @Description: 在《修改操作》切点方法执行结束后,清空所有缓存
* @author
* @date 2019年7月11日 下午2:58:16
*/
@After(value="modifyPointCut()")
public void afterModifyPointCut() {
if(configProperties.isOpenRedisCache()){
try {
redisCacheExecutor.delByPattern("*");
}
catch (Throwable e) {
log.debug(e);
}
}
}
/**
* @Description: 创建缓存key,通过传入参数转大写,用"_"分隔
* @author
* @param targetName
* service实现类名称
* @param methodName
* service实现类中方法名称
* @param arguments
* service实现类中方法参数集合
* @return
* @date 2019年7月11日 下午2:33:10
*/
private String generateCacheKey(String targetName, String methodName, Object[] arguments) {
StringBuffer keySb = new StringBuffer();
keySb.append(targetName.toUpperCase());
keySb.append("_");
keySb.append(methodName.toUpperCase());
if ((arguments != null) && (arguments.length != 0)) {
for (int i = 0; i < arguments.length; i++) {
Object arg = arguments[i];
if(arg instanceof String){
keySb.append("_").append(arg.toString().toUpperCase());
}else if(arg instanceof Integer){
keySb.append("_").append(arg.toString().toUpperCase());
}else if(arg instanceof Double){
keySb.append("_").append(arg.toString().toUpperCase());
}else if(arg instanceof Float){
keySb.append("_").append(arg.toString().toUpperCase());
}else if(arg instanceof Boolean){
keySb.append("_").append(arg.toString().toUpperCase());
}else if(arg instanceof Long){
keySb.append("_").append(arg.toString().toUpperCase());
}else if(arg instanceof Byte){
keySb.append("_").append(arg.toString().toUpperCase());
}else if(arg instanceof Short){
keySb.append("_").append(arg.toString().toUpperCase());
}
}
}
return keySb.toString();
}
}
ConfigProperties 是我对properties中,常量配置进行同一管理的类。
到此,整个配置完成。