打造一款属于本身的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 为完整项目): 源码
相关文章
相关标签/搜索