一起来写个SpringBoot[2] — — 设置请求路由

项目地址:https://github.com/xiaogou446/jsonboot
本节延续第一节的内容,branch:feature/annotatedClassScanner
命令行:git checkout feature/annotatedClassScanner

设置请求路由

上节写到请求从Netty建立到Http服务器中进入,并通过Handler请求处理器分别处理Get请求与Post请求,可是Get请求有很多,对应处理的Get方法也有很多,我们需要找到对应请求的请求处理器,再对请求进行处理。

参照SpringBoot中定义路由的注解,由于我们是只返回json的数据,那么直接定义@RestController注解、@GetMapping 和 PostMapping注解进行路由。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface RestController {

    String value() default "";

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface GetMapping {

    String value() default "";

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface PostMapping {
    String value() default "";
}

通过Reflections定义一个扫描特定路径下该注解类的功能,通过该方法,我们能直到对应路径下比如com.df路径下有 @RestController注解标注的类有哪些。

    /**
     * 实现路径内注解扫描功能
     *
     * @param packageName 需要扫描的包路径
     * @param annotation 需要在包路径内寻找类的注解
     * @return 包路径内包含该注解的类
     */
    public Set<Class<?>> scan(String packageName, Class<? extends Annotation> annotation){
        //初始化工具类 指定包名和注解对应的类型
        Reflections reflections = new Reflections(packageName, new TypeAnnotationsScanner());
        //获取某个包下对应功能的注解类
        Set<Class<?>> annotationClass = reflections.getTypesAnnotatedWith(annotation, true);
        log.info("annotationClass : {}, size: {}", annotationClass, annotationClass.size());
        return annotationClass;
    }

定义两个Map,用来存储对应的方法路径,比如@RestController("/user") + @GetMapping("/findName") 那么路径为 /user/findName ,map的key为路径,value为Method

    /**
     * Get方法映射路径
     */
    public static Map<String, Method> getMethodMapping = new HashMap<>();

    /**
     * Post方法映射
     */
    public static Map<String, Method> postMethodMapping = new HashMap<>();

那么思路就明确了,先通过反射包扫描到 @RestController所标注的类,记录类路由,后遍历每个类中的方法,如果标有 @GetMapping@PostMapping,就将其中的value值记录,与@RestController中的值拼接,存到对应的Map中。

    /**
     * 根据注解进行访问路径的拼接
     *
     * @param packageName 需要进行扫描的包名
     */
    public void loadRoutes(String packageName){
        AnnotatedClassScanner annotatedScanner = new AnnotatedClassScanner();
        Set<Class<?>> scan = annotatedScanner.scan(packageName, RestController.class);
        for (Class<?> aClass : scan){
            // 从扫描的类中获取该注解信息
            RestController restController = aClass.getAnnotation(RestController.class);
            String baseUri = restController.value();
            Method[] methods = aClass.getMethods();
            //获取方法映射
            loadMethodRoutes(baseUri, methods);
            classMapping.put(baseUri, aClass);
        }
        System.out.println(classMapping);
        System.out.println(getMethodMapping);
        System.out.println(postMethodMapping);
    }

    /**
     * 添加方法注解的路径映射
     *
     * @param baseUri 从类注解中解析的基础uri
     * @param methods 该类中的方法
     */
    private void loadMethodRoutes(String baseUri, Method[] methods){
        for (Method method : methods){
            if (method.isAnnotationPresent(GetMapping.class)){
                GetMapping getMapping = method.getAnnotation(GetMapping.class);
                getMethodMapping.put(baseUri + getMapping.value(), method);
                //如果有getMapping在上面了,则不进行postMapping的使用
                continue;
            }
            if (method.isAnnotationPresent(PostMapping.class)){
                PostMapping postMapping = method.getAnnotation(PostMapping.class);
                postMethodMapping.put(baseUri + postMapping.value(), method);
            }
        }
    }

处理Get请求

上述定义好路由后,也就可以根据对应的请求uri,在GetMapping中找到对应的处理方法,通过反射进行执行。

在处理Get的请求中,同时也会用到 @RequestParam 注解,@RequestParam注解可以做到参数映射的效果,即 @RequestParam的value值可以与外部传入的key对应获取值。

/**
 * 用于标注传入参数映射
 *
 * @author qinghuo
 * @since 2021/03/22 9:34
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@Documented
public @interface RequestParam {

    String value();

}

在处理Get请求时,也会解析请求的uri,获取请求uri上的参数,转化为queryParamMap,后将解析的path为key,在之前定义的GetMapping中获取到对应的处理方法。

获取到方法后就是对参数的处理,我们需要定义好每个参数的值与类型,才能进行反射调用方法。遍历每个方法的每一个参数,获取到参数的类型,为后续的参数转换类型埋下伏笔。判断每个参数上是否标有 @RequestParam注解,如果有则获取value值作为key,不然就按默认的参数名称为key,在GetMapping中取到对应参数的值。将值记录后反射调用方法获取执行结果。

@Slf4j
public class GetRequestHandler implements RequestHandler {

    @Override
    public Object handler(FullHttpRequest fullHttpRequest) {

        QueryStringDecoder queryDecoder = new QueryStringDecoder(fullHttpRequest.uri(), Charsets.toCharset(CharEncoding.UTF_8));
        //获取参数列表
        Map<String, String> queryParamMap = UrlUtils.getQueryParam(queryDecoder);
        String path = queryDecoder.path();
        Method method = Route.getMethodMapping.get(path);
        if (method == null){
            return null;
        }
        log.info("request path: {}, method: {}", path, method.getName());
        //获取到该方法到参数
        Parameter[] parameters = method.getParameters();
        List<Object> params = new ArrayList<>();
        for (Parameter parameter : parameters){
            RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
            //获取每个参数的类型
            Class<?> type = parameter.getType();
            //定义最终参数
            String paramValue;
            //有注解的情况
            if (requestParam != null){
                //获取注解设置的value值
                String paramKey = requestParam.value();
                //以注解设置的value值为key去参数map中取出值
                paramValue = queryParamMap.get(paramKey);
            }else{
                //如果没有注解,则直接进行名称对应查找
                paramValue = queryParamMap.get(parameter.getName());
            }
            params.add(ObjectUtils.convertToClass(type, paramValue));
        }
        return ReflectionUtil.executeMethod(method, params.toArray());
    }

}

其实这里会出现一个问题!在我们获取到参数并存入List params时需要进行一个参数的转换,从uri上切取下的参数是String类型的,而我们最终需要的却是其他类型,这必须进行一个参数的转换。一开始没有找到什么好的方式…只能自己使用一个比较愚蠢的方法,后来看了大佬的实现才明白还有这种操作。


    /**
     * 转换String类型对象为目标类型对象
     *
     * @param targetClass 需要转换的目标类型
     * @param content String文本对象
     * @return 转换后的对象
     */
    public static Object convertToClass(Class<?> targetClass, String content){
        PropertyEditor editor = PropertyEditorManager.findEditor(targetClass);
        editor.setAsText(content);
        return editor.getValue();
    }

     /**
     * 用于将String对象转换为需要的类型对象(已废弃,使用PropertyEditorManager代替)
     *
     * @param type 原类型
     * @param str 需要转换的字符串
     * @return 转换后的对象
     */
    @Deprecated
    public static Object getNumber(Class<?> type,String str) {
        Class<?>[] paramsClasses = { str.getClass() };
        Object[] params = { str };
        Class<?> typeClass = ObjectUtils.convertBaseClass(type);
        Object o = null;
        try {
            Constructor<?> c = typeClass.getConstructor(paramsClasses);
            o = c.newInstance(params);
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return o;
    }

    /**
     * 如果是基本数据类型就转换为其包装类 (已废弃)
     *
     * @param type 传入的类型
     * @return 处理后的类型
     */
    @Deprecated
    public static Class<?> convertBaseClass(Class<?> type){
        switch (type.getTypeName()){
            case "int":
                return Integer.class;
            case "short":
                return Short.class;
            case "long":
                return Long.class;
            case "boolean":
                return Boolean.class;
            case "byte":
                return Byte.class;
            case "float":
                return Float.class;
            case "char":
                return Character.class;
            case "double":
                return Double.class;
            default:
                return type;
        }
    }

测试

在这里插入图片描述
在这里插入图片描述

本章基本完成了请求路由的设置以及部分基本注解的实现。

下一节:一起来写个SpringBoot[3] — — 完成Post请求

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值