1. 程式人生 > >springmvc原理詳解(手寫springmvc)

springmvc原理詳解(手寫springmvc)

最近在複習框架 在網上搜了寫資料 和原理 今天總結一下 希望能加深點映像  不足之處請大家指出

我就不畫流程圖了 直接通過程式碼來了解springmvc的執行機制和原理

回想用springmvc用到最多的是什麼?當然是controller和RequestMapping註解啦 

首先我們來看怎樣定義註解的

首先來定義@Controller

@Target表示該註解執行在什麼地方
1、public static final ElementTypeTYPE      類、介面(包括註釋型別)或列舉宣告
2、public static final ElementTypeFIELD     欄位宣告(包括列舉常量)
3、public static final ElementTypeMETHOD    方法宣告
4、public static final ElementTypePARAMETER     引數宣告
5、public static final ElementTypeCONSTRUCTOR    構造方法宣告
6、public static final ElementTypeLOCAL_VARIABLE     區域性變數宣告
7、public static final ElementTypeANNOTATION_TYPE     註釋型別宣告
8、public static final ElementTypePACKAGE    包宣告

@Retention :用來說明該註解類的生命週期。它有以下三個引數:


     RetentionPolicy.SOURCE  : 註解只保留在原始檔中


     RetentionPolicy.CLASS  : 註解保留在class檔案中,在載入到JVM虛擬機器時丟棄


     RetentionPolicy.RUNTIME  : 註解保留在程式執行期間,此時可以通過反射獲得定義在某個類上的所有註解。

@Target(ElementType.TYPE)//表示註解執行在哪裡
@Retention(RetentionPolicy.RUNTIME)//用來說明該註解類的生命週期
public @interface Controller {
}
其次定義@RequestMapping
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface  RequestMapping {
    public String value();//定義一個字元陣列來接收路徑值 
}

定義好了註解 怎樣讓其產生效果呢?

假設程式啟動執行中 我們如何去知道類上面是否存在註解了? 這就需要用到java的反射機制了。在執行狀態中對於任意一個類,都能夠知道這個類的所有屬性和方法,對於任意一個物件,我們都可以呼叫它任意的一個方法。

專案目錄結構如下:



比如說在執行狀態中 我們知道springmvc掃描的包為com.czmec.Controller.IndexController這個包下面  我們通過反射就可以獲取改包下的資訊

public class Test {
    public static void main(String[] args) {
        Class clazz= IndexController.class;
        //判斷這個類是否存在@Controller  是否標記Controller註解
        if (clazz.isAnnotationPresent(Controller.class)){
            System.out.println(clazz.getName()+",被標記為控制器!");
            //吧標記了@Controller註解的類管理起來
            String path="";
            //判斷標記了Controller類上是否存在@RequestMapper
            if (clazz.isAnnotationPresent(RequestMapping.class)){
                //如果存在 就獲取註解上的路徑值
                RequestMapping reqAnno= (RequestMapping) clazz.getAnnotation(RequestMapping.class);
                path=reqAnno.value();
            }

            //獲取類上路徑過後 再獲取該類的所有公開方法進行遍歷 並且判斷哪些方法上有@RequestMapping
            Method[] ms=clazz.getMethods();
            for (Method method:ms){
                //如果不存在RequestMapping註解 進入下一輪迴圈
                if(!method.isAnnotationPresent(RequestMapping.class)){
                    continue;
                }
                System.out.println("對映對外路徑"+path+method.getAnnotation(RequestMapping.class).value());
            }
        }
    }
}

執行結果如下:


通過以上測試類 反射機制去獲取加了註解的資訊(個人理解:註解是一種標記 在程式碼做標記 然後在特定的管理下 可以通過某種動態方式找到想要的資訊)

開發人員通過自己的業務需求去添加註解類 那麼框架的設計就應該去吧這些添加了註解的類管理起來。

比如在springmvc配置中需要配置掃描的包 那麼我們需要寫一個工具類來根據配置的掃描包來管理需要管理的類

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
/**
 * 用來掃描指定的包下面的類
 */
