1. 程式人生 > >學習Spring原始碼:手寫實現spinrgmvc

學習Spring原始碼:手寫實現spinrgmvc

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();
		}

	}

}

到這裡就完成了。 可以測試下  :

好了。告辭