迷你MVC教程

迷你MVC教程——命令行方式创建第一个Servelt小程序

本文节选自《Head First Servlets and JSP》第3章 MVC实战,根据个人配置和实操情况有所删改。本人注解部分用斜体表示。图片截取自原书,部分为个人操作截图。本人电脑配置win10x64, tomcat7, jdk10。本教程的特色处在于全程采用手工编写和命令行结合,不借助IDE开发环境,适合初次接触Servlet者当作练习的小项目。任何问题请在下方留言。

构建小型Web应用步骤

  1. 分析用户的视图以及高层体系结构
  2. 创建用于开发这个项目的开发环境
  3. 创建用于部署这个项目的部署环境
  4. 对Web应用的各个组件完成迭代式的开发和测试

(基于Beer Advisor的案例。根据用户选择的酒色提供酒的品牌建议的servlet小程序

用户视图

  • 用户最初请求的HTML表单,生成HTTP POST请求
  • 处理请求返回的result.jsp

体系结构

  1. 客户请求得到form.html页面
  2. 容器获得form.html页面
  3. 容器把这个页面返回给浏览器,用户再在浏览器上回答表单上的问题
  4. 浏览器把请求数据发送给容器
  5. 容器根据URL查找正确的servlet,并把请求传递给这个servlet
  6. servlet调用BeerExpert寻求帮助
  7. BeerExpert类返回一个回答,servlet把这个回答增加到请求对象
  8. servlet把请求转发给JSP
  9. JSP从请求对象得到回答
  10. JSP为容器生成一个页面
  11. 容器将jsp页面返回给用户
体系结构

开发环境

IDE项目的目录结构

开发环境

部署环境

将Web项目部署到容器中

部署环境

迭代式的开发和测试

  1. 构建和测试用户最初请求的HTML表单
  2. 构建控制器servlet的第一个版本,并用HTML表单测试这个控制器。这个版本通过HTML表单来调用,并打印出它接收到的参数
  3. 为模型类构建一个测试类,然后构建并测试模型类本身
  4. 把servlet升级到第2版。这个版本增加了一个功能,可以调用模型类来得到啤酒建议。
  5. 构建JSP, 把servlet升级到第3版本(增加一个功能,可以把表示流分派到JSP完成),然后再测试整个应用。

第一个表单页面的HTML

form.html包含标题文本,一个下拉列表,还有一个提交按钮(原书代码)

<html>
    <body>
        <h1 align="center">
            Beer Selection Page
        </h1>
        <!--为什么选择POST而不是GET? HTML认为这就是要调用servlet。在你的目录结构里没有一个叫“SelectBeer.do”的东西。这只是一个逻辑名-->
        <form method="POST" action="SelectBeer.do">
            <p>
                Select beer characteristics
            </p>
            Color:
            <select name="color" size="1">
                <!--我们就是这样拆个那就下拉菜单的,你可以有自己不同的选项-->
                <option value="light">light</option>
                <option value="amber">amber</option>
                <option value="brown">brown</option>
                <option value="dark">dark</option>
            </select>
            <br><br>
            <center>
            <input type="SUBMIT">
            </center>
        </form>
    </body>
</html>

部署和测试开始页面

  1. 在开发环境中创建HTML

创建这个HTML文件,取名为form.html,然后保存在开发环境的/beerV1/web/目录下

  1. 把这个文件复制到部署环境

把form.html文件的一个副本放在tomcat/webapps/Beer-vl/中

  1. 在开发环境中创建DD

创建XML文档,取名为web.xml, 把它保存在开发环境的/beer/etc/目录下(原书代码)

<!--没有必要知道这是什么意思,只需要照着输入就行-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">

  <servlet>
      <!--这是一个虚构的名字,只能在DD的其他部分使用-->
    <servlet-name>Ch3 Beer</servlet-name>
      <!--servlet类文件的完全限定名-->
    <servlet-class>com.example.web.BeerSelect</servlet-class>
  </servlet>
    
  <servlet-mapping>
    <servlet-name>Ch3 Beer</servlet-name>
      <!--不要忘记最前面有一个斜线。我们希望用户这样引用“servlet.do”只是一个约定-->
    <url-pattern>/SelectBeer.do</url-pattern>
  </servlet-mapping>
    
</web-app> 

由于原书代码是tomcat5, 版本有所变化,可以到tomcat/webapps/ROOT/WEB-INF自带目录下拷贝了一份web-xml文件,并添加相应的映射语句。

  1. 把这个文件复制到部署环境

把web.xml文件的一个副本放在tomcat/webapps/Beer-v1/WEB-INF/

  1. 启动Tomcat

Tomcat既作为Web服务器,又作为Web容器。要启动Tomcat, 先用cd命令切换到tomcat主目录,再运行bin/startup.sh

启动Tomcat

命令行中用cd切换到tomcat/bin目录下(tomcat根据版本名称不同),win系统运行startup.bat

  1. 测试页面

在浏览器中打开这个HTML页面,为此键入:

http://localhost:8080/Beer-v1/form.html

逻辑名映射到servlet类文件

以下部分为xml文件中配置的详解

  1. 用户填写表单,然后点击submit。浏览器生成了以下请求URL:

/[1]Beer-v1[2]/SelectBeer.do[3]

在用户发送的http post请求中,“/Beer-v1”不是路径的一部分。在form.html中,它只说:<form method="POST" action="SelectBeer.do>"

但浏览器为请求追加了“/Beer-v1/”,因为用户请求就来自这里。换句话说,form.html中的“SelectBeer.do”相对于其所在页面的URL。在这里,就是相对于Web应用的根:“/Beer-v1”

  1. 容器搜索DD, 找到<url-pattern>与/SelectBeer.do匹配的一个<servlet-mapping>, 这里的斜线{/}表示Web应用的上下文根,SelectBeer.do就是资源的逻辑名
  2. 容器看到对应这个<url-pattern><servlet-name>是“Ch3 Beer”。但是这并不是实际servlet类文件的名字。“Ch3 Beer”是servlet名,而不是servlet类的名字。

对容器来说,servlet只是在DD中<servlet>标记下的一个东西。servlet名只在DD中使用,以便DD的其他部分建立与该servlet的映射

  1. 容器查找<servlet-name>为“Ch3 Beer”的<servlet>标记
  2. 根据<servlet>标记中的<servlet-class>,容器可以知道有哪个servlet类负责处理这个请求。如果这个servlet还没有初始化,就会加载类,并初始化servlet
  3. 容器开始一个新线程来处理这个请求,并把请求传递给这个线程(传递给servelt的service()方法)
  4. 容器把响应(通过Web服务器)发回给用户

控制器servlet的第1版

确保HTML页面能适当地调用servlet, 而且servlet能正确地接收HTML参数。

(原书代码)

//确保与前面创建的开发结构和部署结构匹配
package com.example.web;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

//HttpServlet扩展了GenericServlet,GenericServlet则实现了Servlet接口
public class BeerSelect extends HttpServlet{
    
    //我们使用doPOST来处理HTTP请求,因为HTML表单指出,method=POST
    public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
        
        //这个方法来自ServletResponse接口
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("Beer Selection Advice<br>");
        //这个方法来自ServletRequest接口。注意这个参数与HTML<select>标记中"name"属性的值匹配
        String c = request.getParameter("color");
        //这里我们没有返回建议,只是把测试信息显示出来
        out.println("<br>Got beer color "+c);
    }
}

