相信大家对AOP都不陌生,
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
先简单了解一下AOP的基本概念:
- Aspect(切面):通常是一个类,里面可以定义切入点和通知
- JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
- Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
- Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
- AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
现在我们看看SpringBoot 对于AOP的玩法:
- 添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
-
实例 (用AOP控制ip对于一个接口在规定时间内的访问次数限制)
- 首先可自定义一个注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestLimit {
/**
* ip允许访问的次数,默认值1000
*/
int ipCount() default 5;
/**
* ip时间段,单位为毫秒,默认值一分钟
*/
long ipTime() default 60000;
/**
* uri允许访问的次数,默认值600
*/
int uriCount() default 600;
/**
* uri时间段,单位为毫秒,默认值一分钟
*/
long uriTime() default 60000;
}
- 接下来,实现AOP切面:
@Order(5)
@Aspect
@Component
public class RequestLimitAspect {
private Logger logger = LoggerFactory.getLogger(RequestLimitAspect.class);
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Before("@annotation(com.mention.shophelp.base.annotation.RequestLimit)")
public Object requestLimit(JoinPoint pjp) throws Throwable {
Object target = pjp.getTarget();
//拦截的方法名称
String methodName = pjp.getSignature().getName();
//拦截的放参数类型
Class[] parameterTypes = ((MethodSignature) pjp.getSignature()).getMethod().getParameterTypes();
Class[] clazzs = target.getClass().getInterfaces();
//1.获取类
Class clazz = target.getClass();
if (clazzs != null && clazzs.length > 0) {
clazz = clazzs[0];
}
//2.获取方法
Method m = clazz.getMethod(methodName, parameterTypes);
//3.获取request、callback
Object[] args = pjp.getArgs();
HttpServletRequest request = null;
if (args != null && args.length > 0) {
if (args[0] instanceof HttpServletRequest) {
request = (HttpServletRequest) args[0];
if (request != null) {
String callback = request.getParameter("callback");
}
}
}
String reequestLimitRes = this.RequestLimitCheck(m, request);
if ("fail".equals(reequestLimitRes)) {
return "fail";//返回值改为自己的格式
} else {
Object obj = pjp.toString();
return obj;
}
}
private String RequestLimitCheck(Method m, HttpServletRequest request) throws IOException {
//ip、user_phone+uri 两个维度的访问限制
if (m != null && m.isAnnotationPresent(RequestLimit.class)) {
RequestLimit requestLimit = m.getAnnotation(RequestLimit.class);
//失效时间、访问次数
int ipTime = (int) (requestLimit.ipTime());
int ipCount = requestLimit.ipCount();
InetAddress addr = InetAddress.getLocalHost();
String ip_key = addr.getHostAddress();//获得本机IP
String ipKey = redisTemplate.opsForValue().get((ip_key));//一次读取出多个key_value
Integer ipNumCache = 0;//ip访问次数
if (ipKey != null && ipKey.length() > 0) {
if (StringUtils.isNotBlank(ip_key)) {
ipNumCache = Integer.parseInt(ipKey);
}
}
//ip限制判断
if (ipNumCache == 0) {
redisTemplate.opsForValue().set(ip_key, "1", ipTime, TimeUnit.MILLISECONDS);
request.setAttribute("fail", 0);
} else if (ipNumCache >= ipCount) {
request.setAttribute("fail", 1);
logger.info("request_limit:用户IP[" + ip_key + "],超过了限定的次数[" + ipCount + "]");
} else {
redisTemplate.opsForValue().increment(ip_key, 1);//自增
request.setAttribute("fail", 0);
}
}
return "success";
}
}
- 这时已经大功告成,剩下就是我们在哪里方法上需要,添加上 @RequestLimit 注解即可,
int failNum = Integer.valueOf(request.getAttribute("fail").toString());
if (failNum > 0){
actionResult.setError(-1, "该ip超过了请求次数,请稍后再试");
return actionResult;
}
对于上边注解有不了解的可自行百度,每一次的尝试都是在进步!!!