手寫一個springx(一)

源碼:https://github.com/zhaoyunxing92/springx

前言

如果你稍微留意下commit就會這個項目早在好2個月前就創建了,我也一直想寫下,但是確實太懶了就一直拖着着。這幾天由於沒有太多開發任務就有想寫了。

依賴環境

  • jdk:1.8

  • tomcat8.5

  • servlet3.1.0

  • maven3.6.0

正文

​ 1. 這個項目又兩個分支:webxmlnowebxml,我開始是基於servlet2.5寫的,後面我又想用servlet3.x了於是就兩個分支了(後面主要代碼都在nowebxml上)。

2. Servlet容器啓動會掃描,當前應用裏面每一個jar包的ServletContainerInitializer的實現,然後就注入一個DispatcherServlet 就可以了,跟在web.xml配置一樣,只是這裏都在java代碼裏面了(看過spring代碼的同學可能一眼就看出了它也是有個SpringServletContainerInitializer類作爲人口的)

public class SpringxServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) {
        ServletRegistration.Dynamic dispatcherServlet = ctx.addServlet("dispatcherServlet", DispatcherServlet.class);
        dispatcherServlet.setInitParameter("scanPackage", "com.sunny.springx.example");
        dispatcherServlet.addMapping("/*");
        dispatcherServlet.setLoadOnStartup(1);
    }
}

3.上面代碼可以看出有個scanPackage參數,這個是告訴springx從那裏開始工作。DispatcherServlet繼承HttpServlet所以開始會調用init方法,然後就是大概四步就可以完成一個簡單的springx

    @Override
    public void init(ServletConfig config) {
        Instant start = Instant.now();
        // 1.掃描相關類
        doScanner(config.getInitParameter("scanPackage"));
        // 2.初始化類
        doInstance();
        // 3. 注入
        doAutoWried();
        // 4. 初始化url
        initHandlerMapping();

        Instant end = Instant.now();

        System.out.println("springx init in " + Duration.between(start, end).toMillis() + " ms");
    }

​ 4. 主要邏輯都在DispatcherServlet這裏面

public class DispatcherServlet extends HttpServlet {

    //存放class name
    private List<String> classNames = new ArrayList<>();
    // ioc 容器
    private Map<String, Object> ioc = new HashMap<>();
    //handMapping
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            resp.getWriter().write("500 " + e.getMessage());
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");

        Handler handler = getHandler(url);
        //url 不存在404
        if (Objects.isNull(handler)) {
            resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
            resp.getWriter().write("not found url : " + url);
            return;
        }

        // 獲取方法
        Object invoke = handler.method.invoke(handler.controller, null);
        resp.setStatus(HttpServletResponse.SC_OK);
        resp.getWriter().write(invoke.toString());
    }


    @Override
    public void init(ServletConfig config) {
        Instant start = Instant.now();
        // 1.掃描相關類
        doScanner(config.getInitParameter("scanPackage"));
        // 2.初始化類
        doInstance();
        // 3. 注入
        doAutoWried();
        // 4. 初始化url
        initHandlerMapping();

        Instant end = Instant.now();

        System.out.println("springx init in " + Duration.between(start, end).toMillis() + " ms");
    }

    /**
     * 獲取 handler
     *
     * @param url 請求url
     * @return
     */
    private Handler getHandler(String url) {
        if (handlerMapping.isEmpty()) return null;

        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.pattern.matcher(url);
            if (!matcher.matches()) continue;
            return handler;
        }
        return null;
    }

    private void initHandlerMapping() {
        if (ioc.isEmpty()) return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 獲取類
            Class<?> clazz = entry.getValue().getClass();
            //沒有controller註解的跳過
            if (!clazz.isAnnotationPresent(Controller.class)) continue;
            //根url
            String rootUrl = "";
            if (clazz.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                rootUrl = requestMapping.value();
            }

            //掃描類全部方法
            for (Method method : clazz.getMethods()) {

                //只處理RequestMapping註解
                if (!method.isAnnotationPresent(RequestMapping.class)) continue;
                RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                // 拼接rootUrl 全局替換避免兩個斜槓 正則
                String reg = ("/" + rootUrl + requestMapping.value()).replaceAll("/+", "/");

                handlerMapping.add(new Handler(method, entry.getValue(), Pattern.compile(reg)));
                System.out.println("mapping:" + reg + "," + method);
            }
        }

    }

    private void doAutoWried() {
        if (ioc.isEmpty()) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 獲取類中全部的字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {

                // 不包含autowried註解的跳過
                if (!field.isAnnotationPresent(Autowried.class)) continue;

                Autowried autowried = field.getAnnotation(Autowried.class);

                String beanId = autowried.value().trim();
                if (StringUtils.isBlank(beanId)) {
                    beanId = field.getName();
                }
                //設置授權
                field.setAccessible(true);
                try {
                    // 字段賦值
                    field.set(entry.getValue(), ioc.get(beanId));
                } catch (IllegalAccessException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    private void doInstance() {
        if (classNames.isEmpty()) return;
        try {
            for (String className : classNames) {
                //根據名稱實例化加了controller和service註解的類
                Class<?> clazz = Class.forName(className);
                //beanId 默認類小寫
                String beanId;
                if (clazz.isAnnotationPresent(Controller.class)) {// 處理Controller註解
                    Controller controller = clazz.getAnnotation(Controller.class);
                    beanId = controller.value();

                    if (StringUtils.isBlank(beanId)) {
                        beanId = StringUtils.lowerFirstCase(clazz.getSimpleName());
                    }

                    ioc.put(beanId, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(Service.class)) { // 處理Service 註解
                    // 獲取註解
                    Service service = clazz.getAnnotation(Service.class);

                    beanId = service.value();
                    if (StringUtils.isBlank(beanId)) {
                        beanId = StringUtils.lowerFirstCase(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();
                    ioc.put(beanId, instance);
                    // 獲取類的實現
                    for (Class<?> anInterface : clazz.getInterfaces()) {
                        ioc.put(StringUtils.lowerFirstCase(anInterface.getSimpleName()), instance);
                    }
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 加載路徑下的全部class name
     *
     * @param scanPackage 包開始掃描路徑
     */
    private void doScanner(String scanPackage) {
        // com.sunny.springx.example 路徑替換文件路徑
        URL url = getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        assert url != null;
        File classDir = new File(url.getFile());

        for (File file : Objects.requireNonNull(classDir.listFiles())) {
            //如果是文件夾繼續掃描
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    private ClassLoader getClassLoader() {
        return this.getClass().getClassLoader();
    }

    private class Handler {
        Method method;
        //方法對象實力
        Object controller;
        //url正則
        Pattern pattern;

        Handler(Method method, Object controller, Pattern pattern) {
            this.method = method;
            this.controller = controller;
            this.pattern = pattern;
        }
    }
}

結尾

這個項目寫的比較粗糙,後面有時間我打算把spring的能力都在這個項目裏面體現下。比如參數綁定我就沒有實現,只是定義了註解還沒有實現。當然你也可以fork去寫這玩玩,感謝圍觀
  zhaoyunxing微信公衆號

關注公衆號一起寫bug,一起嘻嘻哈哈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章