编译、部署和测试控制器servlet

编译servlet

用-d标志编译servlet,把类放在开发环境中

原书

编译servlet

win系统

win系统编译servelt

注解:-classpath用于设置临时环境变量,指定查找用户类文件和注释处理程序的位置;本例中由于编译servlet需要用到额外类库,tomcat提供了这些jar包,故选定tomcat/lib/servlet-api.jar为环境变量。书中还添加了classes和"."(代表当前目录)作为路径,其实可以略去。

-d用于指定编译生成的class文件存放目录,本例中存放于classes路径中,由于servlet的package语句,会自动生成com.example.web目录(若不存在)。

最后,添上servlet类的存放路径,若存放在当前目录则直接输入文件名。

部署servlet

要部署servlet, 建立.class文件的一个副本,并把它移到部署结构的/Beer-v1/WEB-INF/classes/com/example/web/目录下

测试

  1. 重启tomcat
重启tomcat

直接在tomcat/bin目录下输入startup.bat命令即可重启,无须关闭

  1. 启动浏览器,访问:

http://localhost:8080/Beer-v1/form.html

  1. 选择一种啤酒颜色,点击“Submit”
  2. 如果你的servlet能正常运行,就能在浏览器上看到servlet的响应显示为:

Beer Selection Advice

Got beer color brown

