打造一款屬於本身的web服務器——配置controller

    這天一熱,胖子的的世界就是一片悽慘啊,隨便動動身子,就跟洗個澡似得,心情當然煩躁,一煩躁就很難靜下心來寫東西了......因此這一段沒咋用心寫,就稍微水點吧,同時,我又打算要減肥了!>_<!。

    上一次咱們介紹了session的實現,使web服務器(如今總以爲準確來講應該叫可獨立部署的web框架,稱不上服務器)具有了基本功能,可是仔細一想就會發現一個嚴重的問題:每當實現一個新的controller,那麼就須要在invokController方法裏邊增長判斷,以便url可以找到對應controller。對於一個web服務器(或是框架)而言,內部應該是封閉的,即便是爲了支持拓展也應該是提供外部接口實現,經過改代碼來修改,絕對是極其糟糕的設計。下邊咱們來看幾種實現方式: html

1、如何實現

    首先咱們來思考一個url請求時如何映射到對應的處理方法的,常規思路以下:
    能夠看到這裏邊有兩個關鍵點:一、路由表的結構是怎麼實現的?二、查到對應類的對象是怎麼加載的(實例化)?只要弄清楚了這兩個問題,其實一切就迎刃而解了。
    路由表的主要做用是經過url可以找到對應的對應處理類的對應方法,咱們很容易就能想到的結構就是key-value結構的map了,實際上多數狀況也的確如此。可是要注意到是,在實際存儲的時候,具體的內容要看狀況而定,在簡單狀況下直接key存uri,value存處理類信息就行,可是當你想支持多種映射規則時(如前綴匹配、正則匹配等等),就要歸類存儲了。整體而言這一部分仍是很容易實現的。
    下邊咱們來看一下第二個問題,路由表裏邊通常存儲的應該是類信息(至於到具體方法的映射大同小異),並且多數狀況下只是類名,可是實際處理卻須要類對象來執行。那麼咱們就須要考慮這些類對象時如何加載的。一種方法就是在web服務器啓動時就實例化全部註冊的類(應該也是經過反射),而另外一種則是在使用的時候根據類名經過反射動態生成對象,其實二者區別只是對象實例化的時機。(這裏忽然想到一個問題,通常而言這裏實例化的對象應該是單例的,也就是相同請求用的對象都是一個,併發處理時在器內部實現的,本項目目前暫未考慮這點,以後會改進的)

2、功能設計

    仍是先來設計一下,這一次比較簡單,對於路由表咱們目前只須要支持uri和controller一一對應的狀況,因此直接使用一個map存儲就好。而配置爲了方便直接經過註解實現(以前實現了配置文件配置,後來去掉了,須要的話能夠本身加上),web服務器啓動時先獲取制定目錄下全部controller類型註解,而後根據註解信息將映射存入map。而url請求時,從map中查找到對應類名,經過反射實例化並調用對應方法。

3、實現代碼

    首先來看一下註解的定義( java自定義註解 ),這裏定義了兩個註解,一個是url的映射,另外一個則是方法映射(只有聲明該註解的方法會被映射 ):
package org.eh.core.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註解
 * @author guojing
 * @date 2014-3-5
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Controller {

	/**
	 * controller名,暫時無用
	 */
	public String name();

	/**
	 * 對應url請求路徑,如htp://127.0.0.1/test/list.do 則對應 controller爲:/test/,對應方法爲:list
	 */
	public String url();
}
/**
 * 方法映射註解
 * @author guojing
 * @date 2014-3-13
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RequestMapping {

}
    定義完了註解,再來實現一個 AnnocationHandler處理註解信息 ,該類主要實現三個功能:1.  獲取指定包下的全部類名(包含包名),2. 將全部註解Controller加入Constants.UrlClassMap,3. 獲取類的指定方法 ,其實現以下:
/**
 * 註解處理類
 * @author guojing
 * @date 2014-3-5
 */
public class AnnocationHandler {

