手写spring(五)------------------------------------------------------------------依赖注入、关联映射、请求分发

项目地址:https://github.com/gongxianshengjiadexiaohuihui/noobspring

依赖注入

 private void doAutowired(){
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>开始注入");
        if(ioc.isEmpty()){
            return;
        }
        for(Map.Entry<String,Object> entry : ioc.entrySet()){
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for(Field field : fields){
                if(!field.isAnnotationPresent(NBAutowired.class)){
                    continue;
                }
                String beanName = field.getAnnotation(NBAutowired.class).value();
                if("".equals(beanName)){
                    beanName = StringUtil.lowerFirstCase(field.getType().getSimpleName());

                }
                /**
                 * 在字段是私有变量的时候,也能获得访问权限
                 */
                field.setAccessible(true);

                try {
                    /**
                     * 给entry.getValue这个对象的这个字段field赋值(从ioc容器中拿到对应beanName的bean),请注意是对象
                     */
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (Exception e) {
                    logger.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>注入失败");
                    e.printStackTrace();
                    throw new RuntimeException("注入失败");
                }
            }

        }
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>注入完成");
    }

遍历ioc容器,找到被@NBAutowired标记的字段,开启字段的访问权限,从ioc容器中获取对应类型的实例进行赋值操作。field.getType就是获取类型,来自那个类,将首字母小写就可以对应到ioc中的key

关联映射

private void initHandlerMapping(){
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>开始关联映射");
        if(ioc.isEmpty()){
            return;
        }
        for(Map.Entry<String,Object> entry: ioc.entrySet()){
            Class<?> clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(NBController.class)){
                continue;
            }
            String baseUrl = "";
            /**
             * 获得@Controller的父级的url
             */
            if(clazz.isAnnotationPresent(NBRequestMapping.class)){
                baseUrl = clazz.getAnnotation(NBRequestMapping.class).value();
            }
            Method[] methods = clazz.getMethods();
            for(Method method : methods){
                if(!method.isAnnotationPresent(NBRequestMapping.class)){
                    continue;
                }

                /**
                 * 将url和方法关联并存储在handlerMapping中(replaceAll和split支持正则表达式)
                 */
                String url = ("/" + baseUrl + "/" + method.getAnnotation(NBRequestMapping.class).value()).replaceAll("/+","/");
                handlerMapping.put(url,method);
                logger.debug("mapped:{},{}",url,method);
            }
        }
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>关联映射完成");
    }

这里主要处理@NBRequestMapping注解标记的类或方法,如果标记的是类,将注解的值作为父路径,添加到路径中,如果修饰的是方法,则在前面的基础上添加路径,并将url和方法关联起来url作为key,method作为value存在类型为hashMap的handlerMapping中。

浏览器请求的方法默认是get方法,我们重写HttpServlet的get方法

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            this.doDispatch(req, resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Exception,Detail:\r\n" +e.getMessage() + "\r\n" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]","").replaceAll(",\\s","\r\n"));
            e.printStackTrace();

        }
    }

请求转发的实现

 private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        if(handlerMapping.isEmpty()){
            return;
        }
        String url = req.getRequestURI();
        /**
         * 返回站点的根目录
         * 比如我们访问的地址是http://localhost:8080/demo/data/remove
         * demo是我们的项目  对应的contextPath就是http://localhost:8080/demo
         */
        String contextPath = req.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");
        if(!handlerMapping.containsKey(url)){
            resp.getWriter().write("404 Not Found!");
            return;
        }
        /**
         * 获取请求参数列表
         */
        Map<String,String[]> params = req.getParameterMap();

        /**
         * 根据url拿到对应的方法
         */
        Method method = this.handlerMapping.get(url);
        /**
         * 获取方法的形参列表
         */
        Parameter[] parameters = method.getParameters();
        Annotation annotation[][] = method.getParameterAnnotations();
        /**
         * 保存参数值
         */
        Object[] paramValues = new Object[parameters.length];
        /**
         * 根据形参列表,按顺序获取请求参数列表的值
         */
        for(int i =0; i < parameters.length; i++){
            if(parameters[i].getType() == HttpServletRequest.class){
                paramValues[i] = req;
                continue;
            }
            if(parameters[i].getType() == HttpServletResponse.class){
                paramValues[i] = resp;
                continue;
            }
            if(parameters[i].getType() == String.class){
                String name = parameters[i].getName();
                /**
                 * 按照@NBRequestParam的value 从请求参数找值
                 */
                if(annotation[i].length != 0 & annotation[i][0].annotationType() == NBRequestParam.class){
                    /**
                     * 类型转换,猜测里面存的值应该是一个注解的泛型
                     */
                    name = ((NBRequestParam)annotation[i][0]).value();
                    if(!params.containsKey(name)){
                        throw new RuntimeException("The required parameter does not exist!   " + name );
                    }
                }
                paramValues[i] = params.get(name)[0];
                continue;
            }

        }
        /**
         * 通过反射获取实体对象,并通过反射调用方法
         */
        String beanName = StringUtil.lowerFirstCase(method.getDeclaringClass().getSimpleName());
        method.invoke(this.ioc.get(beanName),paramValues);


    }

我们首先拿到请求的路径,去掉站点目录路径,拿到处理后的路径从handlerMapping中得到对应的方法,接着我们仍需要处理最后一个注解@NBRequestParam(目前只支持标记String类型,后续会完善)

主要是对应关系,我们需要把请求的参数,正确的赋给处理请求的方法。

很关键的一点是获取方法的形参列表,这个是有序的一维数组

然后获取方法的注解列表,这个是有序的二维数组。

获取请求参数列表,这个是无序的Map类型。

我的思路是利用有序的形参列表,去遍历,遇到String类型的,就判断其修饰的注解,如果存在@NBRequestParam,就获取值作为key去请求参数列表的找,找到,就保存在参数值中

最后一步通过反射获取实体对象,并调用方法,反射真的很强大,但是发射会影响性能,也不能乱用。

至此,工作已经完成,可以跑起来了

我的项目里有个demo来测试

运行结果

猜你喜欢

转载自blog.csdn.net/qq_33543634/article/details/87089466