构建和测试模型类

模型规范

  • 包应当是com.example.model
  • 其目录结构应当是/WEB-INF/classes/com/model
  • 提供一个方法getBrands(), 取一个喜欢的啤酒颜色(String)作为参数,并返回一个ArrayList, 其中包含推荐的啤酒品牌(String)

为模型构建测试类

为模型创建测试类(在构建模型本身之前先创建测试类)。刚开始测试模型时,模型还在开发环境中,与其他Java类一样,此时无需启动Tomcat也能测试

作者代码(我是在创建模型类后再创建测试类)

package com.example.model;

import java.util.*;

class BeerExpertTest{
    public static void main(String[] args){
    BeerExpert be=new BeerExpert();
    List<String> testBrands1=be.getBrands("amber");
    Iterator it1=testBrands1.iterator();
    while(it1.hasNext()){
    System.out.println("try1: "+it1.next());
        }
    System.out.println("----------------------");
    List<String> testBrands2=be.getBrands("");
    Iterator it2=testBrands2.iterator();
    while(it2.hasNext()){
    System.out.println("try2: "+it2.next());
        }
    }
}

命令行中运行测试类(请在构建模型类后进行此步)

编译测试类

运行此步后,开发环境项目中的/classes/com/example/model中会生成两个.class文件(原来BeerExpert编译的.class文件被更新)。-classpath中的./src为测试类所依赖的模型类(com.example.model.BeerExpert)的查找路径。原教材中无此步,可略过。

可以切换至测试类所在包的基目录,然后运行java命令

解析运行测试类方法1

也可以直接在beerV1/下设置环境变量为classes目录

解析运行测设类方法2

构建和测试模型

(原书代码,增加了List泛型为String类)

package com.example.model;
import java.util.*;

public class BeerExpert{
    public List<String> getBrands(String color){
        List<String> brands = new ArrayList()<String>;
        if (color.equals("amber")){
            brands.add("Jack Amber");
            brands.add("Red Moose");
        }else{
            brands.add("Jail Pale Ale");
            brands.add("Gout Stout");
        }
        return(brands);
    }
} 
编译模型类

win系统与上图操作命令一致,因为模型类中没有导入其他外部类,所以无须设置classpath变量

改进servlet,调用模型得到真正的建议

第2版的servlet中,通过改进doPost()方法,调用模型来得到建议(第3版还会向JSP提供建议)

改进servlet, 第2版

先把servlet放在一边,只考虑Java

  1. 改进doPost()方法来调用模型
  2. 编译servlet
  3. 部署和测试更新后的Web应用

(作者代码,增加了List泛型部分,作者改用增强for循环遍历集合,也可采用原书中的iterator迭代器遍历)

import com.example.model;
public class BeerSelect2 extends HttpServlet{
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("Beer selection Advice<br>");
        String c = request.getParameter("color");
        BeerExpert be = new BeerExpert();
        List<String> advisedBrands= be.getBrands(c);
        for(String ad:advisedBrands){
            out.print("<br>try:"+ad);
        }
    }
}