	/**
	 * 將全部註解Controller加入Constants.UrlClassMap
	 * @param parkage 類名(包含包路徑)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void paserControllerAnnocation(String parkage) throws ClassNotFoundException {
		List<String> classlist = getPkgClass(parkage);
		for (String str : classlist) {
			Class c = Class.forName(str);
			if (c.isAnnotationPresent(Controller.class)) {
				Controller desc = (Controller) c.getAnnotation(Controller.class);
				Constants.UrlClassMap.put(desc.url(), str);
			}
		}
	}

	/**
	 * 獲取指定包下的全部類名(包含包名)
	 * @param parkage 指定包名
	 * @return
	 */
	public List<String> getPkgClass(String parkage) {
		String path = Constants.CLASS_PATH + parkage.replace(".", "/") + "/";
		List<String> list = new ArrayList<String>();

		File file = new File(path);
		for (String str : file.list()) {
			if (str.endsWith(".class")) {
				list.add(parkage + "." + str.replace(".class", ""));
			} else if (str.indexOf(".") == -1) {
				list.addAll(getPkgClass(parkage + "." + str));
			}
		}

		return list;
	}
	
	/**
	 * 獲取類的指定方法
	 * @param c
	 * @param methodName
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Method getMethod(Class c, String methodName) throws NoSuchMethodException,
			SecurityException {
		Method method = c.getMethod(methodName, Map.class);
		return method.isAnnotationPresent(RequestMapping.class) ? method : null;
	}
}
    而後要在 EHHttpHandler的 invokController方法中經過反射( java反射 )調用方法,以下:
/**
	 * 調用對應Controller處理業務
	 */
	private ResultInfo invokController(HttpExchange httpExchange) {
		String path = httpExchange.getRequestURI().getPath();

		String classPath = Constants.UrlClassMap.get(path.substring(0,
				path.lastIndexOf("/") + 1));
		if (classPath == null || classPath.length() == 0) {
			return null;
		}
		Class controllerClass = Class.forName(classPath);
		Controller controller = (Controller) controllerClass.newInstance();

		String methodName = path.substring(path.lastIndexOf("/") + 1,
				path.lastIndexOf("."));
		// 經過反射獲取對應方法
		AnnocationHandler annocationHandler = new AnnocationHandler();
		Method method = annocationHandler
				.getMethod(controllerClass, methodName);

		Map<String, Object> map = null; // 參數
		map = analysisParms(httpExchange);

		// 設置session
		HttpSession httpSession = ApplicationContext.getApplicationContext()
				.getSession(httpExchange);
		map.put("session", httpSession);

		return (ResultInfo) method.invoke(controller, new Object[] { map });
	}
    最後還要在啓動時加載配置信息,在 EHServer的 startServer中添加
// 加載註解配置的controller
		AnnocationHandler annocationHandler = new AnnocationHandler();
		try {
			annocationHandler
					.paserControllerAnnocation("org.eh.core.web.controller");
		} catch (Exception e) {
			log.error("加載controller配置出錯!", e);
			return;
		}
    至此,這次功能完成。

4、測試

    下邊咱們來測試下該功能是否好用,仍是用 IndexController吧,咱們加入註解信息:
/**
 * 主頁對應的contoller
 * @author guojing
 */
@org.eh.core.annotation.Controller(name = "session", url = "/session/")
public class IndexController implements Controller{
	
	@RequestMapping
	public ResultInfo process(Map<String, Object> map){
		ResultInfo result =new ResultInfo();
		
		// 這裏咱們判斷請求中是否有name參數,若是有則放入session,沒有則從session中取出name放入map
		HttpSession session = (HttpSession) map.get("session");
		if (map.get("name") != null) {
			Object name = map.get("name");
			session.addAttribute("name", name);
		} else {
			Object name = session.getAttribute("name");
			if (name != null) {
				map.put("name", name);
			}
		}
		
		result.setView("index");
		result.setResultMap(map);
		return result;
	}
}
    而後瀏覽器打開 http://localhost:8899/session/process.do?name=guojing,結果又看到這個熟悉的頁面了,說明一切ok,你也能夠多寫幾個controller,直接加入註解就能訪問,不用改其餘任何代碼

5、總結

    照例最後來點廢話,至此其實本項目才真正能拿去用了,不過若是你真打算用,就會發現不少不爽的地方,好比本項目代碼和業務代碼混在一塊兒、模板支持太差、配置信息寫在代碼裏等等,那麼下次咱們未來解決這些問題。
    最後獻上源碼,learn-3源碼(對應的 master 爲完整項目): 源碼
相關文章
相關標籤/搜索