NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

Web MVC by Example

| Comments

早期的web应用主要是静态页面的浏览。静态页面使用HTML语言来编写,放在服务器上。用户使用浏览器通过HTTP协议请求服务器上的Web页面,服务器上的Web服务器软件接收到用户发送的请求后,读取请求URI所标识的资源,加上消息报头发送给客户端的浏览器;浏览器解析响应中的HTML数据,从而向用户呈现多姿多彩的HTML页面。

CGI->Servlet->JSP->Model1->Model2

早期使用的Web服务器扩展机制是CGI,它允许用户调用Web服务器上的CGI程序。CGI的全称是Common Gateway Interface,即公共网关接口。用户通过单击某个链接或者直接在浏览器的地址栏中输入URL来访问CGI程序,Web服务器接收到请求后,发现这个请求是给CGI程序的,于是就启动并运行这个CGI程序,对用户请求进行处理。CGI程序解析请求中的CGI数据,处理数据,并产生一个响应(通常是HTML页面)。这个响应被返回给Web服务器,Web服务器包装这个响应(例如添加消息报头),以HTTP响应的形式发送给Web浏览器。

Java Servlet(Java服务器小程序Servlet=Server+Applet)是一个基于Java技术的Web组件,运行在服务器端,由Servlet容器所管理,用于生成动态的内容。Servlet是平台独立的Java类,编写一个Servlet,实际上就是按照Servlet规范编写一个Java类。

Servlet不能独立运行,它必须被部署到Servlet容器(例如:Tomcat)中,由容器来实例化和调用Servlet的方法,Servlet容器在Servlet的生命周期内包容和管理Servlet。

用户通过单击某个链接或者直接在浏览器的地址栏中输入URL来访问Servlet,Web服务器接收到该请求后,并不是将请求直接交给Servlet,而是交给Servlet容器。Servlet容器实例化Servlet,调用Servlet的一个特定方法对请求进行处理,并产生一个响应。这个响应由Servlet容器返回给Web服务器,Web服务器包装这个响应,以HTTP响应的形式发送给Web浏览器。

下面是用servlet编写的get和post请求页面例子:

web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

   <servlet>
       <servlet-name>hello</servlet-name>
       <servlet-class>me.zeph.springview.demo.HelloWorldServlet</servlet-class>
   </servlet>

   <servlet-mapping>
       <servlet-name>hello</servlet-name>
       <url-pattern>/hello</url-pattern>
   </servlet-mapping>

</web-app>
servlet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package me.zeph.springview.demo;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class HelloWorldServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      resp.setContentType("text/html");
      PrintWriter writer = resp.getWriter();
      writer.write("<h1>HelloWorld</h1>");
      writer.write("<form action='hello' method='post'>");
      writer.write("<input type='text' name='value'>");
      writer.write("<input type='submit' value='submit'>");
      writer.write("<form/>");
  }

   @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      resp.setContentType("text/html");
      String value = req.getParameter("value");
      PrintWriter writer = resp.getWriter();
      writer.write("Hi, " + value);
  }
}

这样的web开发流程都在实现一个servlet类的实例。也就是无论是业务逻辑还是前端显示,都是放在Servlet类中来完成,通过实现doGet和doPost的方法,来完成前端参数的获取和视图的渲染。

但这样做的缺点是表现逻辑、控制逻辑和业务逻辑全部写在了Java类中,导致逻辑有些混乱

之后,JSP技术出现,它由Sun和许多公司参与共同创建的一种使软件开发者可以响应客户端请求,而动态生成HTML、XML或其他格式文档的Web网页的技术标准。

它以Java语言作为脚本语言的,使Java代码和特定的预定义动作可以嵌入到静态页面中。

Sun这样做提供了非常好的前端显示,前端开发工程师也可以更好地改善页面体验,但实际上,它仍然没有解决前端显示和控制逻辑以及业务逻辑的混乱问题。

Servlet是将前端显示放在了Java类中,而JSP是将控制和业务逻辑放在了JSP中。

JSP+JavaBean是纯JSP的增强版被称为Model1。特点是使用<jsp:useBean>标准动作,自动将请求参数封装为JavaBean组件。

这样可以将部分业务逻辑封装到JavaBean中。

Model1的架构中,JSP要负责控制逻辑、表现逻辑和业务对象(JavaBean)的调用,所以实际上仍然不理想。

Model2架构其实可以认为就是我们所说的Web MVC模型,只是控制器采用Servlet、模型采用JavaBean、视图采用JSP

下面是一个Model2的例子:

web.xml配置和上面一样配置一个Servlet映射。但是Servlet只负责控制逻辑,不进行前端操作,而是直接将前端渲染分发给jsp页面进行。

HelloWorldServlet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package me.zeph.springview.demo;

import me.zeph.springview.demo.domain.User;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloWorldServlet extends HttpServlet {

   public static final String GET_PATH = "WEB-INF/view/hello-world.jsp";
   public static final String POST_PATH = "WEB-INF/view/hello-world-confirm.jsp";

   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     req.getRequestDispatcher(GET_PATH).forward(req, resp);
   }

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         User user = getUserFromRequest(req);
     String path = null;
     if (user.validate()) {
        req.setAttribute("user", user);
        path = POST_PATH;
     } else {
        req.setAttribute("error", true);
        path = GET_PATH;
     }
     req.getRequestDispatcher(path).forward(req, resp);
   }

   private User getUserFromRequest(HttpServletRequest req) {
     User user = new User();
     user.setName(req.getParameter("name"));
     user.setPassword(req.getParameter("password"));
     return user;
   }
}
hello-world.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>hello world</title>
</head>
<body>
<form action="helloWorld" method="post">
    <label for="name">name:</label><input type="text" id="name" name="name">
    <label for="password">password:</label><input type="password" id="password" name="password">
    <input type="submit" value="submit">
    <c:if test="${error}">
        login failed
    </c:if>
</form>
</body>
</html>
hello-world-confirm.jsp
1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>helloWorldConfirm</title>
</head>
<body>
hi,${user.name}
</body>
</html>
User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package me.zeph.springview.demo.domain;

public class User {
  public static final String NAME = "zeph";
  public static final String PASSWORD = "123456";
  private String name;
  private String password;

   public String getName() {
      return name;
  }

   public void setName(String name) {
      this.name = name;
  }

   public String getPassword() {
      return password;
  }

   public void setPassword(String password) {
      this.password = password;
  }

   public boolean validate() {
      return NAME.equals(name) && PASSWORD.equals(password);
  }
}

总结,Model2模式已经很好的将控制逻辑,业务逻辑和显示逻辑分开,但其中也有些位置还可以进一步改进。

例如:从参数到业务模型的转换(数据绑定),视图显示技术不容易更换(View解析)。

不过,Web MVC框架替我们解决的了这些问题(流行的Web MVC框架有:Spring MVC,Struts)。

关于Spring MVC框架的使用将留在下一篇介绍。

Comments