一、注解的基础
1.注解的定义:Java文件叫做Annotation,用@interface表示。
2.元注解:@interface上面按需要注解上一些东西,包括@Retention、@Target、@Document、@Inherited四种。
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.CONSTRUCTOR) // 构造函数
@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和注解的强大之处,站在伟人的肩膀上看得更远。