注:原书中每次修改都替换原始的servlet, 作者采用创建新的servlet方式,以便区分不同版本的servlet——此方式需要在xml配置文件中相应修改servlet类完全限定名

第2版Servlet代码

package com.example.web;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import com.example.model.*;
import java.util.*;

public class BeerSelect2 extends HttpServlet{
    
    public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
             
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("Beer selection Advice<br>");
        String c = request.getParameter("color");
        BeerExpert be = new BeerExpert();
        List<String> advisedBrands= be.getBrands(c);
        for(String ad:advisedBrands){
            out.print("<br>try:"+ad);

    }
}
}

第2版servlet的关键步骤

主要有两件事要做:重新编译servlet和部署模型类

编译servlet

编译servlet

win系统下

win系统编译servlet

注:第2版servlet与前一版不同之处在于调用了模型,故在classpath环境变量中添加模型类的路径

部署和测试Web应用

  1. 把servlet.class文件的一个副本移到以下位置:

../Beer-v1/WEB-INF/classes/com/example/web/

这会替换第1版的servlet类文件

  1. 把模型的.class文件副本移到:

../Beer-v1/WEB-INF/classes/com/example/model/

  1. 关闭并重启tomcat
  2. 通过form.html测试这个应用

创建提供建议的JSP”视图”

<!--这是一个“页面指令”-->
<%@ page import="java.util.*" %>

<html>
    <body>
        <!--一些标准HTML模板-->
        <h1 align="center">
            Beer Recommendations JSP
        </h1>
        <br>
        <!--以下称为scriptlet代码,用于输入java代码-->
        <%
        //这里从请求对象得到一个属性
        List styles=(List)request.getAttribute("styles");
        Iterator it = styles.iterator();
        while(it.hasNext()){
            out.print("<br>try:"+it.next());
        }
        %>
    </body>
</html>

部署JSP

不用编译JSP(这个工作会在第一个请求到达容器时由容器完成)

  1. 把它命名为“result.jsp"
  2. 保存再开发环境的/web/中
  3. 将其副本移到部署环境的/Beer-v1/中

改进这个servlet,让它”调用“JSP(第3版)

这一步,我们要把servlet修改为”调用“JSP来生成输出(视图)。容器提供了一种称为”请求分派“的机制,允许容器管理一个组件调用另一个组件。我们通过使用这种机制,servlet从模型中得到信息,把它保存在请求对象中,然后把请求分派给JSP

必须对这个servlet做的重要修改

  1. 把模型组件的回答增加到请求对象,以便JSP访问
  2. 要求容器把请求转发给”result.jsp"

第3版servlet的代码

如下修改servlet, 将模型组件的回答增加到请求对象(以便JSP获取),并要求容器把请求分派给JSP

(作者代码,修改了类名和泛型,需要在xml文件修改配置)

package com.example.web;

import com.example.model.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

public class BeerSelect3 extends HttpServlet{
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
        String c = request.getParameter("color");
        BeerExpert be = new BeerExpert();
        List<String> result = be.getBrands(c);
        
       //为请求对象增加一个属性,供JSP使用。注意,JSP要寻找"styles"
        request.setAttribute("styles", result);
        //为JSP实例化一个请求分派器
        RequestDispatcher view = request.getRequestDispatcher("result.jsp");
        //使用请求分派器要求容器准备好JSP,并向JSP发送请求和响应
        view.forward(request,response);
    }
}

编译、部署和测试最后的应用

编译servlet

部署和测试Web应用

  1. 把servlet的.class文件副本移到../Beer-v1/WEB-INF/classes/com/example/web/
  2. 关闭并重启tomcat
  3. 通过form.html测试应用

  1. 主机服务器的根

  2. Web应用上下文

  3. 逻辑资源名

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335