MVC模式实际上是众多经典的Java开发模式中的一种。它的基本原理是通过元素分解,来处理基于“请求-响应”模式的程序中的各种问题。
M (Model)—数据模型 V (View)—视图展现 C(Control)—控制器
任何一个B/S应用,其本质实际上是一个“请求-响应”的处理过程的集合体。那么MVC模式是如何被提炼出来并成为一个模式的呢? 我们来模拟一个“请求-响应”的过程,如图2-7所示。
图2-7 请求-响应模式 |
在整个请求-响应过程中,有哪些元素是必不可少的呢?
数据模型
在图中,就是顺着箭头方向进行传输的数据,它们是程序的核心载体,也是贯穿程序运行的核心内容。
对外交互
在图中,对外交互表现为一个“头”和一个“尾”。“头”指的是请求发起的地方,没有请求,一切之后的所有内容都无从谈起。“尾”指的是逻辑执行完成后,对外展现出来的执行结果。在传统意义上,我们利用HTML扩展的技术(如JSP等)来实现对外交互,在展现结果时,我们还需要完成一定的展现逻辑,比如错误展示、分支判断,等等。
程序的执行和控制
实际上它不仅是接受请求数据的场所,也是处理请求的场所。在请求处理完毕之后,它还要负责响应跳转。这个部分可能会存在着不同的表现形式。以前,我们用JSP和Servlet,后来用Struts1或者Struts2的Action。而这一变化,实际上包含了我们不断对程序进行重构的过程。
上面这3大元素,在不同的年代被赋予了不同的表现形式。例如,在很久以前,我们使用Servlet或者JSP来编写程序跳转的控制过程,有了Struts1.X后,我们使用框架所定义的Action类来处理。这些不同的表现形式有的受到时代的束缚,表现形式非常落后,有的甚至已经不再使用。但是我们忽略这些外在的表现形式就可以发现,这不就是我们已经熟悉的MVC吗?
数据模型—Model
对外交互—View
程序的执行和控制—Control
MVC的概念就这么简单,这些概念其实早已深入我们的内心,而我们所缺乏的是将其本质挖掘出来的能力。我们来看看如图2-8所示的这幅流行了很多年的讲述MVC模型的图。
图2-8 MVC模型图 |
在这幅图中,MVC三个框框各司其职,结构清晰明朗。这也成为我们进行编程开发的最强有力的理论武器,我们需要做的,只是为这些框框赋予不同的表现形式。实际上,框架就是这么干的!而框架的高明之处,仅仅在于它不仅赋予这些元素正确而恰当的表现形式,同时解决了当元素运行起来时所碰到的各种问题。因此,我们始终应该做到:程序时时有,概念心中留。只要MVC的理念在你心中,无论程序怎么变,都能做到万变不离其宗。
Struts2框架的作用
当表示层有了MVC模式,程序开发就会变得有章可循。至少,我们不会像无头苍蝇一样无从入手。MVC模式很直观地规定了表示层的各种元素,只要能够通过恰当的程序表现形式来实现这些元素,我们实际上已经在履行最佳实践了。
至此,我们不妨返璞归真,忘记所谓的框架,用最简单的方式来实现一个简单的MVC雏形。在这个过程中,我们不妨回到框架的本质问题上,思考一下究竟一个框架为表示层解决了什么样的编程难题,难道框架只是实现MVC这三大元素那么简单而已?
我们选择Registration(注册)作为业务场景。首先,我们需要一个JSP页面来呈现用户注册的各个字段、一个User类来表示用户实体以及一个RegistrationServlet类来处理注册请求。相关实现源码如代码清单2-6、代码清单2-7和代码清单2-8所示。
代码清单2-6 registration.jsp
- <form method="post" action="/struts2_example/registration">
- user name: <input type="text" name="user.name" value="downpour" />
- birthday: <input type="text" name="user.birthday" value="1982-04-15" />
- <input type="submit" value="submit" />
- </form>
代码清单2-7 User.java
- public class User {
- private String name;
- private Date birthday;
- public User() {
- }
- // 此处省略setter与getter方法
- }
代码清单2-8 RegistrationServlet.java
- public class RegistrationServlet extends HttpServlet {
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse
- resp) throws ServletException, IOException {
- // 从request获取参数
- String name = req.getParameter("name");
- String birthdayString = req.getParameter("birthday");
- // 做必要的类型转化
- Date birthday = null;
- try {
- birthday = new SmpleDateFormat("yyyy-MM-dd").
- parse(birthdayString);
- } catch (ParseException e) {
- e.printStackTrace();
- }
- // 初始化User类,并设置字段到user对象中去
- User user = new User();
- user.setName(name);
- user.setBirthday(birthday);
- // 调用业务逻辑代码完成注册
- UserService userService = new UserService();
- userService.register(user);
- req.getRequestDispatcher("/success.jsp").forward(req, resp);
- }
- }
除了上述这3段源代码外,我们还需要建立起JSP页面中的form请求与Servlet类的响应之间的关系。这一关系,是在web.xml中维护的,如代码清单2-9所示。
代码清单2-9 web.xml
- <servlet>
- <servlet-name>Register</servlet-name>
- <servlet-class>example.RegistrationServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>Register</servlet-name>
- <url-pattern>/struts2_example/registration</url-pattern>
- </servlet-mapping>
我们来看看上面的这4段代码是如何构成MVC的雏形的。
Model(数据模型)—User.java
View(对外交互)—registration.jsp
Control(程序执行和控制)—RegistrationServlet.java
URL Mapping(请求转化)—web.xml
我们可以看到MVC的实现似乎并不复杂。在不借助额外的框架帮助的前提下,只要基本知晓JSP和Servlet标准(它们是使用Java进行Web开发的规范和标准),任何程序员都可以像模像样地实现MVC模式,因为从原理上讲,MVC只是一个概念,我们只需要把这个概念中的各个元素赋予相应的程序实现即可。
不过程序终究是一个动态的执行过程。一旦程序开始运行,上面的这些程序实现就会开始遭遇种种困境。这些困境主要来源于两个方面:其一,出于程序自身的可读性和可维护性考虑,需要通过重构来解决程序的复杂性困境。其二,出于业务扩展的需求,需要通过框架级别的功能增强来解决可扩展性困境。
具体来说,上面的这些程序实现有如下不太好的地方:
1.RegistrationServlet 和很多其它的Servlet有着非常相似的执行步骤:接受参数--进行类型转换--调度业务逻辑接口执行逻辑--返回处理结果。
2.在上面的例子中,我们可以看到负责视图层跳转的RegistrationServlet 是通过硬编码的方式完成程序执行跳转的。这种方式不但不能无法支持多种新的视图技术,比如Flash、Stream数据流,同时也无法使我们从复杂的视图跳转的硬编码中释放出来。
3.类型转换 多种多样,如何做到 复用、可扩展。
4.Web容器是一个典型的多线程环境,针对每个Http请求,Web容器的线程池会分配一个特定的线程进行处理。那么如何保证在多线程环境下,处理请求的Java类是线程安全的对象?如何保证数据的流转和访问都是线程安全的?
在上面的例子中,我们使用的是基于Servlet标准的方式进行编程,扩展Servlet用于处理Http请求。然而恰恰就是这种编程模型,是一种非线程安全的编程模型,因为Servlet对象是一个非线程安全的对象。也就是说,如果我们在doPost方法中访问RegistrationServlet中所定义的局部变量,就会产生线程安全问题。
不好的地方还有很多,就不再举例子了。
Struts2框架的作用:接收参数、参数校验、流程控制、页面展现等很多功能,减轻了我们的开发负担,提高了开发效率。