关于Spring MVC的使用,事实上已足够简单,《Spring 3.x企业应用开发实战》和《Spring实战》也说得足够好,尤其是前者。不多说。聊聊表现层的话题吧,如对本篇内容感兴趣,请读《Struts2技术内幕》,是的,你没看错,一个我自己都不再推荐使用的框架,我也当然不会为它写一篇,但这书,值得读,当年曾震得我惊如天人。
说到表现层,绕不过MVC。
落花散人《老子集注》:
在无数凡夫俗子的喋喋不休之下,大圣人原本清晰的概念变得模糊不清!
这是一切流行词语的宿命:它太流行了,就无论什么人都来说一番,说来说去概念就不清了。
所以讨论之前做出以下设定:
1.本文中说的模型,指的是一种有状态无行为的对象,是用于承载业务逻辑结果数据,准备被渲染成页面的东西,其后缀,可能是entity/bean/dto/vo等。
2.本文中说的页面,指的是模型被灌到模板中后渲染出的结果,一般情况下是个html片段(也包括ajax或rest api的json响应,但不是本文重点)。
3.本文中说的控制器,指的是请求进、响应出的黑箱中,最上层的那个东西,负责接收请求和给出响应,但具体业务逻辑它交给业务层去处理。
4.本文中说的模板,指的是定义了页面格式的那个东西,可能是标签化的jsp或freemarker文件等。
5.本文中说的请求,指的是HttpServletRequest表示的网络请求,主要关注点包括路径和参数。
6.本文中说的响应,指的是HttpServletResponse表示的网络响应,性质为把页面给还到浏览器(也包括给还到其他http请求发起者,如移动端的rest请求,但不是本文重点)。
概念都很容易理解,那就从控制器具体说说。
这是上面少有的没有举例的东西,因为它的工作职责最多。
还是把它拆开看吧,既然「请求进响应出」,而「请求的主要关注点是路径和参数」,那控制器就该承担两个职责:
1)处理路径
2)处理参数
处理路径在实践中常常就是:
1)把这个路径的请求,映射给哪个方法?
——在servlet里是servlet-mapping,在种种框架中就是xml或annotation或约定优于配置的设定。
2)把该方法的结果,灌装到哪个模板,来生成结果页面?
——在servlet里是forward,在种种框架里还是xml或annotation或约定优于配置的设定。
处理参数在实践中常常就是:
1)怎样想办法尽量简单地把参数给到那个方法的手里?
——在servlet里是getParameter,在Struts1中是ActionForm,在Struts2中是Action属性,在SpringMVC中是方法参数。
所以,本文中控制器的概念的组成包括请求分发器、响应渲染器、表现层方法三部分。
请求分发器和响应渲染器的代码由框架提供,配置由程序员进行,它们联手为表现层方法扫清外围的嘈杂,使表现层方法得以相对简单地干正事。
而我们知道,干正事其实是业务层的职责……
所以表现层方法就很清闲,方法体大约是调用一下业务层,俗称「表现层是个薄层」。
但表现层方法原本不薄。它原本应当做4件事:
1)获得参数,即request.getParameter()
2)执行逻辑,即service.xx()
3)设置模型,即request.setAttribute()
4)渲染页面,即request..forward()
但框架既然职责在于「让程序员专注于业务」,就当然该降低1) 3) 4)的复杂度。
对1)获得参数,框架的想法就是把参数绑定到一些容易被表现层方法处理的什么东西上,较先进的是方法参数。
对3)设置模型,框架的想法就是让程序员可以轻松地把数据放到什么地方,放到这个地方就相当于放进了模型里,较先进的是方法返回值。
对4)渲染页面,框架的想法就是让响应渲染器做这事,程序员尽量轻松地给响应渲染器提供必要信息即可,较先进的是基于约定,根据表现层类和方法名寻找相应路径的模板(类和方法名的约定同时也是指定请求分发的路由)。
于是,表现层方法的方法名、参数、返回值都分担掉了责任,方法体就可以只调一句service.xx()就行了……
所以我觉得,SpringMVC那个Controller,配不上「控制器」这么八荒六合唯我独尊的霸气名字。
你一个只有方法没有属性的单例对象,每个方法体又只包括一行语句,如果不是托了Java是个面向对象语言的福,这些方法完全可以散落出去,各自承担责任,还要你包袱皮何用?
别说,之前还真散落过……Servlet就是每个控制器处理一个请求,Struts2的初衷也是。
但Java这熊孩子,在lambda横空出世之前,每个方法都必须身处类之中(导致了各种各样的设计模式的出现,最典型的就是策略模式,实践中常表现为令人闻风丧胆的匿名内部类),结果用Servlet时web.xml膨胀不堪。
Struts2实践中更是完全违反了「命令模式」「面向对象」的初衷,一个类里负责各个业务的属性都有,别提多乱了……
想想也是,你一个方法就一条语句,还要独占一个类,委实不能再过分。
大家叫苦不迭,直到有了SpringMVC这个尤物,大家把各个模块的表现层方法归拢归拢,放到一个类里,减轻配置和约定的负担。
这样一来,也可以用类名称和方法名称,分别匹配模板所存在的文件夹名称和模板文件名称,以方便响应渲染时模板的指定。
类名称匹配文件夹名称……
我们可怜的Controller,终于其性质不幸从一个类,堕落成了一个package……
其实,其他语言的主流MVC框架,也都是这么回事。
处理请求的根本载体是方法,映射请求的根本性质是框架把请求映射到方法。
类夹在框架和方法之间,上不上下不下,高不成低不就,当然只能沦落成package,美其名曰「表现层组件」。
Struts2还曾想扶它一把,给它点属性爽爽,大家表示,实在扶不上墙,你还是当无状态单例吧。
为什么会这样捏?
因为,处理请求给出响应这件事,根本就是,面向过程的。
所以,表现层方法,理所当然,应当是一等公民。
而Java,没给它这个机会。
它只好,把类架空。
伟大的Spring MVC,终于让我们看清了这一点。
请求进、响应出的责任,经过层层包裹,终于成了参数进,返回值出。
这是如此理所当然和天经地义。
脱离了对象的方法是什么——是函数。
函数是什么——对一组给定的自变量,给出一个确切的因变量——也就是映射。
我们浏览网页的时候在做的是什么——给出一个操作,希求得到一个确切的合逻辑的结果。
我们想要结果。
我们做出动作。
有一个函数,帮我们把动作映射成结果。
——所以还要你这个类何用?
当然,虽然你在语义上已经没有价值,但在编程元素上你还是不可缺少,物尽其用,你就当package吧!
《魏书.孝静纪第十二》:
帝不悦,曰:「自古无不亡之国,朕亦何用此活!」
文襄怒曰:「朕!朕!狗脚朕!」