學習Spring原始碼:手寫實現spinrgmvc
阿新 • • 發佈:2018-12-19
1.關於spring ioc容器:
spring ioc容器是什麼呢? 我們可以理解為將東西存放起來的東西。比如將java物件存放在ioc容器中。
簡單的說就是 ioc容器等於 ====>>> Map<String,Object> 大集合
瞭解了容器,接下里就可以寫程式碼了。用過Spring的都知道這些註解。這裡不過多講解。
這裡我們主要是自己手寫以上幾個註解.通過在tomcat啟動時建立 dispatcherServlet init初始化時,完成注入;接下里會詳細講解。
2.關於註解:
算啦算啦,直接上程式碼. 有註釋.
@Controller:
package com.springmvc.wh.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Controller控制類註解 * 命名可以自定義比如(WHController),這裡為了可以更加直觀體現 * @author 文浩 * */ @Target(ElementType.TYPE) // 該註解表示: 此註解作用範圍只能在類上面 @Retention(RetentionPolicy.RUNTIME) // 該註解表示: 在系統執行時通過反射獲取資訊 @Documented // javaDoc public @interface Controller { String value() default ""; // 表示可以在註解內傳入引數,引數型別為String 預設為""; }
@Autowired:
package com.springmvc.wh.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自動注入註解 * * @author 文浩 * */ @Target(ElementType.FIELD) // 表示作用範圍只限於成員變數上 @Retention(RetentionPolicy.RUNTIME) // 系統執行時載入 @Documented // javaDoc public @interface Autowired { String value() default ""; }
@RequestMapping:
package com.springmvc.wh.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 對映路徑
*
* @author 文浩
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD }) // 表示作用範圍為類,和方法
@Retention(RetentionPolicy.RUNTIME) // 執行載入
@Documented
public @interface RequestMapping {
String value() default "";
}
@RequestParam :
package com.springmvc.wh.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 引數註解
*
* @author 文浩
*
*/
@Target(ElementType.PARAMETER) // 作用範圍為方法引數裡
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
@Service
package com.springmvc.wh.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
3.到這裡,註解已經寫完了。接下里編寫測試的程式碼.Service和Controller
比較懶,還是直接上程式碼好了。
Controller:
package com.springmvc.wh.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.springmvc.wh.annotation.Autowired;
import com.springmvc.wh.annotation.Controller;
import com.springmvc.wh.annotation.RequestMapping;
import com.springmvc.wh.annotation.RequestParam;
import com.springmvc.wh.service.DomeService;
@Controller()
@RequestMapping("/wh")
public class DemoController {
@Autowired("domeService")
private DomeService domeService;
@RequestMapping("/Test")
public void Test(HttpServletRequest request, HttpServletResponse response,
@RequestParam("name") String name,
@RequestParam("age") String age) {
PrintWriter out;
try {
out = response.getWriter();
String result = domeService.Test(name, age);
out.write(result);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Service:
package com.springmvc.wh.service;
public interface DomeService {
public String Test(String name, String age);
}
package com.springmvc.wh.service.impl;
import com.springmvc.wh.annotation.Service;
import com.springmvc.wh.service.DomeService;
@Service("domeService")
public class DomeServiceImpl implements DomeService {
public String Test(String name, String age) {
return "name====>>" + name + " ; age===>>" + age;
}
}
這裡沒有使用持久層框架. 所有直接return 了,不然應該連線Dao層。不多解釋。
關鍵來了,註解寫了,Controller寫了,service也寫了。
能映射了嗎,能注入了嗎。答:當然不行
因為我們缺少了最關鍵的部分,servlet呢?.
沒錯,下一步,dispatcherServlet, 在tomcat啟動時,完成注入。
4. dispatcherServlet
要想在tomcat啟動時完成注入應該怎麼寫呢。
關於servlet生命週期,這裡不在過多解釋。
當然是init()方法; 好了廢話有點多,還是上程式碼吧。
package com.springmvc.wh.servlet;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.springmvc.wh.annotation.Autowired;
import com.springmvc.wh.annotation.Controller;
import com.springmvc.wh.annotation.RequestMapping;
import com.springmvc.wh.annotation.RequestParam;
import com.springmvc.wh.annotation.Service;
import com.springmvc.wh.controller.DemoController;
public class DispatcherServlet extends HttpServlet {
// doScan()用於儲存所有class路徑
private List<String> className = new ArrayList<String>();
// doInstance() 儲存例項化後的bean
private Map<String, Object> beans = new HashMap<String, Object>();
private Map<String, Object> handerMap = new HashMap<String, Object>();
// <load-on-startup> 啟動時呼叫
/**
*
*/
public void init(ServletConfig config) {
// 掃描 com.spring.mvc.wh 下路徑 獲取所有class檔案並建立例項
// 得到Class<?>
doScan("com.springmvc.wh");
// 得到所有Class檔案後,對這些class檔案進行例項化
doInstance();
// 為例項化例項變數進行注入
doAutowired();
// 遍歷方法獲得對映路徑
doUrlMapping();
}
/**
* 遍歷例項,得到@RequstMapping(...)裡的路徑
*/
public void doUrlMapping() {
for (Map.Entry<String, Object> entry : beans.entrySet()) {
Object insetance = entry.getValue();
Class<?> clazz = insetance.getClass();
if (clazz.isAnnotationPresent(Controller.class)) {
RequestMapping reqMap = clazz.getAnnotation(RequestMapping.class);
String calssPath = reqMap.value();
// 得到類中所有方法
Method[] methods = clazz.getMethods();
// 遍歷所有方法
for (Method method : methods) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping reqMap1 = method.getAnnotation(RequestMapping.class);
// 獲取@ReuqstMapping註解中的value值
String methodPath = reqMap1.value();
handerMap.put(calssPath + methodPath, method);
} else {
continue;
}
}
}
}
}
/**
* 遍歷例項化的例項,為成員變數進行注入
*/
public void doAutowired() {
try {
for (Map.Entry<String, Object> entry : beans.entrySet()) {
Object instance = entry.getValue();
Class<?> clazz = instance.getClass();
// 如果是Controller 類下
if (clazz.isAnnotationPresent(Controller.class)) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Autowired aotuwired = field.getAnnotation(Autowired.class);
String key = aotuwired.value();
Object value = beans.get(key);
// 開啟私有變數的許可權
field.setAccessible(true);
field.set(instance, value);
}
} else {
continue;
}
}
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 遍歷calssName集合中的檔案,並且例項化
*/
public void doInstance() {
// 遍歷className裡的所有calss檔案
for (String className : className) {
String cn = className.replace(".class", "");
try {
Class<?> clazz = Class.forName(cn);
// 判斷calss檔案上的註解是不是Controller註解
if (clazz.isAnnotationPresent(Controller.class)) {
Object value = clazz.newInstance();
// map.put(key,value);
RequestMapping reqMap = clazz.getAnnotation(RequestMapping.class);
String key = reqMap.value();
beans.put(key, value);
} else if (clazz.isAnnotationPresent(Service.class)) {
// 判斷class檔案上的註解是不是 Service
Object value = clazz.newInstance();
Service service = clazz.getAnnotation(Service.class);
String key = service.value();
beans.put(key, value);
} else {
continue;
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// mvc.xml ----》 basePackage
public void doScan(String basePackage) {
// basePackage == com.springmvc
// 掃描編譯好的所有的類路徑
URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
// 路徑地址
String fileStr = url.getFile();
// 轉換為檔案型別,用於判斷 是屬於檔案還是資料夾
File file = new File(fileStr);
// 獲取basePackage 下所有的.calss
String[] filesStr = file.list();
for (String path : filesStr) {
File filePath = new File(fileStr + path);
// 判斷路徑是不是資料夾
if (filePath.isDirectory()) {
// 如果是路徑 遞迴繼續掃描
doScan(basePackage + "." + path);
} else {
className.add(basePackage + "." + filePath.getName());
}
}
}
// 原始碼採用策略模式,這裡粗略簡寫
public static Object[] hand(HttpServletRequest req, HttpServletResponse resp, Method method) {
// 拿到當前待執行的方法有哪些引數
Class<?>[] paramClazzs = method.getParameterTypes();
Object[] args = new Object[paramClazzs.length];
int args_i = 0;
int index = 0;
for (Class<?> paramClazz : paramClazzs) {
if (ServletRequest.class.isAssignableFrom(paramClazz)) {
args[args_i++] = req;
}
if (ServletResponse.class.isAssignableFrom(paramClazz)) {
args[args_i++] = resp;
}
// 從0-3判斷有沒有RequestParam註解,很明顯paramClazz為0和1時不是
// 當為2和3時為@requestParam,需要解析
// [@Con.xxx.xxx.ReuqstParam(value = value)]
Annotation[] paramAns = (Annotation[]) method.getParameterAnnotations()[index];
if (paramAns.length > 0) {
for (Annotation paramAn : paramAns) {
if (RequestParam.class.isAssignableFrom(paramAn.getClass())) {
RequestParam rp = (RequestParam) paramAn;
// 找到註解的引數
args[args_i++] = req.getParameter(rp.value());
}
}
}
index++;
}
return args;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
// 獲取請求路徑
String uri = req.getRequestURI();
String context = req.getContextPath();
String path = uri.replace(context, ""); // 路徑下所有key
Method method = (Method) handerMap.get(path);
DemoController insetance = (DemoController) beans.get("/" + path.split("/")[1]);
Object[] args = hand(req, resp, method);
method.invoke(insetance, args);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
到這裡就完成了。 可以測試下 :
好了。告辭