Redis是用于处理“缓存”的,当客户端尝试查询某些数据时,服务器端的处理流程大致是:
使用Redis后,可以明显的提高查询效率(当数据表中的数据量大时,效果明显),同时,还能减轻数据库服务器的压力。
在使用之前,还应该确定需要将哪些数据使用Redis处理查询,通常,应该是查询频率可能较高的、允许数据不够准确的(即使数据有一些不准确,但是对整个项目没有严重后果的),甚至这些数据极少改变的。
在具体使用时,可以直接使用RedisTemplate
去操作Redis,也可以对RedisTemplate
的使用进行再次封装。
缓存预热:启动项目时,就将缓存数据加载到Redis中。
在Spring Boot项目中,当需要实现“启动项目时直接执行”的效果,需要自定义组件类,实现ApplicationRunner
接口,重写其中的run()
方法,此run()
将在项目启动成功后自动执行。
提示:缓存预热的操作应该通过
ApplicationRunner
来实现,这样才可以保证在所有组件都已经正确的创建后再执行缓存预热,如果通过某些组件的构造方法来编写缓存预热的代码,此时某些组件可能还没有创建,则无法正确执行。
关于缓存预热的具体实现:
更新缓存的策略有多种,通常使用的可以是:
关于自动更新,需要使用到“计划任务”。
使用计划任务,需要自定义组件类,然后,在类中自定义方法(应该是public
权限,返回值类型声明为void
,参数列表为空),这个方法将作为计划任务执行的方法,在此方法上需要添加@Scheduled
注解,并配置其执行频率或特定的执行时间,最后,还需要在配置类上使用@EnableScheduling
注解,以开启当前项目的计划任务。
AOP:Aspect Oriented Programming,面向切面编程
注意:AOP并不是Spring框架独有的技术或特点,即使没有使用Spring框架,也可以实现AOP,但是,Spring框架很好的支持了AOP,所以,通常会使用Spring来实现AOP。
在开发实践中,数据的处理流程大致是:
- 注册:客户端 <---(请求)---> Controller <------> Service <------> Mapper
-
- 登录:客户端 <---(请求)---> Controller <------> Service <------> Mapper
-
- 下单:客户端 <---(请求)---> Controller <------> Service <------> Mapper
假设,现在添加一个需求:统计每个业务(Service中的方法)的执行耗时。
在没有AOP的情况下,只能编辑每个Service方法,添加几乎相同代码来实现以上需求,并且,当需求发生变化时,每个Service方法可能需要再次调整。
使用AOP实现以上需求,大致需要:
在项目中添加spring-boot-starter-aop
依赖。
在项目的根包下创建aop.TimerAspect
类,在类上添加@Component
和@Aspect
注解
- long start = System.currentTimeMillis();
-
- xxx;
-
- long end = System.currentTimeMillis();
-
- long t = end - start;
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-aopartifactId>
- dependency>
- package cn.tedu.csmall.product.aop;
-
- import lombok.extern.slf4j.Slf4j;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.springframework.stereotype.Component;
-
- @Slf4j
- @Component
- @Aspect
- public class TimerAspect {
-
- // 【切面中的方法】
- // 访问权限:public
- // 返回值类型:当使用@Around时,使用Object类型
- // 方法名称:自定义
- // 参数列表:当使用@Around时,添加ProceedingJoinPoint类型的参数
- // 异常:当使用@Around时,抛出Throwable
-
- // @Before:在……之前
- // @After:在……之后
- // @AfterReturning:在返回之后
- // @AfterThrowing:在抛出异常之后
- // @Around:环绕
-
- // 关于以上注解,大致是:
- // @Before
- // try {
- // pjp.proceed();
- // @AfterReturning
- // } catch (Throwable e) {
- // @AfterThrowing
- // throw e;
- // } finally {
- // @After
- // }
-
- // 注解中的表达式用于匹配某些方法,在表达式中,应该表示出“返回值类型 包.类.方法(参数列表)”
- // 以表达式中,可以使用星号(*)和2个连续的小数点(..)作为通配符
- // 其中,星号可以用于:返回值类型、包名、类名、方法名、参数
- // 而2个连续的小数点可以用于:包、参数
- // 星号只表示匹配1次,而2个连续的小数点表示匹配0~n次
-
- // 连接点:JoinPoint,表现为切面需要处理的某个方法,或其它的某种行为
- // 切入点:Point Cut,写在@Around等注解中的表达式
- // 通知:Advice,即@Around等注解及对应的代码
- // 切面:Aspect,囊括了切入点和通知的模块
-
- @Around("execution(* cn.tedu.csmall.product.service.impl.*.*(..))")
- public Object timer(ProceedingJoinPoint pjp) throws Throwable {
- // 【需求】统计每个Service方法的耗时
- log.debug("在某个Service的某个方法之前执行了……");
-
- long start = System.currentTimeMillis();
-
- // 执行(处理)连接点,即执行业务方法
- // 注意:必须获取调用proceed()方法的返回值
- // 注意:如果连接点是Service中的方法,调用proceed()时的异常必须声明抛出,不可以try...catch
- Object result = pjp.proceed();
-
- long end = System.currentTimeMillis();
- log.debug("当前切面匹配到的组件类:{}", pjp.getTarget());
- log.debug("当前切面匹配到的方法:{}", pjp.getSignature());
- log.debug("当前切面匹配到的方法的参数列表:{}", pjp.getArgs());
- log.debug("执行耗时:{}毫秒", end - start);
-
- // 注意:必须返回调用proceed()方法的结果
- return result;
- }
-
- }