1、pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.meihaocloud</groupId> <artifactId>SpringbootMongodb</artifactId> <version>0.0.1</version> <name>SpringbootMongodb</name> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- springboot 整合 log4j --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.8.RELEASE</version> </dependency> <!--end springboot 整合 log4j --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <!-- <version>3.6</version> --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <!-- 须应用commons-pool2 , 不然会报错 --> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.5.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.properties
server.port=80 #spring.redis.cluster.nodes=192.168.8.115:8001,192.168.8.115:8002,192.168.8.115:8003,192.168.8.115:8004,192.168.8.115:8005,192.168.8.115:8006 #spring.redis.lettuce.pool.max-active=8 #spring.redis.lettuce.pool.max-idle=8 #spring.redis.lettuce.pool.min-idle=0 #spring.redis.lettuce.pool.max-wait=1000ms spring.redis.jedis.pool.max-active=10 spring.redis.jedis.pool.max-idle=5 spring.redis.jedis.pool.min-idle=0 spring.redis.jedis.pool.max-wait=-1ms spring.redis.timeout=6000ms spring.redis.host=192.168.8.161 spring.redis.port=6379
LimitAnno.java 注解类
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @description 限流注解 * * @Inherited:在使用此自定义注解时,如果注解在类上面时,子类会自动继承此注解,否则,子类不会继承此注解。这里一定要记住,使用Inherited声明出来的注解,只有在类上使用时才会有效,对方法,属性等其他无效。 * @Target:表示此注解可以放置的位置。常见的位置有:TYPE=枚举或注解上,FIELD=字段上,METHOD=方法上,PARAMETER=函数形参列表中,CONSTRUCTOR=构造函数上,LOCAL_VARIABLE=局部变量上 等等其他位置。 * @Retention:此注解的生命周期。常见的有:SOURCE=源码时期;CLASS=字节码时期(已编译);RUNTIME=运行时期,通常是用这个的时候要多。 * @Documentd:生成注解文档。 */ @Inherited @Target(ElementType.METHOD) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface LimitAnno { int limit() default 5 ; // 限流数量 int time() default 1 ; // 限流时间,单位秒 Class<?> fallbackClass() default LimitAnno.class ; // 限流后降级的类,不指定就是该Controller String fallbackMethod() default "" ; // 限流后降级的方法, 参数和返回值类型必须和原方法一致 }
注解切面:LimitAspect.java
import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; import javax.annotation.Resource; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; @Component @Aspect public class LimitAspect { private static Log log = LogFactory.getLog(LimitAspect.class); @Resource private RedisTemplate<String, Object> redisTemplate; @Pointcut("@annotation(com.meihaocloud.mongodb.redislock.LimitAnno)") public void limitAspect() { } @Around("limitAspect()") public Object limitAspectA(ProceedingJoinPoint pjp) throws Throwable { // 参数名 Object[] args = pjp.getArgs(); Signature signature = pjp.getSignature(); if (!(signature instanceof MethodSignature)) { return pjp.proceed(args); } MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); LimitAnno limitAnno = method.getAnnotation(LimitAnno.class); if (limitAnno == null) { return pjp.proceed(args); } int limitCount = limitAnno.limit(); int limitTime = limitAnno.time(); Object bean = pjp.getTarget(); // 类名 String className = bean.getClass().getName(); // 方法名 String methodName = signature.getName(); // 根据类名、方法名、参数 生成key String key = "limit_" + className + "_" + methodName; Object result = null; ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue(); Integer count = null; synchronized (log) { // 保证原子操作 count = (Integer) opsForValue.get(key); log.info("count : " + count ); if (count == null || count == 0 || count < 0) { opsForValue.set(key, 1, limitTime, TimeUnit.SECONDS); // 执行方法主体,获取结果值 result = pjp.proceed(args); } else if (count < limitCount) { opsForValue.set(key, count + 1, limitTime, TimeUnit.SECONDS); // 执行方法主体,获取结果值 result = pjp.proceed(args); } else { // 大于limitCount , 执行降级方法 Class<?> backClass = limitAnno.fallbackClass(); Object newInstance = backClass.newInstance(); if (!(newInstance instanceof LimitAnno)) { // 没有配置Class,默认就是该类 bean = newInstance; } String backMethod = limitAnno.fallbackMethod(); if (StringUtils.isNotBlank(backMethod)) { // 没有配置方法,就直接返回null result = MethodUtils.invokeMethod(bean, backMethod, args); } // log.info(result + " ,拒绝访问: thread: " + Thread.currentThread().getId()); } } return result; } }
import java.util.concurrent.TimeUnit; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class RedisController { private static Log log = LogFactory.getLog(LimitAspect.class); @RequestMapping("/test") @LimitAnno(limit = 10, fallbackMethod = "redisTestBack", fallbackClass = LimitCallback.class) public String redisTest(HttpServletRequest request) { String id = "1001"; // 业务处理 return "ok"; } public String redisTestBack(HttpServletRequest request) { // System.out.println("===================限流"); return "limit-error"; } }
LimitCallback.java
import javax.servlet.http.HttpServletRequest; public class LimitCallback { public String redisTestBack(HttpServletRequest request) { // System.out.println("===================限流"); return "limit-error"; } }