学写一个 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()
,是私有方法,然后分别为ServletConfig
和FilterConfig
提供独立的方法公开调用。这样的方法已经变为通用的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 框架的主要内容,如果未能解决你的问题,请参考以下文章