在开发中,我们经常需要使用spring aop自定义注解来实现一些特定的功能。比如权限拦截,多数据源动态切换。
在编写切面通知实现类中我们需要获取到注解上的值内容。比如方法上的注解,类上的注解,接口上的注解。
AOP的基本概念:
切面(Aspect) :通知和切入点共同组成了切面,时间、地点和要发生的“故事”。
连接点(Joinpoint) :程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。
通知(Advice) :通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。
切入点(Pointcut) :通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称。
目标对象(Target Object) :即被通知的对象。
AOP代理(AOP Proxy) 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理;反之,采用CGLIB代理。
织入(Weaving)把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才能做到,例如AspectJ的织入编译器;
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理是使用了JDK的动态代理。
自定义注解实现接口限流
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
*
* @author xiefulin
* @time 2018年11月25日 下午4:55:44
* @description:接口访问次数限制 默认一分钟300次
*/
public @interface RateLimit {
int timeOutSeconds() default 60;
int maxLimitCount() default 300;
@Target 用于描述注解的使用范围
取值
METHOD 用于描述方法。
PARAMETER 用于描述参数
TYPE 用于描述类或接口(甚至 enum )
@Retention 用于描述的注解在什么范围内有效
RUNTIME 在运行时有效
@Documented 在默认的情况下javadoc命令不会将我们的annotation生成再doc中去的,所以使用该标记就是告诉jdk让它也将annotation生成到doc中去。
@Before("execution(* com.xfl.boot.controller.*.*(..))")
public void doBefore(JoinPoint joinPoint) {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
RateLimit annotation = null;
MethodSignature sign = (MethodSignature) joinPoint.getSignature();
Method method = sign.getMethod();
//获取方法上的注解
annotation = method.getAnnotation(RateLimit.class);
if (annotation == null) {
//获取类上的注解
annotation = joinPoint.getTarget().getClass().getAnnotation(RateLimit.class);
if (annotation == null) {
//获取接口上的注解
for (Class<?> cls : joinPoint.getClass().getInterfaces()) {
annotation = cls.getAnnotation(RateLimit.class);
if (annotation != null) {
break;
}
}
}
}
// 添加注解了的需要增加访问次数限制
if (annotation != null) {
String ip = getIpAddress(request);
String uri = request.getRequestURI();
int timeOutSeconds = annotation.timeOutSeconds();
int maxLimitCount = annotation.maxLimitCount();
StringBuilder limitKey = new StringBuilder("access:rate:limit:");
limitKey.append(ip).append(uri);
Integer count = (Integer) redisTemplate.opsForValue().get(limitKey.toString());
// 超过最大访问次数
if (count != null && count >= maxLimitCount) {
// 打印用户信息
throw new AppExpection(100, "访问频繁");
}
if (count == null) {
redisTemplate.opsForValue().set(limitKey.toString(), 1, timeOutSeconds, TimeUnit.SECONDS);
} else {
redisTemplate.opsForValue().increment(limitKey.toString(), 1);
}
}
}
/**
* 获取ip
*
* @param request
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
参考资料:https://www.cnblogs.com/shipengzhi/articles/2716004.html