starter是springboot的一个启动器,引入这个启动器就可以使用想用的功能。
类似spring-boot-starter-web等功能,现在模拟一个redis缓存启动器,实现功能:
其他项目引入这个组件依赖,在配置文件中加上redis连接配置后
1.可以直接使用redis工具类操作redis存数据,不用在另外的项目中再弄一个工具类
2.可以在自定义缓存注解在指定方法上存放方法返回结果,不用代码中嵌入缓存存储逻辑
3.spring项目和spring-boot项目都可以使用,方式略有不同
缺点:
组件比较简单,只有redis一层缓存,没有设计redis,mongdb,encache的多级缓存
关于redis集群下组件使用待完善
spring项目使用:注意开启了动态代理,注意方法上有自定义缓存注解,注意扫描了依赖项目的包
public class App2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println(context.getBean(RedisProperties.class));
System.out.println(context.getBean(RedisCacheAspect.class));
System.out.println(context.getBean(RedisClient.class));
Environment environment = context.getBean(Environment.class);
System.out.println("-----"+environment.getProperty("my.redis.host"));
System.out.println("---" + context.getBean(RedisProperties.class).getPort());
RedisToolUtils.set("qq", "qq");
RedisToolUtils.set("day00ForProperties.TestServiceImpl2.testServiceMethod.[]", "hahaha");
TestServiceImpl2 testServiceImpl = context.getBean(TestServiceImpl2.class);
testServiceImpl.testServiceMethod();
testServiceImpl.testServiceMethod();
testServiceImpl.testServiceMethod();
context.close();
System.out.println("----end-----");
}
}
@Configuration
@ComponentScan(value={"com.my.springboot.spring_boot_starter_redis","day00ForRedisCacheStarter"})
@EnableAspectJAutoProxy
public class MyConfig {
}
@Service
public class TestServiceImpl2 {
@RedisCache(expireTime=3600)
public String testServiceMethod() {
System.out.println("我即将查询数据库了");
return "hahaha";
}
}
spring-boot项目使用:同样扫描依赖包,有redis连接配置,有自定义注解
@SpringBootApplication
/**
* 加载其他项目下的bean
* @author yp-tc-m-7129
*/
@ComponentScan(value={"com.my.springboot.spring_boot_starter_redis.bean","com.my.springboot.starter"})
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
System.out.println(context.getBean(RedisCacheAspect.class));
System.out.println(context.getBean(RedisClient.class));
System.out.println(context.getBean(RedisProperties.class));
// context.getBean(Jedis.class).set("hh", "567890");
// System.out.println(context.getBean(Jedis.class).get("hh"));
TestServiceImpl2 testServiceImpl = context.getBean(TestServiceImpl2.class);
testServiceImpl.testServiceMethod();
testServiceImpl.testServiceMethod();
testServiceImpl.testServiceMethod();
RedisToolUtils.set("mm", "mm");
context.close();
}
}
@Service
public class TestServiceImpl2 {
@RedisCache(expireTime=3600)
public String testServiceMethod() {
System.out.println("我即将查询数据库了");
return "hahaha";
}
}
#zidingyi redis huancun peizhi
my.redis.host=127.0.0.1
my.redis.port=7011
#my.redis.database=0
#my.redis.timeout=2000
#my.redis.maxActive=8
#my.redis.maxWait=-1
#my.redis.maxIdle=5
#my.redis.minIdle=0
组件主要代码:
一个切面,拦截有这个自定义注解的方法,redis存储形式是key,json字符串形式
@Component
@Aspect
public class RedisCacheAspect {
@Autowired
private Environment env;
/**
* 标记RedisCache注解当做切点
*/
@Pointcut("@annotation(com.my.springboot.spring_boot_starter_redis.bean.RedisCache)")
public void pointcut() {
}
/**
* 获取缓存的key key 定义在注解上,支持SPEL表达式
*
* @param pjp
* @return
*/
private String getSelDefKey(ProceedingJoinPoint pjp) {
// 获取类名-全路径
String classType = pjp.getTarget().getClass().getName();
//获取方法名
String methodName = pjp.getSignature().getName();
//获取字符串数组的参数
Object[] paramterArray = (Object[]) pjp.getArgs();
String paramterStr = Arrays.toString(paramterArray);
//组装唯一key
String key = classType + "." + methodName + "." + paramterStr;
return key;
}
@Around("pointcut()")
public Object redisCacheMethod(ProceedingJoinPoint pjp) throws Throwable {
//全局统一配置,用于redis集群挂机后,标记为不再使用缓存
String openFlag = env.getProperty("my.redis.cache.isOpen") == null ? "true" : env.getProperty("my.redis.cache.isOpen");
Boolean cacheEnable = Boolean.valueOf(openFlag);
// 判断是否需要拦截
if (!cacheEnable) {
try {
pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
}
/**
* 把方法所在全路径+方法名+参数作为一个关键key,不会重复
*/
String key = getSelDefKey(pjp);
//获取注解参数
Method method = getMethod(pjp);
RedisCache redisCache = method.getAnnotation(RedisCache.class);
// 获取方法的返回类型,让缓存可以返回正确的类型
Class returnType = ((MethodSignature) pjp.getSignature()).getReturnType();
Object result = RedisToolUtils.get(key);
if (result == null) {
result = pjp.proceed();
if(result !=null){
//只有数据库返回对象才会存储缓存,避免无用的null字符串对象遭受攻击造成redis服务器的内存移除
String temp = com.alibaba.fastjson.JSONObject.toJSONString(result);
//存储类型采用json字符串
RedisToolUtils.setex(key, redisCache.expireTime(), temp);
}
} else {
//找到直接返回缓存的结果,转为可用的类型
result = JSONObject.parseObject((String) result, returnType);
}
return result;
}
/**
* 获取被拦截方法对象
* MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象 而缓存的注解在实现类的方法上
* 所以应该使用反射获取当前对象的方法对象
*/
public Method getMethod(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
return method;
}
}
具体代码参见git地址:欢迎大家使用和提bug,有疑问qq联系
https://github.com/zhangmin1992/redis-cache-starter