aop实现通用缓存,并且防止缓存击穿
程序员文章站
2022-03-13 09:24:59
...
实现代码在附件中
1.自定义注解文件
package sgnctest.el;
import java.lang.annotation.*;
/**
* Author by gjp, Date on 2019/9/16.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCache {
//key 值
String key() default "";
//缓存前缀
String preName() default "lypt";
//超时时间
long outTime() default 60000;
}
2.实现类
package sgnctest.el.service;
import org.springframework.stereotype.Service;
import sgnctest.el.MyCache;
/**
* Author by gjp, Date on 2019/9/16.
*/
@Service
public class ServiceCacheInf{
@MyCache(key="#key")
public String getName(String key) {
System.out.println("====访问数据库"+key);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return key;
}
}
3.建立aop 类
package sgnctest.el.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import sgnctest.el.MyCache;
import sgnctest.el.Spel;
import sgnctest.el.util.LockServer;
import javax.annotation.Resource;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Author by gjp, Date on 2019/9/16.
* aop 操作
*/
@Aspect
@Component
public class CacheAop {
@Resource
private RedisCache redisCache;
ThreadLocal<Long> startTime = new ThreadLocal<>();
// 定义需要匹配的切点表达式,同时需要匹配参数
@Around("@annotation(cache)")
public Object around(ProceedingJoinPoint pjp, MyCache cache) throws Throwable {
System.out.println("方法环绕start...around");
String key = cache.key();
String preName = cache.preName();
long timeOut = cache.outTime();
Object result = null;
String keyVal ="";
try {
// System.out.println("key:" + key + ";preName=" + preName + ";timeOut=" + timeOut);
MethodSignature signature = (MethodSignature) pjp.getSignature();
String[] parameters = signature.getParameterNames();
//jdk1.8
//Method method = signature.getMethod();
// Parameter[] parameters = method.getParameters();
Object obj[] = pjp.getArgs();
//通过el表达式获取值
keyVal = Spel.el(key, parameters, obj);
//从缓存中读取数据
result = redisCache.getCacheById(keyVal);
if(null != result){
return result;
}
//先加锁,然后从数据库中获取数据,从数据库中获取
LockServer.getInstance().doLock(keyVal);
result = redisCache.getCacheById(keyVal);
if(null != result){
return result;
}
result = pjp.proceed(); //pjp.proceed().toString() + "aop String keyVal:"+keyVal;
//加入缓存
redisCache.putCacheById(keyVal,result);
} catch (Throwable e) {
e.printStackTrace();
}finally {
LockServer.getInstance().releaseLock(keyVal);
}
// System.out.println("方法环绕end...around");
return result;
}
}
4.模拟缓存类
package sgnctest.el.service;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/**
* Author by gjp, Date on 2019/9/16.
* 从缓存中获取数据
*/
@Service
public class RedisCache {
private String keys="";
public Object getCacheById(String key){
if(StringUtils.isEmpty(keys)){
return null;
}
return "cache"+keys;
}
public void putCacheById(String key,Object object){
System.out.println("加入缓存"+key);
keys =key;
}
}
5.配置类
package sgnctest.el;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* Author by gjp, Date on 2019/9/16.
*/
@Configuration
@ComponentScan("sgnctest.el.service")
@EnableAspectJAutoProxy //开启Spring对AspectJ代理的支持
public class AopConfig {
}
6.el表达式(技术核心)
package sgnctest.el;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Author by gjp, Date on 2019/9/16.
*/
public class Spel {
private static ExpressionParser parser = new SpelExpressionParser();
/**
* el表达式解析
* @param key 需要转换的el表达式
* @param parameters 变量名称
* @param args 变量值
* @return
*/
public static String el(String key,String[] parameters,Object[] args){
//key 解析为el表达式
Expression exp = parser.parseExpression(key);
EvaluationContext context = new StandardEvaluationContext();
if(null == args || args.length==0){
return null;
}
int len = args.length;
for(int i=0;i<len;i++) {
//参数和值防止上下文中
context.setVariable(parameters[i],args[i]);
}
//获取el中的参数值
return exp.getValue(context,String.class);
}
public static void main(String[] args) {
Spel spel = new Spel();
String key ="#ztb+' '+#pay";
String [] parameter = {"ztb","pay"};
Object [] obj ={"ztb123","pay123"};
System.out.println("result ::"+spel.el(key,parameter,obj));
}
}
7.测试类:
package sgnctest.el;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import sgnctest.el.service.ServiceCacheInf;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Author by gjp, Date on 2019/9/16.
*
* 测试类
*/
public class SpringMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AopConfig.class);
final ServiceCacheInf demoAnnotationService = context.getBean(ServiceCacheInf.class);
ExecutorService executor = Executors.newFixedThreadPool(10);
int callTime = 20;
final CountDownLatch countDownLatch = new CountDownLatch(callTime);
//模拟并发情况下的接口调用统计
for(int i=0;i<callTime;i++){
final int count =i;
executor.execute(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count%3 ==0) {
System.out.println(demoAnnotationService.getName("123456"));
}else if(count %3 ==1){
System.out.println(demoAnnotationService.getName("666"));
}else {
System.out.println(demoAnnotationService.getName("7775"));
}
}
});
countDownLatch.countDown();
}
executor.shutdown();
//等待所有线程统计完成后输出调用次数
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
context.close();
}
}
日志:
main 2019-09-16 14:52:27.765 [main] INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
====访问数据库123456
====访问数据库7775
====访问数据库666
加入缓存666
加入缓存123456
加入缓存7775
cache123456
cache123456
cache123456
方法环绕start...around
cache666
666
123456
方法环绕start...around
方法环绕start...around
cache7775
方法环绕start...around
方法环绕start...around
cache7775
cache7775
cache7775
方法环绕start...around
cache7775
7775
cache7775
方法环绕start...around
方法环绕start...around
cache7775
cache7775
方法环绕start...around
cache7775
cache7775
方法环绕start...around
cache7775
cache7775
cache7775
1.自定义注解文件
package sgnctest.el;
import java.lang.annotation.*;
/**
* Author by gjp, Date on 2019/9/16.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCache {
//key 值
String key() default "";
//缓存前缀
String preName() default "lypt";
//超时时间
long outTime() default 60000;
}
2.实现类
package sgnctest.el.service;
import org.springframework.stereotype.Service;
import sgnctest.el.MyCache;
/**
* Author by gjp, Date on 2019/9/16.
*/
@Service
public class ServiceCacheInf{
@MyCache(key="#key")
public String getName(String key) {
System.out.println("====访问数据库"+key);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return key;
}
}
3.建立aop 类
package sgnctest.el.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import sgnctest.el.MyCache;
import sgnctest.el.Spel;
import sgnctest.el.util.LockServer;
import javax.annotation.Resource;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Author by gjp, Date on 2019/9/16.
* aop 操作
*/
@Aspect
@Component
public class CacheAop {
@Resource
private RedisCache redisCache;
ThreadLocal<Long> startTime = new ThreadLocal<>();
// 定义需要匹配的切点表达式,同时需要匹配参数
@Around("@annotation(cache)")
public Object around(ProceedingJoinPoint pjp, MyCache cache) throws Throwable {
System.out.println("方法环绕start...around");
String key = cache.key();
String preName = cache.preName();
long timeOut = cache.outTime();
Object result = null;
String keyVal ="";
try {
// System.out.println("key:" + key + ";preName=" + preName + ";timeOut=" + timeOut);
MethodSignature signature = (MethodSignature) pjp.getSignature();
String[] parameters = signature.getParameterNames();
//jdk1.8
//Method method = signature.getMethod();
// Parameter[] parameters = method.getParameters();
Object obj[] = pjp.getArgs();
//通过el表达式获取值
keyVal = Spel.el(key, parameters, obj);
//从缓存中读取数据
result = redisCache.getCacheById(keyVal);
if(null != result){
return result;
}
//先加锁,然后从数据库中获取数据,从数据库中获取
LockServer.getInstance().doLock(keyVal);
result = redisCache.getCacheById(keyVal);
if(null != result){
return result;
}
result = pjp.proceed(); //pjp.proceed().toString() + "aop String keyVal:"+keyVal;
//加入缓存
redisCache.putCacheById(keyVal,result);
} catch (Throwable e) {
e.printStackTrace();
}finally {
LockServer.getInstance().releaseLock(keyVal);
}
// System.out.println("方法环绕end...around");
return result;
}
}
4.模拟缓存类
package sgnctest.el.service;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/**
* Author by gjp, Date on 2019/9/16.
* 从缓存中获取数据
*/
@Service
public class RedisCache {
private String keys="";
public Object getCacheById(String key){
if(StringUtils.isEmpty(keys)){
return null;
}
return "cache"+keys;
}
public void putCacheById(String key,Object object){
System.out.println("加入缓存"+key);
keys =key;
}
}
5.配置类
package sgnctest.el;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* Author by gjp, Date on 2019/9/16.
*/
@Configuration
@ComponentScan("sgnctest.el.service")
@EnableAspectJAutoProxy //开启Spring对AspectJ代理的支持
public class AopConfig {
}
6.el表达式(技术核心)
package sgnctest.el;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Author by gjp, Date on 2019/9/16.
*/
public class Spel {
private static ExpressionParser parser = new SpelExpressionParser();
/**
* el表达式解析
* @param key 需要转换的el表达式
* @param parameters 变量名称
* @param args 变量值
* @return
*/
public static String el(String key,String[] parameters,Object[] args){
//key 解析为el表达式
Expression exp = parser.parseExpression(key);
EvaluationContext context = new StandardEvaluationContext();
if(null == args || args.length==0){
return null;
}
int len = args.length;
for(int i=0;i<len;i++) {
//参数和值防止上下文中
context.setVariable(parameters[i],args[i]);
}
//获取el中的参数值
return exp.getValue(context,String.class);
}
public static void main(String[] args) {
Spel spel = new Spel();
String key ="#ztb+' '+#pay";
String [] parameter = {"ztb","pay"};
Object [] obj ={"ztb123","pay123"};
System.out.println("result ::"+spel.el(key,parameter,obj));
}
}
7.测试类:
package sgnctest.el;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import sgnctest.el.service.ServiceCacheInf;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Author by gjp, Date on 2019/9/16.
*
* 测试类
*/
public class SpringMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AopConfig.class);
final ServiceCacheInf demoAnnotationService = context.getBean(ServiceCacheInf.class);
ExecutorService executor = Executors.newFixedThreadPool(10);
int callTime = 20;
final CountDownLatch countDownLatch = new CountDownLatch(callTime);
//模拟并发情况下的接口调用统计
for(int i=0;i<callTime;i++){
final int count =i;
executor.execute(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count%3 ==0) {
System.out.println(demoAnnotationService.getName("123456"));
}else if(count %3 ==1){
System.out.println(demoAnnotationService.getName("666"));
}else {
System.out.println(demoAnnotationService.getName("7775"));
}
}
});
countDownLatch.countDown();
}
executor.shutdown();
//等待所有线程统计完成后输出调用次数
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
context.close();
}
}
日志:
main 2019-09-16 14:52:27.765 [main] INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
方法环绕start...around
====访问数据库123456
====访问数据库7775
====访问数据库666
加入缓存666
加入缓存123456
加入缓存7775
cache123456
cache123456
cache123456
方法环绕start...around
cache666
666
123456
方法环绕start...around
方法环绕start...around
cache7775
方法环绕start...around
方法环绕start...around
cache7775
cache7775
cache7775
方法环绕start...around
cache7775
7775
cache7775
方法环绕start...around
方法环绕start...around
cache7775
cache7775
方法环绕start...around
cache7775
cache7775
方法环绕start...around
cache7775
cache7775
cache7775
上一篇: 新浪也SNS了 - 你才SNS,你全家都SNS SNSQQ
下一篇: aspectj连接点