迈向Spring MVC的旅程

来源:互联网 发布:知其雄守其雌的解释是 编辑:程序博客网 时间:2024/05/20 04:49

 Servlet独行天下时代

Servlet可以说是Java平台第一个用于Web开发的技术,从早期的CGI(Common Gateway Interface)时代来说,Servlet的提出是一个很大的进步--它运行于Web容器之内,提供了Session和生命周期管理等功能。最主要的是,Servlet依然是一个Java类,从中可以直接访问Java平台的各种服务,并使用相应的API支持是很正常的一件事咯,比如:调用业务对象,使用JDBC进行数据库访问等。。。

 

但Servlet本身并非万能的,他的强项上面也提到了(主要的就是:无缝地衔接业务对象与Web层对象之间的调用)。但在当时开发人员只用Servlet这一种技术可以选择的时候,单一的Servlet被赋予了过多的使命,从而导致开发人员会将各种逻辑混于一处,包括流程控制逻辑,视图显示逻辑,业务逻辑,数据访问逻辑。这就造成了后期系统维护的时候非常苦难。

不妨看看一段Web应用中的Servlet实现代码:

 

/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse *      response) */protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {// TODO Auto-generated method stubdoPost(request, response);}/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse *      response) */protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {// TODO Auto-generated method stubString para1 = request.getParameter("para1");String para2 = request.getParameter("para2");response.setContentType("text/html");PrintWriter out = response.getWriter();out.print("<html>");out.print("<head><title>Title Page</title></head>");out.print("<body>");out.print("<table>");out.print("<tr><td>title</td><td>content</td></tr>");String sql = "select * from newTable where c1 = ? and c2 = ?";Connection con = getConnection();try {PreparedStatement pstr = con.prepareStatement(sql);pstr.setString(1, para1);pstr.setString(2, para2);ResultSet rs = pstr.executeQuery();while (rs.next()) {out.print("<tr>");out.print("<td>"+rs.getString(1)+"</td>");out.print("<td>"+rs.getString(2)+"</td>");out.print("</tr>");}rs.close();pstr.close();con.close();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}out.print("</table>");out.print("</body>");out.print("</html>");out.close();}private Connection getConnection() {Connection con = null;try {Class.forName("jdbc:");String url = "";con = DriverManager.getConnection(url, "root", "mysql");} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}return con;}


 如上代码可读性极差,这个呢我们可以放在一边不说,单纯的数据库访问逻辑和业务处理逻辑与对应的视图渲染逻辑相互混杂,就这一点,就已经让今天的我们觉得该Servlet的实现是如此的不堪入目,没有相应的分离,直接导致逻辑无法重用,这样系统的维护起来成本可想而知,即使在重构,out.print();这样的代码也没有办法脱离。

Servlet的总结:

1.Servlet的存在,起初不是一个Web应用程序只存在一个Servlet实现。实际上,Servlet时代之初,我们更多的是使用“一个Servlet对应一个Web请求”的方式。对于实现简单的Web应用来说,分散的程度看起来还不算夸张。可是,随着应用规模的扩展,我们的视图都是通过out.println来渲染的,试想一下,开发和维护out.println的工作量是何等的恐怖?

2.我们的代码示例只是给出一个简单的视图渲染逻辑。可是,随着页面逻辑的膨胀,要维护这么一堆几乎无法“纵观全局”的out.println,我们这些开发人员怎么办?暂且不谈开发人员大都不熟悉HTML等页面标记语言,就算熟知,要在浩瀚的out.println中寻找要更改的位置,并要保证不会造成之前的显示逻辑的破坏,谈何容易呢?即使使用ECS这样的类库,我想也不会减去多少维护这些out.println的痛苦

3.因为视图逻辑是以Java代码的形式写死到Servlet中的,所以如果视图逻辑需要改动的话,我们就得更改Servlet的代码并重新编译。开发人员或许会说视图逻辑不归我们管,那是美工和前台人员的工作,可是,写死到java代码中的视图逻辑,你又能逃脱了干系不成?

上面的分析告诉我们,单靠Servlet一人之力,我们无法解决视图逻辑与Servlet紧密耦合的问题。但是无论Servlet怎么样,他至少也奠定了Java面向Web开发的先河。

 

繁盛一时的JSP时代

为了能够将Servlet中的视图渲染逻辑以独立的单元抽取出来,我们通常使用“模板化(templating)”的方法。JSP的提出,成为Java平台上开发Web应用程序事实上的模板化视图标准。

有了JSP的加入,我们就可以将原先Servlet中通过out.println语句输出的视图渲染逻辑,抽取到.jsp后缀名的模板文件中。这样,我们就有了如下代码:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><!DOCTYPE><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Title Page</title></head><body><table border="1"><tr><td>Title1</td><td>Title2</td><td>Title3</td></tr><c:forEach items="${infoList }" var="infoBean"><tr><td>${infoBean.c1 }</td><td>${infoBean.c2 }</td><td>${infoBean.c3 }</td></tr></c:forEach></table></body></html>


现在,由JSP专职负责视图的渲染工作,我们的Servlet就得以解脱,如今的Servlet代码如下:

public class MagicServlet extends HttpServlet {private static final long serialVersionUID = 1L;/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse *      response) */protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {// TODO Auto-generated method stubdoPost(request, response);}/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse *      response) */protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {// TODO Auto-generated method stubString para1 = request.getParameter("para1");String para2 = request.getParameter("para2");response.setContentType("text/html");PrintWriter out = response.getWriter();out.print("<html>");out.print("<head><title>Title Page</title></head>");out.print("<body>");out.print("<table>");out.print("<tr><td>title</td><td>content</td></tr>");String sql = "select * from newTable where c1 = ? and c2 = ?";Connection con = getConnection();try {PreparedStatement pstr = con.prepareStatement(sql);pstr.setString(1, para1);pstr.setString(2, para2);ResultSet rs = pstr.executeQuery();List<InfoBean> list = new ArrayList<InfoBean>();InfoBean infoBean;while (rs.next()) {infoBean = new InfoBean();infoBean.setC1(rs.getString(1));infoBean.setC2(rs.getString(2));infoBean.setC3(rs.getString(3));list.add(infoBean);}rs.close();pstr.close();con.close();request.setAttribute("infoList", list);request.getRequestDispatcher("Index.jsp").forward(request, response);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}private Connection getConnection() {Connection con = null;try {Class.forName("jdbc:");String url = "";con = DriverManager.getConnection(url, "root", "mysql");} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}return con;}


由于以上代码没有进行进一步封装,但是从上面我们也看出比单纯的Servlet孤军奋战强多了,如此看来,我们离成功仅仅是一步之遥啦!但是,在此之前,我们还是经历了一段曲折的日子。

实际上如果当初我们是“技术布道者”。能够严格地界定JSP的基本使命的话,我想我们早就迈入了Web MVC的世界了。不过,我们更喜欢“怨天尤人”的话,我们可以把责任推卸给JSP本身,我们都知道,JSP与其他模板技术有一个主要的区别,那就是--它最终是编译为Servlet来运行的,这一层使得JSP拥有比其他模板技术更大的能量。

1.我们可以直接在JSP中编写JAVA代码,通过Sripetlet(脚本)。只要愿意,任何的应用程序逻辑几乎都能够融入JSP文件中。那几年包括各大论坛以及一些JSP技术书籍,把不应该放在JSP内的逻辑,也全都写入了JSP的示例中,使得我们不上当都难。

2.使用Servet处理Web请求,我们需要在web.xml文件中,注册相应的请求URL与具体的处理Servlet之间的一个映射关系。之前说过,最初阶段,我们是一个Web请求对应一个Servlet进行处理,所以web.xml中就过多地充斥着映射配置信息,如果JSP的话,则可以省去这些繁琐,直接通过链接就可以了,无需任何配置,这就助长了JSP的“歪风”,直接使用JSP替代Servlet处理Web请求。对于几个简单的页面还理得清关系,一旦上了规模就招架不住了。如下代码便是:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><%@ pageimport="java.sql.Connection,java.sql.DriverManager,java.sql.PreparedStatement,java.sql.ResultSet;"%><!DOCTYPE><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Title Page</title></head><body><%String para1 = request.getParameter("para1");String para2 = request.getParameter("para2");Class.forName("your.driver.class.name");Connection con = DriverManager.getConnection("serverAddress");String sql = "select * from newTable where c1 = ? and c2 = ?";PreparedStatement pstr = con.prepareStatement(sql);pstr.setString(1, para1);pstr.setString(2, para2);ResultSet rs = pstr.executeQuery();%><table border="1"><tr><td>Title1</td><td>Title2</td><td>Title3</td></tr><%while(rs.next()){%><tr><td><%=rs.getString(1) %></td><td><%=rs.getString(2) %></td><td><%=rs.getString(3) %></td></tr><%}%></table></body></html>


 

从上面的代码我们隐约的感觉它和Servlet一样,有点“重蹈覆辙”了,此时包括SUN公司也注意到了这一点,所以,这样促使了JSP MOdel 1的诞生。

在JSP独大的世界中引入了JavaBean,通过JavaBean对相关业务逻辑的封装,我们完成了初步的关注点分离,不过,JSP Model 1的提出并么有进一步地界定JSP的基本职责,本该一心关注视图渲染逻辑的JSP,现在依然紧揣着本该是Servlet强项的Web流程管理功能不放。

JSP Model 1也有其优势性,我们可以用它快速的构建Web应用程序的原型,但是,切忌,不要以这种架构用于实际的生产环境!!!

 

Servlet与JSP的结合

JSP的不良诱惑使得我们走上了歧路,本已经尽在咫尺的良好框架,在经历了一段封层的岁月之后,又重现光芒。让我们回到最初的时代。JSP作为视图模板存在,不管他有多大的能耐,我们只让它负责视图的渲染工作,这样,对于JSP来说,只需要页面开发人员或者前端开发人员来负责和管理即可。而已经脱离了视图渲染逻辑Servlet,现在只让它请求处理流程的控制以及业务层的交互,这部分工作则由Java开发人员负责。至此,我们不仅将各个关注点清晰地分离出来,而且也分离了Java开发人员与前台开发人员之间的职责,这样对于一个负责并且需要多人协作的团队来说,是至关重要的。

通过结合Servlet和JSP,并且引入封装业务层逻辑的JavaBean,我们得到了JSP Model 1的升级版架构,即JSP Model 2,如下图:

 Model 2中,与我们重构过程结果所展示的一样,由Servlet来管理处理流程,由JSP来负责视图的渲染,由JavaBean封装业务逻辑并负责与数据层进行交互,Model 2 提出可以说是Java平台Web应用开发过程中一个里程碑,它促进了Web开发领域至今一直沿用的MVC设计模式的广泛应用。

虽然Model 2 已经具备了使用MVC模式实现的Web应用架构的雏形,但并非严格意义上的MVC。为了搞清楚其间的差别。我们来简单的回顾一下MVC模式中几个组件,如下图:

 

 

可见,最初意义上的MVC模式(即:Model 1),在视图与模型之间的数据同步工作采用模型Push到视图形式完成的,而对于Web应用来说,无法实现从模型Push数据到视图。所以,我们对Modle 1做了调整,由控制器与模型进行交互,在原来通知模型数据更新的基础上,还要获取更新的结果一并转发给视图,也就是说,现在改由控制器从模型中Pull数据给视图,这种意义上的MVC称为Web MVC,也是当今大多数Web开发框架所使用的架构模式。

实际上,JSP Model 2 已经十分接近Web MVC架构了,但是真正的和Web MVC应用框架还是有些差距的,从上面的图我们可以发现,Servlet是作为控制器的角色存在的,但是,该架构示意图并没有进一步规定,具体应用中到底只需要一个控制器,还是多个控制器,这样就造成如下几种情况:

Web应用程序中使用多个Servlet作为控制器:这实际上也是从最初Servlet步入Java平台Web开发领域后使用最多的模式,即一个Servlet对应一个Web请求的处理,并借助Web容器的URL映射匹配能力来解决Web请求到具体的Servlet的映射。自然,我们就需要在web.xml中配置,但随着硬哟娜不过规模的增加,web.xml的体积将愈加庞大,这将不利于整个系统的开发和维护工作。所以这种开发随着开发理念的更新自然淡出了我们的视野

Web应用程序中使用单一Servlet作为集中控制器:这样所有的Web请求都映射到了集中的Servlet控制器来处理,这样就无法借助Web容器的URL映射匹配能力工作了,这些逻辑都是硬编码到Servlet控制器中来。往往这些逻辑无法重用,然而最主要的是我们一旦写死,要调整URL映射的处理,就得修改Servlet控制器的代码并重新编译,灵活性和扩展性无从谈起。不过,情况并不是我们想象的那么坏,引入设计模式这种方式依然是我们比较倾向使用的控制器实践方式。去除架构中控制逻辑的硬编码,并尽可能地复用Web应用程序开发过程的一些通用逻辑。而在JSP Model 2架构基础上发展起来的各种Web MVC应用框架,恰好顺应了历史的需求。现在,我们步入了各种Web应用程序框架盛行的时代。

 

数风流人物,还看今朝

Web框架存在的意义在于,它们为Web应用程序的开发提供了一套可复用的基础设施,这样开发人员只需要关注特定于每个应用的逻辑开发工作,而不需要每次都重复那些可以统一处理的通用逻辑。当前Web开发框架有2种:

请求驱动的Web框架(request-driven framework):又称为request/response框架。顾明思议,这种框架是基于Servlet的请求/响应处理模型构建的。

事件驱动Web开发框架(event-driven web framework):又称基于组件的Web开发框架。这套框架采用与Swing等GUI开发框架类似的思想,包括现在流行的JSF都属于这一类(我个人不倾向于这套框架)。

Spring MVC属于请求驱动的Web框架,然而对于请求驱动的Web框架来说,它们大多数是在JSP Model 2的基础上发展而来。如前所述,在JSP Model 2 中,我们更倾向于使用单一Servlet作控制器的实践方法。实际上,现在的请求驱动Web框架也大都如此,但为了避免上面提到的问题,这些框架通常会结合Front Controller以及Page Controller模式,对单一Servlet的控制器做进一步改进,对原先过于耦合的各种控制逻辑进行逐步的分离。具体来说,就是由原来的单一Servlet作为整个应用程序的Front Controller,该Servlet接收到具体的Web处理请求之后,会参照预先可配子的映射信息,将待处理的Web处理请求转发给此以及的控制器(sub-controller)来处理,如下图:

在控制器(Front Controller)接收到处理请求之后,它会对Web请求的URL信息进行分析,然后根据分析结果参照配置文件的操作流程,将当前Web请求转发到次级控制器进行处理,原先单一的Servlet变得如此灵活,这样就可以提高了整个Web应用中控制器逻辑的可复用性。(以上次级控制器的总称为:page Controller)

Struts框架如此,webworok框架也亦如此,实现原理都是将单一的Servlet变得灵活,提高整个Web应用的控制器逻辑的可复用性,那么SpringMVC呢?当然也一样,下面的章节中我们开始SpringMVC的初体验吧!!

 

本章小结:我们主要回顾Java平台上的Web发展历程,从Servlet独当一面开始到JSP的繁盛一时再到Servlet和JSP的结盟,最终演化成今天Web开发框架盛行的时代不惊让我感慨万分,作为一个JavaWeb开发人员我的确感到自身的不足,也真正的意识到无论什么技术我们不要一味的为了实现功能而不顾一切,如果不是当时JSP的“歪风”可能今天的Web框架也更加成熟,要了解一门语言必须从头开始一步一个脚印!

原创粉丝点击