Redis+自定义注解+AOP实现缓存

闲着没事研究aop实现查询缓存,闷头敲代码,敲出了以下东西,希望看到的大神指正!

一、自定义注解
通过自定义注解,在修饰的方法上添加该注解,在aop中切入,为该方法添加缓存到redis中

package com.wwy.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定义缓存注解
 * @author wwy
 * @date 2019年12月13日
 * @version v0.0.1
 *
 */
@Retention(RetentionPolicy.RUNTIME)//声明注解何时起作用(运行时)
@Target({ElementType.METHOD,//声明注解作用的位置(方法)
		ElementType.TYPE})//声明注解作用的位置(类、接口、枚举)
public @interface Cache {
	/**
	 * 名称
	 * @return
	 */
String[] value() default"";
/**
 * 写入还是读取
 * 
 * @return
 */
boolean read() default true;
}

注解的value属性用于指定方法操作的数据库的名字,以便于后面再更新数据库数据时准确清楚缓存避免脏读,read属性指定该方法是读操作还是写操作,以便于后面切面中判断是要添加缓存还是清除缓存!
二、AOP
1.定义切面类,使用环绕通知,切入点为自定义注解修饰的方法,
2.利用全类名+方法名+数据库名作为redis中的key,
3.判断如果修饰方法的注解的read属性值为true,则先在缓存中查询,如果缓存中没有就在数据库中查询,然后把数据添加到缓存中
4.如果修饰方法的注解的read属性值为false,则遍历value属性,删除没有个包含value值得redis缓存
代码如下:

package com.wwy.aop;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Set;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.wwy.annotation.Cache;
import com.wwy.util.JSONUtil;
import lombok.extern.slf4j.Slf4j;
/**
 * 定义切面类,为查询数据添加缓存
 * @author wwy
 * @date 2019年12月13日
 * @version v0.0.1
 *
 */
@Slf4j
@Component
@Aspect
public class CacheAop {
	
	@Autowired
	private StringRedisTemplate redis;
	
	//环绕通知,切入到自定义注解修饰的方法或者类上(的多有方法)
	@SuppressWarnings("unchecked")
	@Around("@annotation(com.wwy.annotation.Cache)")
	public Object cache(ProceedingJoinPoint joinPoint) {
		//获取方法签名信息(返回值,包名,类名,方法名,参数列表)
		MethodSignature signature = (MethodSignature)joinPoint.getSignature();
		//获取切入方法操作的数据库名
		String[] dataSourceName=null;
		//获取切入方法是读取数据库还是写入数据库
		boolean read=false;
		Annotation[] annotations = signature.getMethod().getAnnotations();
		for(Annotation a:annotations) {
			if(a.annotationType().equals(Cache.class)) {
				dataSourceName=((Cache)a).value();						
				read=((Cache)a).read();
			}
		}
		//将方法操作的数据库名连接起来,后面连接到key中
		String dataSourceKey="";
		for(String k:dataSourceName) {
			dataSourceKey+=("<"+k+">");
		}
		//获取返回值类型
		Class<Object> returnType = signature.getReturnType();		
		//获取全类名
		String className = signature.getDeclaringTypeName();
		//获取方法名
		String name = signature.getMethod().getName();
		//拼装redis的key为全类名+方法名去除.+数据库名
		String key=(className+name).replace(".", "")+dataSourceKey;
		log.info("Rediskey:"+key);
		//读操作:通过key查询redis中的数据,如果有数据,就返回redis中的数据
		if(read) {
			log.info("查询操作");
			log.info("查询的数据库为:"+dataSourceKey);
			String value = redis.opsForValue().get(key);
			if(value==null||value.isEmpty()) {
				Object proceed=null;
				try {				
					//执行连接点方法
					proceed = joinPoint.proceed();
					if(proceed!=null) {
						//将返回的对象存放到redis中
						redis.opsForValue().set(key, JSONUtil.toJson(proceed));
					}
				} catch (Throwable e) {
					e.printStackTrace();
				}
				log.info("数据来源于数据库");
				return proceed;
			}else {
				try {
					log.info("数据来源于缓存");
					//如果redis中有值,返回redis中的对象
					return JSONUtil.toObject(value, returnType);
				} catch (IOException e) {

					e.printStackTrace();
				}
			}
			//写操作:清楚数据库中的关于数据表的缓存
		}else {
			log.info("增删改操作");
			log.info("增删改的数据库为:"+dataSourceKey);
			Object proceed=null;
			try {
				proceed = joinPoint.proceed();
				if(proceed!=null) {		
					//模糊匹配包含数据库名的key
					
					//获取方法需要操作的所有数据库名
					for(String dkey:dataSourceName) {
						
						//获取redis缓存中所有跟该数据库有关的key
						Set<String>  keys=redis.keys("*"+dkey+"*");
						//删除与该数据库有关的所有缓存	
						if(keys!=null&&!keys.isEmpty()) 
						redis.delete(keys);
						log.info("已删除"+dkey+"相关的缓存");
					}
				}
				return proceed;
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}
		return null;
	}
}

三、Service层方法
简单的试验就用之前写的demo做个测试吧!代码也很简单,只是单纯的在方法上添加了自定义注解修饰,贴出来的意义不大,但还是贴出来吧,万一哪个大佬愿意帮我指正,可以直接用来测试……

package com.wwy.test.service.impl;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wwy.annotation.Cache;
import com.wwy.entry.APIEntry;
import com.wwy.entry.Car;
import com.wwy.test.dao.read.ReadMapper;
import com.wwy.test.dao.write.WriteMapper;
import com.wwy.test.service.DataSourceTestService;
/**
 * 
 * @author wwy
 * @date 2019年12月12日
 * @version v0.0.1
 *
 */
@Service
public class DataSourceTestServiceImpl implements DataSourceTestService{

	@Autowired
	private ReadMapper readMapper;
	@Autowired
	private WriteMapper writeMapper;
	/**
	 * 读
	 */
	@Override
	@Cache(read = true,value= {"car"})//次查询通过redis缓存
	public APIEntry read() {
		// TODO Auto-generated method stub
		List<Car> list=readMapper.read();
		if(list!=null&&!list.isEmpty()) {
			return APIEntry.OK(list);
		}else {
			return APIEntry.ERROR("查询失败");
		}				
	}
	/**
	 * 写
	 */
	@Cache(read = false,value= {"car","order"})
	@Override
	public APIEntry write(Car car) {
		int count=writeMapper.write(car);
		if(count<=0) {
			return APIEntry.ERROR("写入失败");
		}else {
			return APIEntry.OK(null);
		}	
	}
}

你可能感兴趣的:(技术,redis,缓存,aop)