public class ClassScanner {
    /**
     * 用來掃描指定的包下面的類
     * @param basePackg 基礎包
     * @return  Map<String,Class<?>> Map<類,類的class例項>
     */
    public static Map<String,Class<?>> scannerClass(String basePackg){
        Map<String,Class<?>> results=new HashMap<String, Class<?>>();
        //com.czmec.controller
        //通過包名替換成 com/czmec/controller
        String filePath=basePackg.replace(".","/");
        //通過類載入器獲取完整路徑
        try {
            Enumeration<URL> dirs=Thread.currentThread().getContextClassLoader().getResources(filePath);
            String rootPath=Thread.currentThread().getContextClassLoader().getResource(filePath).getPath();
            System.out.println(rootPath);
            if (rootPath!=null){
                rootPath=rootPath.substring(rootPath.lastIndexOf(filePath));
            }
            ///C:/Users/user/Desktop/%e5%ad%a6%e4%b9%a0/springmvc/out/production/springmvc/com/czmec/Controller
            //獲取的是類的真實的物理路徑 接下來就是io啦
            while (dirs.hasMoreElements()){
                URL url=dirs.nextElement();
                System.out.println(url);
                /**
                 * /C:/Users/user/Desktop/%e5%ad%a6%e4%b9%a0/springmvc/out/production/springmvc/com/czmec/Controller
                 file:/C:/Users/user/Desktop/%e5%ad%a6%e4%b9%a0/springmvc/out/production/springmvc/com/czmec/Controller
                 */
                //根據url 判斷是檔案還是資料夾
                if (url.getProtocol().equals("file")){
                    File file=new File(url.getPath().substring(1));//因為路徑前面多了一個/所有從1開始
                    //如果是資料夾 就需要遞迴找下去 找到所有檔案
                    try {
                        scannerFile(file,rootPath,results);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return results;
    }

    //遞迴
    private  static void scannerFile(File folder,String rootPath,Map<String,Class<?>> classes) throws ClassNotFoundException {
        //拿到folder裡面的所有檔案物件  如果資料夾為空 會返回一個Null
        File[] files=folder.listFiles();
        for (int i=0;files!=null&&i<files.length;i++){
            File file=files[i];
            //如果是資料夾就繼續進行遞迴
            if (file.isDirectory()){
                if (rootPath.substring(rootPath.length()-1).equals("/")){
                    rootPath=rootPath.substring(0,rootPath.length()-1);
                }
                scannerFile(file,rootPath+"/"+file.getName()+"/",classes);
            }else {
                String path=file.getAbsolutePath();//獲取真實路徑
                if (path.endsWith(".class")){
                    //將路徑中的\替換成/
                    path=path.replace("\\","/");
                    //獲取完整的類路徑 比如com.czmec.IndexController
                    if (!rootPath.substring(rootPath.length()-1).equals("/")){
                        rootPath+="/";
                    }
                    String className=rootPath+path.substring(path.lastIndexOf("/")+1,path.indexOf(".class"));
                    className=className.replace("/",".");
                    System.out.println(className);
                    //吧類路徑是例項儲存起來
                    classes.put(className,Class.forName(className));
                }
            }
        }
    }

    public static void main(String[] args) {
        ClassScanner.scannerClass("com.czmec");
    }
}

通過上面程式碼 可以看出在指定的包下 獲取包下類的路徑 並獲取例項 儲存在Map中, 吧這些類掃描出來通過反射技術獲取註解值,獲取控制器類等等

在springmvc中 我們會配置springmvc的核心控制器DispatcherServlet來設定路徑許可權

DispatcherServlet的生命週期有:init(),service,destory;

下面是生命週期圖



通過這個圖我們可以瞭解到DispatcherServlet在執行過程中所起到的作用

配置DispatcherServlet有兩種方式 一種是基於xml 一種是基於註解 下面我們來看看基於註解

在DispatcherServlet類中 通過設定@WebServlet來設定攔截路徑 @WebInitParam來設定設定的包

在初始化方法中去獲取包下面所有的類並且迭代所有類的例項 獲取有Controller註解 並通過類載入機制例項化物件

儲存在Map中 再獲取類中所有加了@RequestMapping註解的方法,和路徑儲存在methods中  並在service中解析請求路徑通過invoke方法

@WebServlet(urlPatterns = {"*.do"},initParams = {@WebInitParam(name = "basePackage",value = "com.czmec")})
public class DispacherServlet extends javax.servlet.http.HttpServlet {
//    protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
//
//    }
//
//    protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
//
//    }
    //儲存Controller例項
    private Map<String,Object>  controllers=new HashMap<String,Object>();
    //被反射呼叫的method
    private Map<String,Method>  methods=new HashMap<String,Method>();
    public DispacherServlet(){
        super();
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //獲取包名
        String basePackage=config.getInitParameter("basePackage");
        //掃描
        Map<String ,Class<?>> cons= ClassScanner.scannerClass(basePackage);
        //迭代
        Iterator<String> itro=cons.keySet().iterator();
        while (itro.hasNext()){
            String className=itro.next();
            Class clazz=cons.get(className);
            if (clazz.isAnnotationPresent(Controller.class)){
                System.out.println(clazz.getName()+",被標記為控制器!");
                //吧標記了@Controller註解的類管理起來
                String path="";
                //判斷標記了Controller類上是否存在@RequestMapper
                if (clazz.isAnnotationPresent(RequestMapping.class)){
                    //如果存在 就獲取註解上的路徑值
                    RequestMapping reqAnno= (RequestMapping) clazz.getAnnotation(RequestMapping.class);
                    path=reqAnno.value();
                }
                try {
                    controllers.put(className,clazz.newInstance());
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                //獲取類上路徑過後 再獲取該類的所有公開方法進行遍歷 並且判斷哪些方法上有@RequestMapping
                Method[] ms=clazz.getMethods();
                for (Method method:ms){
                    //如果不存在RequestMapping註解 進入下一輪迴圈
                    if(!method.isAnnotationPresent(RequestMapping.class)){
                        continue;
                    }
                    System.out.println("對映對外路徑"+path+method.getAnnotation(RequestMapping.class).value());
                    methods.put(path+method.getAnnotation(RequestMapping.class).value(),method);
                }
            }
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req.getRequestURI());
        ///abc.do
        String uri=req.getRequestURI();
        String contextPath=req.getContextPath();
        //獲取對映路徑
        String mappingPath=uri.substring(uri.indexOf(contextPath)+contextPath.length(),uri.indexOf(".do"));
        //
        Method method=methods.get(mappingPath);
        //獲取例項物件
        Object controller=controllers.get(method.getDeclaringClass().getName());
        try {
            method.invoke(controller);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
配上tomcat執行

輸入地址:http://localhost:8080/czmec/delete.do



通過以上程式碼的說明 可以瞭解基本springmvc的執行機制和原理

至此最基本的springmvc就完成了

有哪裡錯誤的地方請指出