一、Git項目地址:
地址:https://github.com/kobeyk/handwriting-springmvc.git
網上可以搜到很多,大致思路和手寫步驟如下:
1、仿Spring註解,自定義一套屬於自己的註解,如仿寫常用的@Controller、@Service、@AutoWired等
2、自定義DispatcherServlet類,繼承HttpServlet類,重寫幾個方法,如servlet的初始化方法init()
3、在init方法中,做一些Servlet容器初始化的工作:
3.1 掃描package下面的所有class,將class的完全限定名保存在List集合中
3.2 實例化第一步集合中的Bean對應的實例,實例的過程採用的是Java的反射機制,且哪些bean是需要實例化的是有過濾條件 的,如加了@Controller註解的bean類纔可以newInstance(),實例化的結果,放在beanMap中進行存儲,map中的key就是 bean的name,value就是bean實例。
3.3 實例化單個bean後,接着要處理單個bean中依賴注入的問題,也就是要處理@AutoWired註解的類字段了
3.4 bean依賴注入的問題解決後,剩下的就是解決前端請求url和後端method之間的映射(Mapping)了,也就是說,前端發送url請求後,後端,也就是Servlet容器是如何知道要去找哪個Controller中的哪個對應的method去執行呢?這就需要我們再次通過反射技術,去找尋url和method之間的關係了,找到後,存放在mapping容器中,以便於doGet和doPost方法中取出調用。
4、最後,配置項目的web.xml
二、項目結構
三、核心代碼(部分)
public class MyDispatcherServlet extends HttpServlet {
// 重寫三個方法,一個是父類GenericServlet的初始化方法init(),兩個分別是父類HttpServlet的doGet和doPost請求處理方法
/**存放指定的掃描包下面的所有的class*/
List<String> classNames = new ArrayList<>();
/**存放符合條件的實例化後的bean(類似於簡版的ioc容器),如加了xxx註解的bean要放進來*/
Map<String,Object> beanMap = new HashMap<>(16);
/**url和method映射關係 (請求api地址和處理請求的方法的鍵值對)*/
Map<String, Method> urlMethodMap = new HashMap<>(16);
@Override
public void init() throws ServletException {
// 1、第一步,掃描指定的包(主要是掃描xxx.class文件)
scanPackage("com.appleyk");
System.out.println("classNames = " + classNames);
// 2、利用反射,實例化bean(將實例化的bean,放入bean容器map中)
beanInstance();
System.out.println("beanMap = " + beanMap);
//3、處理@XXAutoWired註解,bean實例中的字段如果有這個註解(依賴其他bean),則將該註解對應的實例從beanMap中取出,並賦予該字段
doAutoWired();
// 4、處理請求的url與控制器類中方法的mapping(映射)關係
handleMapping();
System.out.println("urlMethodMap = " + urlMethodMap);
}
/**
* 獲取指定掃描包下面的所有class的名稱(限定類名)
* @param packageStr 要掃描的包名
*/
public void scanPackage(String packageStr){
// 1、將xx.xx結構的包名,轉換爲實際意義上的xx/xx/路徑
String pageckageDir = packageStr.replaceAll("\\.", "/");
// 2、根據包路徑,拿到當前類路徑(classPath)
URL resource = this.getClass().getClassLoader().getResource(pageckageDir);
// 3、拿到資源的文件(全)路徑
String fileStr = resource.getFile();
// 4、根據路徑創建文件
File file = new File(fileStr);
// 5、判斷文件是否存在,不存在直接返回
if(!file.exists()){
return;
}
// 6、獲取classes文件下面的所有文件集合(可能是文件,也有可能是目錄)
File[] files = file.listFiles();
for (File filePath : files) {
String className = packageStr + "." + filePath.getName().replace(".class","");
if(filePath.isDirectory()){
// 如果是文件夾的話,遞歸繼續獲取類限定名
scanPackage(className);
}else{
// 如果是文件的話,直接包名+“.”+文件拼接成類限定名,然後加入到集合中
classNames.add(className);
}
}
}
/**
* 首字母小寫
*/
private String lowerFirstCase(String str){
char[] chars = str.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
四、如何更改MVC項目的contextPath
五、啓動Tomcat,來一波測試,驗證手寫300多行代碼的實際效果
(1)run
(2)瀏覽器輸入請求url
http://localhost:8080/springmvc/user/query
A: 默認不給參數的調用情況
B: 帶參數的調用情況
http://localhost:8080/springmvc/user/query?name=appleyk&sex=%E7%94%B7