文章目录
前言
自定义注解加上切面的编程可以使业务代码和系统代码分开,在不改动基础源码的情况下,在它前后写一些功能,十分优雅。
我个人还是比较喜欢使用这种方式写代码的。
一、Aop的五大通知
1. 前置通知 @Before
在目标方法执行之前执行执行的通知,无论何时都第一个执行。
方法可以无参,也可以有参。
参数为:JoinPoint
2. 后置通知 @AfterReturning
在目标方法执行之后执行的通知,正常执行时第三个执行。
方法可以无参,也可以有参。
参数为:JoinPoint
3. 异常通知 @AfterThrowing
在目标方法抛出异常时执行的通知,出现异常时第三个执行。
4. 最终通知 @After
是在目标方法执行之后执行的通知,无论何时都第二个执行。
和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返回,例如抛出异常,则后置通知不会执行。
而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。
还有,后置通知可以通过配置得到返回值,而最终通知无法得到。
5. 环绕通知 @Around
环绕通知是囊括了以上四种通知的结合体。
只有环绕通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint。
环绕通知需要返回返回值,否则真正调用者将拿不到返回值,只能得到一个null。
环绕通知有控制目标方法是否执行、有控制是否返回值、有改变返回值的能力。(慎用)
二、练习思路
像平常的视频预览、小说预览量、解决接口的幂等性、缓存等诸多问题都可以通过自定义注解加切面的方式实现
三、使用
1. 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 配置
spring:
redis:
port: 6379
host: 127.0.0.1
logging:
level:
com.lss.like: debug
server:
port: 8000
3. 注解
import java.lang.annotation.*;
/**
* ClassName: MyLike
*
* @author Jay01is
* @version 1.0
* @date 2023/3/8 20:17
*/
@Target(ElementType.METHOD) // 这个注解只能加到方法上面
@Documented // 使用类名生成javadoc文档就需要添加这个注解去了
@Retention(value = RetentionPolicy.RUNTIME) // 注解的范围(生命周期) *.java *.class 打包 运行
public @interface MyLike {
}
4. 切面
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* ClassName: CaCheAspect
*
* @author Jay01is
* @version 1.0
* @date 2023/3/8 20:15
*/
@Aspect
@Component
public class CaCheAspect {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 前置通知 记录用户预览量
* @param joinPoint
* @return
* @throws Throwable
*/
@Before("@annotation(com.lss.like.annotation.MyLike)")
public void doBefore(JoinPoint joinPoint) {
// 取出参数 为该帖子的id
Object[] args = joinPoint.getArgs();
// 判断参数是否存在redis 如果存在则为用户已一分钟访问过
if (stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(args[1]), "记录用户预览", 60, TimeUnit.SECONDS)) {
// 为null代表用户在过期时间之内未访问过 记录用户访问量
stringRedisTemplate.opsForZSet().incrementScore("likes", String.valueOf(args[0]), 1);
// 记录用户访问记录 并设置一分钟过期
stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(args[1]), "记录用户预览", 60, TimeUnit.SECONDS);
}
}
}
5. 调用接口
import com.lss.like.annotation.MyLike;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author Jay01is
* @since 2023-03-08
*/
@Slf4j
@RestController
@RequestMapping("/like")
public class LikeController {
@MyLike
@GetMapping("/find/{lid}/{uid}")
@ApiOperation(value = "查询", notes = "查询")
public String find(@PathVariable Integer lid, @PathVariable String uid){
log.info("lid:{},uid:{}",lid,uid);
return "ok";
}
}