當前位置:
首頁 > 知識 > 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 : 註解保留在程序運行期間,此時可以通過反射獲得定義在某個類上的所有註解。

<code class="language-java"><span stylex="font-size:18px;">@Target(ElementType.TYPE)//表示註解運行在哪裡

@Retention(RetentionPolicy.RUNTIME)//用來說明該註解類的生命周期

public @interface Controller {

}

</span></code>

其次定義@RequestMapping

<code class="language-java"><span stylex="font-size:18px;">@Target({ElementType.METHOD,ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface RequestMapping {

public String value();//定義一個字元數組來接收路徑值

}</span></code>

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

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

項目目錄結構如下:

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

<code class="language-java"><span stylex="font-size:18px;">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());

}

}

}

}</span></code>

運行結果如下:

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

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

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

<code class="language-java"><span stylex="font-size:18px;">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");

}

}

</span></code>

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

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

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

下面是生命周期圖

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

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

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

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

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

<code class="language-java">@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();

}

}

}

</code>

配上tomcat運行

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

通過以上代碼的說明 可以了解基本springmvc的運行機制和原理

至此最基本的springmvc就完成了

有哪裡錯誤的地方請指出

源代碼下載地址:https://github.com/dcg123/springmvc

springmvc原理詳解(手寫springmvc)

springmvc原理詳解(手寫springmvc)

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

mybatis分頁設置方法
MySQL索引類型分析

TAG:程序員小新人學習 |