说明:
spring的两个主要特性就是IOC和AOP
IOC: 控制反转,简单来说就是自己不创建对象,把对象的创建和管理交给spring容器,我们直接使用,Spring 的核心就是一个大的工厂容器
AOP: Spring 提供了面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等切面功能。
那么实现AOP一般都是通过 @Aspect 注解去自定义切面类
● 切面(Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
● 连接点(Joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器,根据连接点可以获取请求的参数等信息
● 切点(Pointcut):对连接点进行拦截的定位
● 通知(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,也可以称作增强
● 目标对象 (Target):代理的目标对象
AOP 一般有 5 种环绕方式:
● 前置通知 (@Before)
● 返回通知 (@AfterReturning)
● 异常通知 (@AfterThrowing)
● 后置通知 (@After)
● 环绕通知 (@Around)
实操
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--Redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、自定义接口访问注解
/**
* 接口访问限制枚举
* @author ppp
* @date 2023/2/14
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
/**
* @return 访问最大次数
*/
int accessMaxTimes() default 60;
/**
* @return 时间
*/
long timeOut() default 1;
/**
* @return 时间单位
*/
TimeUnit timeUnit() default TimeUnit.MINUTES;
}
3、自定义切面类
/**
* 接口访问限制Aop配置
* @author ppp
* @date 2023/2/14
*/
@Aspect
@Component
public class AccessLimitAopConfig {
private final static String KEY = "Redis_accessLimit_key";
@Autowired
private RedisTemplate redisTemplate;
/**
* 定义切点为注解
*/
@Pointcut("@annotation(com.nw.test.configure.annotation.AccessLimit)")
public void AccessLimitPointcut(){
}
@Before(value = "AccessLimitPointcut()")
public void handleAccessLimit(JoinPoint joinPoint){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
AccessLimit annotation = AnnotationUtils.findAnnotation(method, AccessLimit.class);
int accessMaxTimes = annotation.accessMaxTimes();
long timeOut = annotation.timeOut();
TimeUnit timeUnit = annotation.timeUnit();
// 如果redis不存在或已经过期
Long expire =redisTemplate.opsForValue().getOperations().getExpire(KEY);
if (!redisTemplate.hasKey(KEY) || Objects.requireNonNull(expire).intValue() < 0) {
redisTemplate.opsForValue().set(KEY, 1, timeOut, timeUnit);
} else {
long increment = redisTemplate.opsForValue().increment(KEY).intValue();
if (increment > accessMaxTimes) {
throw new RuntimeException("访问已经超过最大值: " + accessMaxTimes);
}
}
}
}
4、配置redis
/**
* Redis管理配置
*/
@Configuration
public class RedisConfigurer {
/**
* 设置 redisTemplate 的序列化设置
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
template.setValueSerializer(genericToStringSerializer);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer(Charset.forName("UTF-8")));
template.setHashValueSerializer(new StringRedisSerializer(Charset.forName("UTF-8")));
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.getRedisCacheConfigurationWithTtl(24 * 60 * 60), // 默认策略,未配置的 key 会使用这个
this.getRedisCacheConfigurationMap() // 指定 key 策略
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
5、使用
/**
* 一分钟内最多请求10次
* @param content
*/
@AccessLimit(accessMaxTimes = 10, timeOut = 1, timeUnit = TimeUnit.MINUTES)
@GetMapping("/sendEvent")
public void sendEvent(@RequestParam String content) {
testService.send(content);
}