springboot中AOP+redis封裝緩存
目錄
一、目的
二、配置
--1、pom.xml
--2、redis配置
--3、響應類
--4、AOP切面的配置
三、如何使用自已寫的緩存程序
一、目的
在controller層的方法上加一個注釋就可以利用aop切面去做代碼的擴展,在代碼的擴展中利用redis做為緩存中間件。關于注解就用自定義的@RedisCached
二、配置
1、pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、redis配置
基礎配置
@Configuration
public class RedisConfig {
/**
* @Description: 創建一個模板類,將redis連接工廠設置到模板類中{ @link RedisTemplate}
* @Author: maozi
* @Date: 2018/6/5 11:49
* @see:
**/
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
return template;
}
}
再封裝一層
@Repository
public class RedisDao {
/**
* @Description: 字符類的模板{ @link }
* @Author: maozi
* @Date: 2018/6/5 11:52
* @see:
**/
@Autowired
private StringRedisTemplate stringTemplate;
/**
* @Description: 對象類的模板 { @link }
* @Author: maozi
* @Date: 2018/6/5 11:53
* @see:
**/
@Autowired
private RedisTemplate<String, Object> template;
public void setStringKey(String key, String value,int expire) {
if(stringTemplate.hasKey(key)){
stringTemplate.delete(key);
}
ValueOperations<String, String> ops = stringTemplate.opsForValue();
ops.set(key,value,expire, TimeUnit.MINUTES);
}
public String getStringValue(String key) {
ValueOperations<String, String> ops = this.stringTemplate.opsForValue();
return ops.get(key);
}
public Object getValue(String key) {
return template.opsForValue().get(key);
}
public void setKey(String key,Object value,long minutes){
if(template.hasKey(key)){
template.delete(key);
}
template.opsForValue().set(key,value,minutes, TimeUnit.MINUTES);
}
public boolean existByKey(String key){
return template.hasKey(key);
}
public boolean existByStringKey(String key){
return stringTemplate.hasKey(key);
}
public long getExpireTime(String key){
return template.getExpire(key);
}
public Boolean setExpireToReturnYes(String key,long timeout){
if (!template.hasKey(key)){
return false;
}
return template.expire(key,timeout,TimeUnit.MINUTES);
}
public Boolean setStringExpireToReturnYes(String key,long timeout){
if(!stringTemplate.hasKey(key)){
return false;
}
return stringTemplate.expire(key,timeout,TimeUnit.MINUTES);
}
public void cleanCacheByString(String key){
stringTemplate.delete(key);
}
public void cleanCache(String key){
template.delete(key);
}
}
注意:這里的模板有分字符的和對象的,如果value是字符的,那建議有字符模板。
application.properties
# redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=1
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=500
spring.redis.pool.min-idle=0
spring.redis.timeout=0
3、響應類
@ApiModel(description = "請求返回結果")
public class ResponseResult {
private String errorCode;
private String errorMsg;
private Object objectResult;
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public Object getObjectResult() {
return objectResult;
}
public void setObjectResult(Object objectResult) {
this.objectResult = objectResult;
}
@Override
public String toString(){
return JSON.toJSONString(this);
}
}
4、AOP切面的配置
自定義注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisCached {
public int expire() default 5;//過期時間,默認5分鐘
}
切面類
/**
* @Description: redis緩存的AOP
* @Author: maozi
* @Date: 2018/9/3 10:07
* @see: RedisCached
**/
@Aspect
@Component
public class RedisAspect {
private final Logger logger = LoggerFactory.getLogger(RedisAspect.class);
@Autowired
RedisDao redisDao;
@Pointcut("execution(public com.zhengjia.entity.ResponseResult com.zhengjia.web.*.*(..)) && @annotation(com.zhengjia.common.Annotation.RedisCached)")
public void redisAdvice(){}
@Around("redisAdvice()")
public Object Interceptor(ProceedingJoinPoint pjp){
Object result = null;
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
String port = String.valueOf(request.getServerPort());
String uri = request.getRequestURI();
String methodType = request.getMethod();
String queryString = request.getQueryString();
//反射拿方法信息
Method method=getMethod(pjp);
//獲得annotation的信息
RedisCached annotation = method.getAnnotation(RedisCached.class);
int expire = annotation.expire();
//檢查請求類型
if(!methodType.equalsIgnoreCase("GET")){
throw new RuntimeException("只允許get請求做緩存");
}
//key的唯一性由url加上參數保證
String keyName = "";
if(method.getAnnotation(IgnoreToken.class) == null){ //如果沒有注解的話,keyName規則上加上一sourceFrom參數
String token = request.getHeader("Authorization");
if(token.isEmpty()) throw new RuntimeException("請求頭Authorization中沒有token信息");
Map userMap = (Map)redisDao.getValue(token);
keyName = (String)userMap.get("sourceFrom") + port + uri + "?" + queryString;
}else {
keyName = port + uri + "?" + queryString;
}
ResponseResult responseResult = new ResponseResult();
try {
if (!redisDao.existByKey(keyName)){//沒存在緩存,去查數據庫
result = pjp.proceed();
responseResult = (ResponseResult)result;
if(responseResult.getObjectResult() != null){ //防止緩存空值
redisDao.setKey(keyName,responseResult.getObjectResult(),expire);
}
}else{//存在緩存中,在緩存中取數據庫
responseResult.setObjectResult(redisDao.getValue(keyName));
responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
result = responseResult;
logger.info("redis緩存中查詢數據,key值為" + keyName);
}
} catch (Throwable e) {
e.printStackTrace();
responseResult.setErrorCode(Constants.RESPONSE_CODE_ERROR);
responseResult.setErrorMsg("redisAspect報錯!");
logger.info("redisAspect報錯!");
}
return result;
}
/**
* 獲取被攔截方法對象
*
* MethodSignature.getMethod() 獲取的是頂層接口或者父類的方法對象
* 而緩存的注解在實現類的方法上
* 所以應該使用反射獲取當前對象的方法對象
*/
public Method getMethod(ProceedingJoinPoint pjp){
//獲取參數的類型
Object [] args=pjp.getArgs();
Class [] argTypes=new Class[pjp.getArgs().length];
if(args.length == 1 && args[0] == null){
args = new Object[0];
}
for(int i=0;i<args.length;i++){
argTypes[i]=args[i].getClass();
}
Method method=null;
try {
//有參的情況下
if(args.length > 0){
method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
}else { //無參的情況下
Class clazz = Class.forName(pjp.getSignature().getDeclaringTypeName());
Method[] i = clazz.getMethods();
for(Method data : clazz.getMethods()){
if(data.getName().equalsIgnoreCase(pjp.getSignature().getName())){
method = data;
break;
}
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return method;
}
}
注意:
1、緩存的key的唯一性是用url來保證,而且這里只允許Get請求,如果想擴展,那就自行修改代碼,另外key的唯一性也可以用其他方式來確定。
2、這里緩存ResponseResult
中的objectResult的值,所以要用ResponseResult做返回,另外是因為,返回值在切面中做了限制。也就是說不用ResponseResult做返回值返回,那這個緩存是不起作用的。
3、expire是提供出去,動態設置緩存過期的時間
三、如何使用自已寫的緩存程序
/**
* 商圈-購物中心關聯度
*/
@GetMapping(value = "/correlation-degree/{name}")
@RedisCached(expire = 6)
public ResponseResult shopMallCorrelationDegree(@PathVariable String name) {
ResponseResult responseResult = new ResponseResult();
List<Map<String, Object>> resultList = bussinessCircleService.getShopMallCorrelationDegree(name);
responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
responseResult.setObjectResult(resultList);
return responseResult;
}