手寫springMVC簡單實現——概要思路實現(一)

    爲了更好的學習springMVC工作原理,最近自己實現了一個簡易版的springMVC,在這與大家分享一下。在學習之前,我憑經驗,猜想springMVC應該是利用servlet接收請求地址,然後再通過請求地址,找到與請求地址對應的方法,然後執行,就可map的key->value感覺一樣,學習之後,驗證了自己的猜想。

    參考文獻:https://blog.csdn.net/chyanwu68/article/details/81096910

    整體代碼結構如下:

package com.jsalpha.utils.servlet;

import com.jsalpha.utils.common.ClassUtil;
import com.jsalpha.utils.common.MethodUtil;
import com.jsalpha.utils.annotation.MyQualifier;
import com.jsalpha.utils.annotation.MyRequestMapping;
import com.jsalpha.utils.load.ClassOfPackageLoader;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 實現springMVC的servlet
 * 1.通過配置文件掃描需要依賴注入包(packageNames)
 * 2.掃描包中的所有類(classNames)
 * 3.過濾並實例化需要依賴注入的類(aliasBeans)
 * 4.控制反轉,將實例化的對象,注入到實例化對象需要依賴注入的屬性中去
 * 5.保存類名與實例化對象的對應關係(注意:這部不是必須的,可以通過修改方法實現省略這部)
 * 6.建立path與method的映射關係
 * @author dengjingsi
 */
public class DispatcherServlet extends HttpServlet {
    /**
     * 掃描配置文件的參數name
     */
    private final static String initFileName = "contextConfigLocation";
    /**
     * 通過配置文件,掃描到的所有類的類名
     */
    private LinkedList<String> classNames = new LinkedList<>();
    /**
     * 類別名對應的map對象
     */
    private Map<String,Object> aliasBeans = new HashMap<>();
    /**
     * 請求地址對應方法map對象
     */
    private Map<String,Method> handlerMap = new HashMap<>();
    /**
     * 類名對應的map對象
     */
    private Map<String,Object> classNameObject = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("init()............");
        // 1.通過配置,獲取所有需要依賴注入類的包名
        String[] packageNames = getPackageNames(config);
        System.out.println("需要掃描的包............");
        for(String packageName : packageNames){
            System.out.println(packageName);
        }

