文章目录

  • 历史
  • 前言
  • 框架版本
  • 实现方式
  • 自定义注解编写
  • 编写切面切点
  • 逻辑编写
  • 编写测试类
  • 问题扩充
  • 完整代码


历史

之前写了个更加复杂的接口版本控制,发现后期不利于阅读,尝试采取AOP思想写了个简单的。
old 接口版本控制

前言

本篇博客需要实现的内容:

使用AOP思想,编写一个接口版本控制。
要求:
低于限定版本的接口不允许访问!

框架版本

  • springboot 2.x

实现方式

自定义注解编写

编写一个自定义注解,放置于指定controller的方法上

@Target({ElementType.METHOD}) //用于方法
@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Documented //这个工具被java doc 收录
public @interface ApiVersion {

    /**
     * 携带参数,默认为1
     * @return
     */
    int value() default 1;
}

编写切面切点

/**
     * 定义切点
     */
@Pointcut("@annotation(cn.linkpower.dtuAuthPlatform.config.apiVersion.ApiVersionConfig.ApiVersion)")
public void resources(){ }

此处增强方式采取@Around 环绕增强

@Around("resources()")
public Object check(ProceedingJoinPoint pjp) throws Throwable {
	return null;
}

逻辑编写

1、当该注解修饰指定方法时,触发@Around中的逻辑。
2、获取对应的HttpServletRequest对象。
3、根据HttpServletRequest对象。拿到该请求接口实际地址信息。
4、正则比对是否是指定的接口。
5、如果是符合要求的接口,则获取该接口上指定的 @ApiVersion注释对象。
6、根据注释对象,获取其中设置的初始值。
7、比较初始值,如果初始值符合要求,则继续执行;否则不继续执行并返回提示信息。

上面的描述还是有点多,这就是下面代码的实现思想。具体看代码:

@Around("resources()")
public Object check(ProceedingJoinPoint pjp) throws Throwable {
    // 1、如果方法上标识了该注解
    // 拿到请求
    ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = requestAttributes.getRequest();
    // 获取请求的uri
    String requestURI = request.getRequestURI();
    log.info("请求的url:{}",request.getRequestURL());
    log.info("请求的uri:{}",requestURI);
    // 2、第二重校验,判断是否符合要求
    Matcher matcher = VERSION_PREFIX_PATTERN.matcher(requestURI);
    if(matcher.find()){
        // 3、符合正则表达式,则获取其中的值
        log.info("matcher-->{}",matcher);
        Integer version = Integer.valueOf(matcher.group(1));
        log.info("version--->{}",String.valueOf(version));
        // 获取注解中设定的值
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        // 获取对应方法对象
        Method method = ms.getMethod();
        // 通过方法对象,根据反射获取方法上的注解
        ApiVersion annotation = method.getAnnotation(ApiVersion.class);
        Integer oldVersion = annotation.value();
        log.info("oldVersion-->{}",oldVersion);
        // 4、判断是否符合要求
        if((version - oldVersion) >= 0){
            // 继续执行
            return pjp.proceed();
        }
    }
    // 这里应该返回一个json,或者抛出一个自定义异常
    return "请求无效。。。";
}

编写测试类

@RestController
public class Controller {
    @ApiVersionConfig.ApiVersion(2)
    @RequestMapping("{version}/test1")
    public String test1(@RequestParam(value = "id",required = false) String id) throws MyException {
        if("1".equals(id)){
            throw new MyException("异常测试。。。。");
        }
        return "666";
    }
}

请求测试,按照规定,必须>= 2才能走上述接口,否则失败。

  • >= 2
http://localhost:10025/v2/test1?id=66

java 控制接口发送频率 java接口版本控制_自定义注解


java 控制接口发送频率 java接口版本控制_spring_02


java 控制接口发送频率 java接口版本控制_spring_03

  • < 2
http://localhost:10025/v1/test1?id=66

java 控制接口发送频率 java接口版本控制_java_04


java 控制接口发送频率 java接口版本控制_java_05

问题扩充

  • 为什么使用Around?这里使用Before不更好么?

这里需要判断,如果不符合要求的,则需要禁止继续执行。

查看源码,发现ProceedingJoinPoint类中具有proceed()方法,调用则可以继续执行后续逻辑。
并且,ProceedingJoinPoint只能用于Around

完整代码

这次就不放置于github了,下面代码为核心代码:

package cn.linkpower.dtuAuthPlatform.config.apiVersion;

import org.aspectj.lang.ProceedingJoinPoint;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Aspect
@Component
public class ApiVersionConfig {
    private Logger log = LoggerFactory.getLogger(ApiVersionConfig.class);

    // 正则
    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");

    /**
     * 自定义注解,实现接口版本管理
     */
    @Target({ElementType.METHOD}) //用于方法
    @Retention(RetentionPolicy.RUNTIME) //运行时有效
    @Documented //这个工具被java doc 收录
    public @interface ApiVersion {

        /**
         * 携带参数,默认为1
         * @return
         */
        int value() default 1;
    }
    /**
     * 定义切点
     */
    @Pointcut("@annotation(cn.linkpower.dtuAuthPlatform.config.apiVersion.ApiVersionConfig.ApiVersion)")
    public void resources(){ }

    @Around("resources()")
    public Object check(ProceedingJoinPoint pjp) throws Throwable {
        // 1、如果方法上标识了该注解
        // 拿到请求
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        // 获取请求的uri
        String requestURI = request.getRequestURI();
        log.info("请求的url:{}",request.getRequestURL());
        log.info("请求的uri:{}",requestURI);
        // 2、第二重校验,判断是否符合要求
        Matcher matcher = VERSION_PREFIX_PATTERN.matcher(requestURI);
        if(matcher.find()){
            // 3、符合正则表达式,则获取其中的值
            log.info("matcher-->{}",matcher);
            Integer version = Integer.valueOf(matcher.group(1));
            log.info("version--->{}",String.valueOf(version));
            // 获取注解中设定的值
            MethodSignature ms = (MethodSignature) pjp.getSignature();
            // 获取对应方法对象
            Method method = ms.getMethod();
            // 通过方法对象,根据反射获取方法上的注解
            ApiVersion annotation = method.getAnnotation(ApiVersion.class);
            Integer oldVersion = annotation.value();
            log.info("oldVersion-->{}",oldVersion);
            // 4、判断是否符合要求
            if((version - oldVersion) >= 0){
                // 继续执行
                return pjp.proceed();
            }
        }
        // 这里应该返回一个json,或者抛出一个自定义异常
        return "请求无效。。。";
    }
}