学写一个 Java Web MVC 框架

Posted sp42a

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学写一个 Java Web MVC 框架相关的知识,希望对你有一定的参考价值。

初始化控制器

在进入实质工作之前,首先要获取配置内容。Servlet过滤器标准方法init(FilterConfig fConfig)在Tomcat启动的时候执行,且在生命周期中只执行一次,如下代码所示是MvcDispatcher的init()。其中参数FilterConfig是读取web.xml中的配置信息对象。另外还有重新doFilter()是用来处理请求的方法,会多次执行,每当有HTTP请求到来时便执行。

/**
 * 初始化这个过滤器
 * @param _config web.xml 中定义的过滤器配置
 */
@Override
public void init(FilterConfig _config) throws ServletException {
	Map<String, String> config = ServletHelper.parseInitParams(null, _config);

	if (config != null && config.get("doIoc") != null) {
		String doIoc = config.get("doIoc");
		for (String packageName : CommonUtil.split(doIoc))
			BeanContext.init(packageName);

		BeanContext.injectBeans(); // 依赖注射扫描
	}

	if (config != null && config.get("controller") != null) {
		String controller = config.get("controller");
		ControllerScanner.scannController(controller);
	}
}

先来看看init(),我们重写了这个接口方法,它用于初始化 MVC的配置,该配置就是前面所说的web.xml过滤器的配置,主要任务是:1、读取controller配置内容;2、读取doIoc配置内容,分别是发现MVC控制器的依赖注入两项主要的工作。

web.xml<param-name>……</param-name><param-value>……</param-value>构成“键对值”结构,于是我们轻易联想到用Map来表示配置。但是FilterConfig本身为Enumeration枚举结构,故需要进行转换,转换方法如下MvcRequest.parseInitParams()

/**
 * 遍历注解的配置,需要什么类,收集起来,放到一个 hash 之中, Servlet 或 Filter 通用
 * 
 * @param servletCfg 这两个参数任选一个,但不能同时传
 * @param filterCfg  这两个参数任选一个,但不能同时传
 * @return 指定的 Servlet 或 Filter 配置对象
 */
public static Map<String, String> parseInitParams(ServletConfig servletCfg, FilterConfig filterCfg) {
	Map<String, String> map = new HashMap<>();

	Enumeration<String> initParams = servletCfg == null ? filterCfg.getInitParameterNames() : servletCfg.getInitParameterNames();

	while (initParams.hasMoreElements()) {
		String key = initParams.nextElement();
		
		String value;
		if(servletCfg == null)
			value = filterCfg.getInitParameter(key); 
		else 
			value =servletCfg.getInitParameter(key);

		map.put(key, value);
	}
	return map;
}

上面代码逻辑没什么问题,但咋一看,似乎嗅到一丝“坏代码”的味道,遂重构改良之,如插图 4.6所示。
重构后的转换方法

上述源码应用了Java函数式的技巧,对于Enumeration的遍历操作进行了抽象,提炼了相同部分的逻辑为一个方法initParams2map(),是私有方法,然后分别为ServletConfigFilterConfig提供独立的方法公开调用。这样的方法已经变为通用的Enumeration转Map方法。

重构的结果仍然是把FilterConfig转为通用的Map结构,读取配置时候更为方便。得到配置config之后,我们似乎能嗅到“重复代码的味道”,对map非空判断获取value之后再判断非空,且重复逻辑两次,能否对其抽象提炼呢?答案是肯定的,这里我们再次发挥一下Java函数式的威力,小试牛刀,修改该方法为如插图所示。
在这里插入图片描述

显然代码行数更少了。MapHelper.getValue()调用两次,何方神圣呢?原来它是工具包MapHelper的一个静态方法2,如插图 4.8所示,逻辑非常的简单。
在这里插入图片描述

可见该方法抽象了重复部分的代码,而不能抽象的部分,则使用了函数表达式s传入到该方法执行。这里是Java函数接口的典型应用,Consumer<T>表示接受一个T类型参数,没有返回值。泛型T是一个类型的变量,当前map结构为<String, String>,故T真实类型为String,即实际为Consumer<String> s

代码优化的脚步还未停下。我们细心地发现,既然getValue()Consumer<String> s参数要求传入String类型参数且不返回任何值,那么在第二次调用getValue()中,ControllerScanner.scannController(controller)该方法也正好符合Consumer<String>接口,何必还要增加一个lambda多此一举呢?于是进一步优化,将其变为:

MapHelper.getValue(config, "controller", ControllerScanner::scannController);

效果无异!代码却更精炼!ControllerScanner::scannController表示获取scannController方法的引用,这是Java 8时代起函数式编程的特性:获取某个方法(函数)的引用,它不是立刻执行的,却可作为变量那样赋值、参与参数的传递。

以上是关于学写一个 Java Web MVC 框架的主要内容,如果未能解决你的问题,请参考以下文章

学写一个 Java Web MVC 框架

学写一个 Java Web MVC 框架

学写一个 Java Web MVC 框架

Java Web ——MVC基础框架讲解及代码演示

Java Web ——MVC基础框架讲解及代码演示

Java Web ——MVC基础框架讲解及代码演示