        // 2.掃描包中的所有類
        try {
            doScanPackage(packageNames);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("掃描到的包中,所有的class類.......");
        for(String name: classNames) {
            System.out.println(name);
        }

        // 3.過濾MyController,MyService註解修飾的類,並實例化
        try {
            doInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("當前實例化的對象信息.........");
        for(Map.Entry<String,Object> map : aliasBeans.entrySet()){
            System.out.println("key:" + map.getKey() + "; value:" + map.getValue());
        }

        // 4.將IOC容器中的service對象設置給controller層定義的field上
        try {
            doIoc();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        // 5.以map數據結構(類名->實例化對象)保存實例化對象
        collcetClassObject();

        // 6.建立path與method的映射關係
        handlerMapping();
        System.out.println("Controller層的path和方法映射.........");
        for(Map.Entry<String, Method> map: handlerMap.entrySet()) {
            System.out.println("key:" + map.getKey() + "; value:" + map.getValue());
        }

    }

    /**
     * 從配置文件中獲取需要掃描的包
     * @param config
     * @return
     */
    public String[] getPackageNames(ServletConfig config){
        String init = config.getInitParameter(initFileName);
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(init.split(":")[1]);
        Properties properties = new Properties();
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String packageNames = properties.getProperty("package");
        return packageNames.split(";");
    }

    /**
     * 掃描需要依賴注入包中的類
     * @param packageNames 需要依賴注入的包名
     * @throws ClassNotFoundException
     */
    private void doScanPackage(String[] packageNames) throws ClassNotFoundException {
        String path = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        ClassOfPackageLoader classOfPackageLoader = new ClassOfPackageLoader();
        for(String packageName : packageNames){
            classOfPackageLoader.collectClassOfPackageInner(path,packageName,classNames);
        }
    }

    /**
     * 過濾MyController,MyService註解修飾的類,並實例化
     */
    private void doInstance() throws ClassNotFoundException {
        for(String className : classNames){
            ClassUtil.addControllerBean(aliasBeans,className);
        }
        for(String className : classNames){
            ClassUtil.addServiceBean(aliasBeans,className);
        }
    }

    /**
     * 依賴注入
     * @throws IllegalAccessException
     */
    private void doIoc() throws IllegalAccessException {
        Field[] fields;
        Annotation a;
        String name;
        Object fieldValue;
        Object bean;
        for(Map.Entry<String,Object> aliasBean : aliasBeans.entrySet()){
            bean = aliasBean.getValue();
            fields = bean.getClass().getDeclaredFields();
            for(Field field : fields){
                a = field.getAnnotation(MyQualifier.class);
                if(null != a){
                    name = ((MyQualifier) a).value();
                    fieldValue = aliasBeans.get(name);
                    field.setAccessible(true);
                    field.set(bean,fieldValue);
                }
            }
        }
    }

    /**
     * 以map數據結構(類名->實例化對象)保存實例化對象
     */
    private void collcetClassObject(){
        String className;
        for(Map.Entry<String,Object> entry : aliasBeans.entrySet()){
            className = entry.getValue().getClass().getName();
            classNameObject.put(className,entry.getValue());
        }
    }

    /**
     * 保存請求地址與方法的對應關係
     */
    private void handlerMapping(){
        Method[] methods ;
        Set<Method> methodList = new HashSet<>();
        Annotation myController;
        Class c;
        for(Map.Entry<String,Object> map : aliasBeans.entrySet()){
            c = map.getValue().getClass();
            methods = c.getMethods();
            //過濾並獲取methods中,MyRequestMapping註解修飾的方法
            methodList.addAll(MethodUtil.filterReuqestMethod(methods,MyRequestMapping.class));
            myController = c.getAnnotation(MyRequestMapping.class);
            if(null != myController) {
                //保存method註解中的地址與method對應關係
                setPathMethod(((MyRequestMapping)myController).value(), methodList);
            }
        }
    }
    /**
     * 補全,並保存methods方法對應的url地址
     * @param value
     * @param methods
     */
    private void setPathMethod(String value, Set<Method> methods){
        MyRequestMapping annotation;
        String url;
        for(Method method : methods){
            annotation = method.getDeclaredAnnotation(MyRequestMapping.class);
            url = annotation.value();
            handlerMap.put(value+url,method);
        }
    }

    /**
     * get方法
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        System.out.println("doGet()............");
        this.doPost(req, resp);
    }

    /**
     * post方法
     * @param req
     * @param resp
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        System.out.println("doPost()............");

        // 通過req獲取請求的uri
        String uri = req.getRequestURI();

        // 替換掉項目目錄
        String context = req.getContextPath();
        String path = uri.replaceAll(context, "");

        // 通過當前的path獲取handlerMap的方法
        Method method = handlerMap.get(path);

        // 從請求與相應參數,獲取method的形參參數
        Object[] params = MethodUtil.getParamMethod(req,resp,method);

        // 通過method反向獲取調用此method的實例對象
        Object o = classNameObject.get(method.getDeclaringClass().getName());

        //通過反射執行method方法
        Object s =null;
        try {
            s = method.invoke(o,params);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        //返回相應結果
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(resp.getOutputStream());
        BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
        bufferedWriter.write(s.toString());
        bufferedWriter.flush();
        outputStreamWriter.close();
        bufferedWriter.close();

    }

}

github地址:https://github.com/homefrontgarden/myspringMVC.git

下一篇:手寫springMVC簡單實現——目錄結構說明(二)

https://blog.csdn.net/weixin_39194257/article/details/89502845

介紹:springMVC實現項目結構、工具類,以及演示

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