Redis(4):カスタム注釈によるredisキャッシュ操作
8449 ワード
一、注釈の基礎1.注記の定義:JavaファイルをAnnotationといい、@interfaceで表します.
2.メタ注釈:@interfaceの上に必要に応じて@Retention、@Target、@Document、@Inheritedの4種類を含むものを注釈します.
3.注記の保存ポリシー:
@Retention(RetentionPolicy.SOURCE)/注釈はソースコードのみに存在し、classバイトコードファイルには含まれません
@Retention(RetentionPolicy.CLASS)/デフォルトの保存ポリシーです.注釈はclassバイトコードファイルに存在しますが、実行時には取得できません.
@Retention(RetentionPolicy.RUNTIME)/注釈はclassバイトコードファイルに存在し、実行時に反射によって取得できます.
4.注釈の作用目標:
@Target(ElementType.TYPE)/インタフェース、クラス、列挙、注記
@Target(ElementType.FIELD)/フィールド、列挙の定数
@Target(ElementType.METHOD)/メソッド
@Target(ElementType.PARAMETER)/メソッドパラメータ
@Target(ElementType.CONTRUCTOR)/コンストラクション関数
@Target(ElementType.LOCAL_VARIABLE)/ローカル変数
@Target(ElementType.ANNOTATION_TYPE)/注記
@Target(ElementType.PACKAGE)/パッケージ
5.注記はjavadocに含まれます.
@Documented
6.注釈は継承できます.
@Inherited
7.注釈解析器:カスタム注釈を解析します.
2、キャッシュシステムの実現
/**
* @author
* @className: RedisCacheRemove
* @description: , AOP Redis
* @date 2019/8/1614:40
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface RedisCacheRemove {
/**key */
String nameSpace() default "";
String key() default "";
}
/**
* @author
* @className: RedisCacheSave
* @description: , AOP Redis
* , , ;
* @date 2019/8/1614:39
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface RedisCacheSave{
/**key */
String nameSpace() default "";
/**key*/
String key();
/** */
long expire() default -1;
/** */
TimeUnit unit() default TimeUnit.SECONDS;
/**
*
* , false
*/
boolean read() default true;
}
/**
* @author
* @className: RedisCacheAspect
* @description: = + /
* @date 2019/8/1614:48
*/
@Aspect
@Component
@Slf4j
public class RedisCacheAspect {
@Resource
private RedisHandler handler;
@Pointcut(value = "@annotation(com.guahao.wedoctor.venus.annotation.RedisCacheSave)")
public void saveCache() {
}
@Pointcut(value = "@annotation(com.guahao.wedoctor.venus.annotation.RedisCacheRemove)")
public void removeCache() {
}
// RedisCacheSave
@Around(value = "saveCache()")
private Object saveCache(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("<====== saveCache :{}.{}======>" ,
proceedingJoinPoint.getTarget().getClass().getName(), proceedingJoinPoint.getSignature().getName());
//
// m ,
Method m = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
// this() ,target() , method
Method methodWithAnnotations = proceedingJoinPoint.getTarget().getClass().getDeclaredMethod(
proceedingJoinPoint.getSignature().getName(), m.getParameterTypes());
Object result;
//
RedisCacheSave annotation = methodWithAnnotations.getDeclaredAnnotation(RedisCacheSave.class);
// key
String key = parseKey(methodWithAnnotations, proceedingJoinPoint.getArgs(), annotation.key(), annotation.nameSpace());
//
//Method methodOfAnnotation = a.getClass().getMethod("key");
//
//String key = (String) methodOfAnnotation.invoke(a);
// redis
log.info("<====== key:{} redis ======>", key);
String cache = handler.getCache(key);
if (cache == null) {
log.info("<====== Redis , ======>");
// ,
result = proceedingJoinPoint.proceed();
if (result != null) {
// redis, ,
long expireTime = annotation.expire();
if (expireTime != -1) {
handler.saveCache(key, result, expireTime, annotation.unit());
} else {
handler.saveCache(key, result);
}
}
return result;
} else {
return deSerialize(m, cache);
}
}
private Object deSerialize(Method m, String cache) {
Class returnTypeClass = m.getReturnType();
log.info(" :{}, :{}" , cache, returnTypeClass);
Object object = null;
Type returnType = m.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
log.info("<====== :{}" , typeArgClass.getName());
object = JSON.parseArray(cache, typeArgClass);
}
}else {
object = JSON.parseObject(cache, returnTypeClass);
}
return object;
}
// RedisCacheSave
@Around(value = "removeCache()")
private Object removeCache(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("<====== saveCache :{}.{}======>" ,
proceedingJoinPoint.getTarget().getClass().getName(), proceedingJoinPoint.getSignature().getName());
//
// m ,
Method m = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
// this() ,target() , method
Method methodWithAnnotations = proceedingJoinPoint.getTarget().getClass().getDeclaredMethod(
proceedingJoinPoint.getSignature().getName(), m.getParameterTypes());
Object[] args = proceedingJoinPoint.getArgs();
Object result;
result = proceedingJoinPoint.proceed(args);
RedisCacheRemove annotation = methodWithAnnotations.getAnnotation(RedisCacheRemove.class);
String key = parseKey(methodWithAnnotations, proceedingJoinPoint.getArgs(), annotation.key(),
annotation.nameSpace());
handler.removeCache(key);
return result;
}
// springEL
private String parseKey(Method method, Object[] argValues, String keyEl, String nameSpace) {
//
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(keyEl);
EvaluationContext context = new StandardEvaluationContext(); //
//
DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
String[] parameterNames = discover.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], argValues[i]);
}
//
return /*method.getName() + ":" +*/ nameSpace + expression.getValue(context).toString();
}
@Component
class RedisHandler {
@Resource
StringRedisTemplate cache;
@PostConstruct
StringRedisTemplate init() {
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
cache.setDefaultSerializer(serializer);
return cache;
}
void saveCache(String key, T t, long expireTime, TimeUnit unit) {
String value = JSON.toJSONString(t);
log.info("<====== Redis :{}", value);
cache.opsForValue().set(key, value, expireTime, unit);
}
void saveCache(String key, T t) {
String value = JSON.toJSONString(t, SerializerFeature.WRITE_MAP_NULL_FEATURES);
cache.opsForValue().set(key, value);
}
void removeCache(String key) {
cache.delete(key);
}
String getCache(String key) {
return cache.opsForValue().get(key);
}
}
}
不足と補充
この注記は、Guavaパッケージのブロンフィルタを使用して、データベースとキャッシュに存在しないクエリーをフィルタに入れ、キャッシュ破壊攻撃を防止することもできます.
まとめは実は車輪を作る必要はありませんが、勉強や特定の業務を目的に小さな車輪を作る価値があり、今回の勉強もAOPと注釈の強さを体得させ、偉人の肩に立ってもっと遠くを見ることができました.