爲了更好的學習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實現項目結構、工具類,以及演示