AOP实现Redis缓存服务

    科技2024-10-27  19

    1.1 现有代码的分析

    说明: 1.虽然在业务层service中完成了代码的实现.但是该代码不具有复用性.如果换了其他的业务则需要重新编辑. 2.由于缓存的代码写在业务层service中,所以代码的耦合性高,不方便以后的扩展. 需求: 1.能否实现代码的复用. 2.能否降低代码的耦合性.

    1.2 AOP

    1.2.1 AOP作用

    名称:面向切面编程. 一句话总结: 在不改变原有代码的条件下,对功能进行扩展. 公式: AOP = 切入点表达式 + 通知方法.

    专业术语: 1.连接点: 在执行正常的业务过程中满足了切入点表达式时进入切面的点.(织入) 多个 2.通知: 在切面中执行的具体的业务(扩展) 方法 3.切入点: 能够进入切面的一个判断 if判断 一个 4.目标方法: 将要执行的真实的业务逻辑.

    1.2.2 关于通知说明

    1.前置通知: 目标方法执行之前执行 2.后置通知: 目标方法执行之后执行 3.异常通知: 目标方法执行之后抛出异常时执行 4.最终通知: 不管什么时候都要执行的方法. 说明:上述的四大通知类型不能控制目标方法是否执行.一般使用上述的四大通知类型,都是用来记录程序的执行状态.

    5.环绕通知: 在目标方法执行前后都要执行的通知方法. 控制目标方法是否执行.并且环绕通知的功能最为强大.

    1.2.3 切入点表达式说明

    1). bean(bean的id) 类名首字母小写 匹配1个类 2). within(包名.类名) 按包路径匹配类 匹配多个类 上述表达式是粗粒度的控制,按类匹配. 3).execution(返回值类型 包名.类名.方法名(参数列表)) 4).@annotation(包名.注解名) 按注解进行拦截.

    1.2.4 AOPDemo复习

    package com.jt.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Component //将对象交给spring容器管理 @Aspect //标识我是一个切面 public class CacheAOP { /** * AOP = 切入点表达式 + 通知方法. * * 拦截需求: * 1.要求拦截itemCatServiceImpl的bean * 2.拦截com.jt.service下的所有的类 * 3.拦截com.jt.service下的所有类及方法 * 3.1拦截com.jt.service的所有的类.返回值为int类型的.并且add开头 * 的方法.并且参数一个 为String类型 */ //@Pointcut(value = "bean(itemCatServiceImpl)") //@Pointcut("within(com.jt.service..*)") //拦截com.jt.service下的所有类的所有方法的任意参数类型 //@Pointcut("execution(int com.jt.service..*.add*(String))") @Pointcut("execution(* com.jt.service..*.*(..))") public void pointcut(){ } //定义前置通知 @Before("pointcut()") public void before(){ System.out.println("我是前置通知"); } }

    1.3 实现Redis缓存

    1.3.1 需求分析

    1.自定义注解CacheFind 主要被注解标识的方法,则开启缓存的实现. 2.为了将来区分业务,需要在注解中标识key属性,由使用者自行填写. 3.为了用户提供数据超时功能.

    1.3.2 自定义注解

    @Retention(RetentionPolicy.RUNTIME) //该注解什么时候有效 @Target({ElementType.METHOD}) //对方法有效 public @interface CacheFind { String key(); //该属性为必须添加 int seconds() default 0; //设定超时时间 默认不超时 }

    1.3.3 编辑CacheAOP

    package com.jt.aop; import com.jt.anno.CacheFind; import com.jt.pojo.ItemDesc; import com.jt.util.ObjectMapperUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import java.lang.reflect.Method; import java.util.Arrays; @Component //将对象交给spring容器管理 @Aspect //标识我是一个切面 public class CacheAOP { //1.注入缓存redis对象 @Autowired private Jedis jedis; /** * 拦截@CacheFind注解标识的方法. * 通知选择: 缓存的实现应该选用环绕通知 * 步骤: * 1.动态生成key 用户填写的key+用户提交的参数 */ @Around("@annotation(cacheFind)") public Object around(ProceedingJoinPoint joinPoint, CacheFind cacheFind){ //1.如何获取用户在注解中填写的内容呢??? 如何获取注解对象.... String key = cacheFind.key(); //前缀 ITEM_CAT_PARENTID //2.如何获取目标对象的参数呢??? Object[] array = joinPoint.getArgs(); key += "::"+Arrays.toString(array); // "ITEM_CAT_PARENTID::[0]" //3.从redis中获取数据 Object result = null; if(jedis.exists(key)){ //需要获取json数据之后,直接转化为对象返回!! String json = jedis.get(key); //如何获取返回值类型 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Class targetClass = methodSignature.getReturnType(); result = ObjectMapperUtil.toObject(json,targetClass); System.out.println("AOP实现缓存的查询!!!"); }else{ //key不存在,应该查询数据库 try { result = joinPoint.proceed(); //执行目标方法,获取返回值结果 String json = ObjectMapperUtil.toJSON(result); if(cacheFind.seconds()>0){ //判断是否需要超时时间 jedis.setex(key, cacheFind.seconds(), json); }else{ jedis.set(key,json); } System.out.println("AOP执行数据库操作!!!"); } catch (Throwable throwable) { throwable.printStackTrace(); throw new RuntimeException(throwable); } } return result; } /** * AOP = 切入点表达式 + 通知方法. * * 拦截需求: * 1.要求拦截itemCatServiceImpl的bean * 2.拦截com.jt.service下的所有的类 * 3.拦截com.jt.service下的所有类及方法 * 3.1拦截com.jt.service的所有的类.返回值为int类型的.并且add开头 * 的方法.并且参数一个 为String类型 */ //@Pointcut(value = "bean(itemCatServiceImpl)") //@Pointcut("within(com.jt.service..*)") //拦截com.jt.service下的所有类的所有方法的任意参数类型 //@Pointcut("execution(int com.jt.service..*.add*(String))") /* @Pointcut("execution(int com.jt.service..*.add*(String))") public void pointcut(){ } //定义前置通知 @Before("pointcut()") public void before(){ System.out.println("我是前置通知"); }*/ }

    1.3.4 关于环绕通知参数的说明

    否则报错信息如下: 问题二: 其他四大通知了类型是否可以添加ProceedingJoinPoint对象 答案: ProceedingJoinPoint 只能添加到环绕通知中. 报错如下:

    1.3.5 关于JoinPoint方法说明

    /** * 要求: 拦截注解方法 * 打印: * 1.打印目标对象的类型 * 2.打印方法的参数 * 3.获取目标对象的名称及方法的名称 * @param joinPoint */ @Before("@annotation(com.jt.anno.CacheFind)") public void before(JoinPoint joinPoint){ Object target = joinPoint.getTarget(); //获取目标对象 Object[] args = joinPoint.getArgs(); //获取方法参数的 String targetName = joinPoint.getSignature().getDeclaringTypeName(); //获取目标对象的名称 //获取目标对象的类型 Class targetClass = joinPoint.getSignature().getDeclaringType(); //获取目标方法的名称 String methodName = joinPoint.getSignature().getName(); System.out.println(target); System.out.println(args); System.out.println(targetName); System.out.println(targetClass); System.out.println(methodName); }
    Processed: 0.010, SQL: 8