• JSD-2204-Redis缓存实战-Spring AOP-Day18


    1.在项目中应用Redis

    Redis是用于处理“缓存”的,当客户端尝试查询某些数据时,服务器端的处理流程大致是:

    • 优先从Redis中获取数据
      • 如果Redis中没有所需的数据,则从数据库中查询,并将查询结果存入到Redis
    • 将Redis中的数据(或:刚刚从数据库中查询出来的数据)响应到客户端

    使用Redis后,可以明显的提高查询效率(当数据表中的数据量大时,效果明显),同时,还能减轻数据库服务器的压力。

    在使用之前,还应该确定需要将哪些数据使用Redis处理查询,通常,应该是查询频率可能较高的、允许数据不够准确的(即使数据有一些不准确,但是对整个项目没有严重后果的),甚至这些数据极少改变的。

    在具体使用时,可以直接使用RedisTemplate去操作Redis,也可以对RedisTemplate的使用进行再次封装。

    1.1关于缓存预热

    缓存预热:启动项目时,就将缓存数据加载到Redis中。

    在Spring Boot项目中,当需要实现“启动项目时直接执行”的效果,需要自定义组件类,实现ApplicationRunner接口,重写其中的run()方法,此run()将在项目启动成功后自动执行。

    提示:缓存预热的操作应该通过ApplicationRunner来实现,这样才可以保证在所有组件都已经正确的创建后再执行缓存预热,如果通过某些组件的构造方法来编写缓存预热的代码,此时某些组件可能还没有创建,则无法正确执行。

    关于缓存预热的具体实现:

    • 删除所有相关的缓存数据
      • 删除列表数据:如果不删除,再次向缓存中写入列表,将是在原列表的基础上追加,则会产生重复的列表项
      • 删除数据项(每一个数据):如果不删除,则会导致原本已经缓存的数据一直存在,某些数据可能在数据库中已经删除,则缓存中的数据也应该被删除
    • 从数据库中查询列表,并写入到缓存
    • 基于查询到的列表,遍历,得到每个数据的id,再从数据库中查出各数据,并写入到缓存

    1.2关于更新缓存

    更新缓存的策略有多种,通常使用的可以是:

    • 手动更新
      • 适用于数据变化频率非常低的应用场景,这些数据的缓存可以是长期存在,偶尔需要更新时,手动更新即可
    • 自动更新
      • 适用于数据频繁的变化,通过手动更新不太现实,将会是每间隔一段时间,或在特定的某个时间(例如每周一凌晨3点)自动更新

    关于自动更新,需要使用到“计划任务”。

    使用计划任务,需要自定义组件类,然后,在类中自定义方法(应该是public权限,返回值类型声明为void,参数列表为空),这个方法将作为计划任务执行的方法,在此方法上需要添加@Scheduled注解,并配置其执行频率或特定的执行时间,最后,还需要在配置类上使用@EnableScheduling注解,以开启当前项目的计划任务。

    2.Spring AOP

    AOPAspect Oriented Programming,面向切面编程

    注意:AOP并不是Spring框架独有的技术或特点,即使没有使用Spring框架,也可以实现AOP,但是,Spring框架很好的支持了AOP,所以,通常会使用Spring来实现AOP。

    在开发实践中,数据的处理流程大致是:

    1. 注册:客户端 <---(请求)---> Controller <------> Service <------> Mapper
    2. 登录:客户端 <---(请求)---> Controller <------> Service <------> Mapper
    3. 下单:客户端 <---(请求)---> Controller <------> Service <------> Mapper

    假设,现在添加一个需求:统计每个业务(Service中的方法)的执行耗时。

    在没有AOP的情况下,只能编辑每个Service方法,添加几乎相同代码来实现以上需求,并且,当需求发生变化时,每个Service方法可能需要再次调整。

    使用AOP实现以上需求,大致需要:

    • 创建切面类,并交给Spring框架管理
    • 配置切面类中的方法在特定的点执行

    在项目中添加spring-boot-starter-aop依赖。

    在项目的根包下创建aop.TimerAspect类,在类上添加@Component@Aspect注解

    1. long start = System.currentTimeMillis();
    2. xxx;
    3. long end = System.currentTimeMillis();
    4. long t = end - start;

     2.1使用Spring AOP

    2.1.1添加依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-aopartifactId>
    4. dependency>

    2.1.2创建配置类

    1. package cn.tedu.csmall.product.aop;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.aspectj.lang.ProceedingJoinPoint;
    4. import org.aspectj.lang.annotation.Around;
    5. import org.aspectj.lang.annotation.Aspect;
    6. import org.springframework.stereotype.Component;
    7. @Slf4j
    8. @Component
    9. @Aspect
    10. public class TimerAspect {
    11. // 【切面中的方法】
    12. // 访问权限:public
    13. // 返回值类型:当使用@Around时,使用Object类型
    14. // 方法名称:自定义
    15. // 参数列表:当使用@Around时,添加ProceedingJoinPoint类型的参数
    16. // 异常:当使用@Around时,抛出Throwable
    17. // @Before:在……之前
    18. // @After:在……之后
    19. // @AfterReturning:在返回之后
    20. // @AfterThrowing:在抛出异常之后
    21. // @Around:环绕
    22. // 关于以上注解,大致是:
    23. // @Before
    24. // try {
    25. // pjp.proceed();
    26. // @AfterReturning
    27. // } catch (Throwable e) {
    28. // @AfterThrowing
    29. // throw e;
    30. // } finally {
    31. // @After
    32. // }
    33. // 注解中的表达式用于匹配某些方法,在表达式中,应该表示出“返回值类型 包.类.方法(参数列表)”
    34. // 以表达式中,可以使用星号(*)和2个连续的小数点(..)作为通配符
    35. // 其中,星号可以用于:返回值类型、包名、类名、方法名、参数
    36. // 而2个连续的小数点可以用于:包、参数
    37. // 星号只表示匹配1次,而2个连续的小数点表示匹配0~n次
    38. // 连接点:JoinPoint,表现为切面需要处理的某个方法,或其它的某种行为
    39. // 切入点:Point Cut,写在@Around等注解中的表达式
    40. // 通知:Advice,即@Around等注解及对应的代码
    41. // 切面:Aspect,囊括了切入点和通知的模块
    42. @Around("execution(* cn.tedu.csmall.product.service.impl.*.*(..))")
    43. public Object timer(ProceedingJoinPoint pjp) throws Throwable {
    44. // 【需求】统计每个Service方法的耗时
    45. log.debug("在某个Service的某个方法之前执行了……");
    46. long start = System.currentTimeMillis();
    47. // 执行(处理)连接点,即执行业务方法
    48. // 注意:必须获取调用proceed()方法的返回值
    49. // 注意:如果连接点是Service中的方法,调用proceed()时的异常必须声明抛出,不可以try...catch
    50. Object result = pjp.proceed();
    51. long end = System.currentTimeMillis();
    52. log.debug("当前切面匹配到的组件类:{}", pjp.getTarget());
    53. log.debug("当前切面匹配到的方法:{}", pjp.getSignature());
    54. log.debug("当前切面匹配到的方法的参数列表:{}", pjp.getArgs());
    55. log.debug("执行耗时:{}毫秒", end - start);
    56. // 注意:必须返回调用proceed()方法的结果
    57. return result;
    58. }
    59. }

  • 相关阅读:
    后端给你返回文件流,前端实现下载文件
    二叉树的遍历
    17_Vue列表过滤_js模糊查询
    Kubernetes 集群中流量暴露的几种方案
    Linux操作系统学习(运维必会)
    Shiro 550、721
    阿里提前批(阿里云)一面30min
    舒服,给Spring贡献一波源码。
    【LeetCode-中等题】515. 在每个树行中找最大值
    yolov5 训练
  • 原文地址:https://blog.csdn.net/TheNewSystrm/article/details/126372170