互联网+智慧城市:核心技术及行业应用
上QQ阅读APP看书,第一时间看更新

2.5 Java Web技术

2.5.1 概述

Java Web,是用Java来解决相关Web互联网领域问题的技术总和。Web包括:Web服务器和Web客户端两部分。Java在客户端的应用有Java applet,不过使用得很少,姑且认为JavaSctipt是Java技术在客户端的一种借鉴式应用吧;Java在服务器端的应用非常丰富,例如Servlet、JSP和第三方框架等。Java技术为Web的发展注入了强大的动力。

Java的Web框架虽然各不相同,但基本也都是遵循特定的路数:使用Servlet或者Filter拦截请求;使用MVC的思想设计架构;使用约定、XML或Annotation实现配置,运用Java面向对象的特点,面向对象实现请求和响应的流程,支持Jsp、Freemarker、Velocity等视图。

JSP(Java Server Pages)是由Sun公司倡导、许多公司参与而建立的动态网页技术标准。它在HTML代码中嵌入Java代码片段(Scriptlet)和JSP标签,构成了JSP网页。在接收到用户请求时,服务器会处理Java代码片段,然后生成处理结果的HTML页面返回给客户端,客户端的浏览器将呈现最终页面效果。

JSP技术所开发的Web应用程序是基于Java的,它拥有Java语言跨平台的特性,以及业务代码分离、组件重用、预编译等特征,并且继承和拥有了Java Servlet所有的功能和特性。

2.5.2 MVC框架

模型视图控制器(MVC,Model View Controller),用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特地发展起来用于将传统的输入、处理和输出功能映射在一个逻辑的图形化用户界面的结构中。MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是指控制器,使用MVC的目的是将M和V实现代码分离,从而使同一个程序可以使用不同的表现形式,如一批统计数据可以分别用柱状图、饼图来表示。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。

1 Model(模型):表示应用程序核心(例如数据库记录列表)。

2 View(视图):显示数据(数据库记录)。

3 Controller(控制器):处理输入(写入数据库记录)。

MVC模式同时提供了对HTML、CSS和JavaScript的完全控制。

4 Model(模型):应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。

5 View(视图):应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。

6 Controller(控制器):应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

MVC分层有助于管理复杂的应用程序,例如,您可以在不依赖业务逻辑的情况下专注于视图设计,同时也让应用程序的测试更加容易。MVC分层同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。最典型的MVC就是JSP+servlet+javabean的模式,如图2.21所示。

图2.21 MVC架构

JavaBean作为模型,既可以作为数据模型来封装业务数据,又可以作为业务逻辑模型来包含应用的业务操作。其中,数据模型用来存储或传递业务数据,而业务逻辑模型接收到控制器传过来的模型更新请求后,执行特定的业务逻辑处理,然后返回相应的执行结果。JavaBean是一种Java语言写成的可重用组件,类必须是具体的和公共的,并且具有无参数的构造器。JavaBean通过提供符合一致性设计模式的公共方法将内部域成员属性暴露。众所周知,属性名称符合这种模式,其他Java类可以通过自身机制发现和操作这些JavaBean属性。换句话说,Javabean就是一个Java的类,只不过这个类你要按上面提到的一些规则来写,例如必须是公共的,无参数构造等,按这些规则写完之后,这个Javabean可以在程序里被方便地重用,使开发效率提高。JavaBean和Server Bean[通常称为Enterprise JavaBean (EJB)]有一些相同之处。它们都是用一组特性创建,以执行其特定任务的对象或组件。它们还有从当前所驻留服务器上的容器获得其他特性的能力,这使得bean的行为根据特定任务和所在环境的不同而有所不同。下面是一个StudentBean的java代码示例。

  public class StudentsBean implements java.io.Serializable
  {
    private String firstName = null;
    private String lastName = null;
    private int age = 0;
    public StudentsBean() {
    }
    public String getFirstName(){
     return firstName;
    }
    public String getLastName(){
     return lastName;
    }
    public int getAge(){
     return age;
    }
    public void setFirstName(String firstName){
     this.firstName = firstName;
    }
    public void setLastName(String lastName){
     this.lastName = lastName;
    }
    public void setAge(int age) {
     this.age = age;
    }
  }

接下来在JSP中就可以使用下面的方法使用JavaBean。

<jsp:useBean id="id" class="bean编译的类" scope="bean作用域">
  <jsp:setProperty name="bean的id" property="属性名" 
          value="value"/>
  <jsp:getProperty name="bean的id" property="属性名"/>
  ...........
</jsp:useBean>

JSP作为表现层,负责提供页面为用户展示数据,提供相应的表单(Form)用于提供用户的请求,并在适当的时候(点击按钮)向控制器发出请求进行更新,这部分内容将在第4.4.3节详细阐述。

Serlvet作为控制器,用来接收用户提交的请求,然后获取请求中的数据,将之转换为业务模型需要的数据模型,然后调用业务模型相应的业务方法进行更新,同时根据业务执行结果选择要返回的视图。这部分内容将在第4.4.4节详细阐述。

基于MVC的常用框架有Struts和Spring等,所谓框架,通常是代码重用;而设计模式是设计重用;架构则介于两者之间,部分代码重用,部分设计重用。框架与设计模式虽然相似,但却有着根本的不同。设计模式是对在某种环境中反复出现的问题以及解决该问题的方案的描述,它比框架更抽象;框架可以用代码表示,也能直接执行或复用,而对模式而言只有实例才能用代码表示;设计模式是比框架更小的元素,一个框架中往往含有一个或多个设计模式,框架总是针对某一特定应用领域,但同一模式却可适用于各种应用。可以说,框架是软件,而设计模式是软件的知识。

本书限于篇幅,不再对纷杂的各类框架进行详述,只在这里简单介绍几种常用的JavaWeb框架。

(1)Struts

Struts是Apache软件基金下Jakarta项目的一部分。Struts框架的主要架构设计和开发者是Craig R.McClanahan。Struts是Java Web MVC框架中不争的“王者”。经过长达九年的发展,Struts已经逐渐成长为一个稳定、成熟的框架,并且占有了MVC框架中最大的市场份额。但是Struts在某些技术特性上已经落后于新兴的MVC框架。面对Spring MVC、Webwork2这些设计更精密,扩展性更强的框架,Struts受到了前所未有的挑战。但站在产品开发的角度来讲,Struts仍然是最稳妥的选择。

Struts由一组相互协作的类(组件)、Servlet以及jsp tag lib组成。基于Struts构架的Web应用程序基本上符合JSP Model2的设计标准,可以说是MVC设计模式的一种变化类型。根据上面对framework的描述,很容易理解为什么说Struts是一个Web framework,而不仅仅是一些标记库的组合。但Struts也包含了丰富的标记库和独立于该框架工作的实用程序类。Struts有其自己的控制器(Controller),同时整合了其他的一些技术去实现模型层(Model)和视图层(View)。在模型层,Struts可以很容易地与数据访问技术相结合,包括EJB、JDBC和Object Relation Bridge。在视图层,Struts能够与JSP、Velocity Templates、XSL等这些表示层组件相结合。

(2)Spring

Spring实际上是Expert One-on-One J2EE Design and Development一书中所阐述的设计思想的具体实现。在这本书中,Rod Johnson倡导J2EE实用主义的设计思想,并随书提供了一个初步的开发框架实现(interface21开发包)。而Spring正是这一思想的更全面和具体的体现。Rod Johnson在interface21开发包的基础之上,进一步地改造和扩充,发展为一个更加开放、清晰、全面、高效的开发框架。

Spring是一个开源框架,由Rod Johnson创建并且在他的著作《J2EE设计开发编程指南》里进行了描述。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBeans来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度来讲,任何Java应用都可以从Spring中受益。Spring解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式)轻量级开源框架,是一个轻量的控制反转和面向切面的容器框架。当然,这个描述有点过于简单。但它的确概括出了Spring是做什么的。Spring的特征如下。

1 轻量:从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布,并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的。典型地,Spring应用中的对象不依赖于Spring的特定类。

2 控制反转:Spring通过一种称作控制反转(IoC)的技术促进了低耦合。当应用了IoC,一个对象依赖的其他对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。

3 面向切面:Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(如审计和事务管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其他的系统级关注点,如日志或事务支持。

4 容器:Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(Prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。

5 框架:Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等),将应用逻辑的开发留给了开发人员。

6 MVC:Spring的作用是整合,但不仅仅限于整合,Spring框架可以被看作是一个企业解决方案级别的框架。客户端发送请求,服务器控制器(由DispatcherServlet实现的)完成请求的转发,控制器调用一个用于映射的类(HandlerMapping),该类用于将请求映射到对应的处理器来处理请求。HandlerMapping将请求映射到对应的处理器Controller(相当于Action)。在Spring当中如果写一些处理器组件,一般实现Controller接口,在Controller中就可以调用一些Service或DAO来进行数据操作。ModelAndView用于存放从DAO中取出的数据,还可以存放响应视图的一些数据。如果想将处理结果返回给用户,那么在Spring框架中还提供一个视图组件(ViewResolver),该组件根据Controller返回的标示,找到对应的视图,将响应(Response)返回给用户。

所有Spring的这些特征具有编写更干净,更可管理,并且更易于测试的代码的特点。它们也为Spring中的各种模块提供了基础支持。

2.5.3 JSP基本构成

JSP页面主要由指令标签、HTML标记语言、注释、嵌入Java代码、JSP动作标签5个元素组成,如图2.22所示。

1. 指令标签

图2.22所示代码的第一行是一个JSP的指令标签,指令标签不会产生任何内容输入网页中,主要用于定义整个JSP页面的相关信息,如使用的语言、导入的类包、指定错误处理页面。

图2.22 JSP的基本构成

(1)page指令

这是JSP页面最常用的指令,用于定义整个JSP页面的相关属性,这些属性在JSP被服务器解析成Servlet时会转换成相应的Java程序代码。page指令的语法格式如下。

  <%@ page attr1="value1" attr2="value2" …… %>

下面对一些常用的属性进行介绍。

1 language属性:该属性用于设置JSP页面使用的语言,目前只支持Java语言,以后可能会支持其他语言,如C++、C#等。该属性的默认值是Java。

2 extends属性:该属性用于设置JSP页面继承的Java类,所有JSP页面在执行之前都会被服务器解析成Servlet,而Servlet是由Java类定义的,所以JSP和Servlet都可以继承指定的父类。该属性并不常用,而且有可能影响服务器的性能优化。

3 import属性:该属性用于设置JSP导入的类包。JSP页面可以嵌入Java代码片段,这些Java代码在调用API时需要导入相应的类包。

4 pageEccoding属性:该属性用于定义JSP页面的编码格式,也就是指定文件编码。JSP页面中的所有代码都使用该属性指定的字符集,如果该属性值设置为iso-8859-1,那么这个JSP页面就不支持中文字符。通常我们设置编码格式为GBK或UTF-8。

5 contentType属性:该属性用于设置JSP页面的MIME类型和字符编码,浏览器会据此显示网页内容。

(2)include指令

include指令用于文件包含。该指令可以在JSP页面中包含另一个文件的内容,但是它仅支持静态包含,也就是说被包含文件中的所有内容都被原样包含到该JSP页面中。如果被包含文件中有代码,将不被执行。被包含的文件可以是一段Java代码、HTML代码或者是另一个JSP页面,例如:<%@include file=“date.jsp”%>。

(3)taglib指令

该指令用于加载用户自定义标签,自定义标签将在后面章节进行讲解。使用该指令加载后的标签可以直接在JSP页面中使用,例如:%@taglib prefix="view"uri="/WEB-INF/tags/view.tld"%。

2. 嵌入Java代码

(1)代码片段

Java代码片段被包含在“<%”和“%>”标记之间。可以编写单行或多行的Java代码,语句以“;”结尾,其编写格式与Java类代码格式相同。图2.22的第9~14行嵌入了Java代码片段。下面代码实现了循环输出九九乘法表。

  <body>
<%
long startTime = System.nanoTime();
%>
输出九九乘法表
<br>
<%
for (int i = 1; i <= 9; i++) { 
for (int j = 1; j <= i; j++) { 
String str = j + "*" + i + "=" + j * i;
out.print(str + "&nbsp"); 
}
out.println("<br>");
}
long time = System.nanoTime() - startTime;
%>
生成九九乘法表用时
<%
out.println(time / 1000);
%>
毫秒。
</body>

(2)声明

声明脚本用于在JSP页面中定义全局的成员变量或方法,它们可以被整个JSP页面访问,服务器执行时会将JSP页面转换为Servlet类,在该类中会把使用JSP声明脚本定义的变量和方法定义为类的成员。

1 定义全局变量,例如:<%! long startTime=System.nanoTime();%>。

2 定义全局方法,例如:

<%!
  int getMax(int a, int b) {
  int max = a > b ? a : b;
   return max;
  }
%>

3 JSP表达式:JSP表达式可以直接把Java的表达式结果输出到JSP页面中。表达式的最终运算结果将被转换为字符串类型,因为在网页中显示的文字都是字符串。JSP表达式的语法格式为:<%=表达式%>。

3. 注释

图2.22中第8行使用了HTML语言的注释格式,由于JSP页面由HTML、JSP、Java脚本等组成,所以在其中可以使用多种注释格式,本节将对这些注释的语法进行讲解。

1 HTML注释:<!--注释文本-->。

2 JSP注释:<%--注释文本--%>。

3 代码注释://单行注释;/ *多行注释* /;/ **JavaDoc注释,用于成员注释* /。

4 JSP动作标签。

JSP 2.0规范中提供了20个标准的使用XML语言写成的动作标签,这些标签可用来实现特殊的功能,如转发用户请求、操作JavaBean、包含其他文件等。动作标签是在请求处理阶段按照在页面中出现的顺序被执行的。JSP动作标签的优先级低于指令标签,在JSP页面被执行时将首先进入翻译阶段,程序会先查找页面中的指令标签,将它们转换成Servlet,从而设置整个JSP页面。本节将介绍常用的动作标签。

(1)<jsp:include>

这个动作标签可以将另外一个文件的内容包含到当前JSP页面中。例如:

  <jsp: include page="validate.jsp"/>

<jsp:include>标签与include指令都拥有包含其他文件内容到当前JSP页面中的能力,但有一定区别,具体如下。

1 <jsp:include>标签是相对路径来指定被包含资源。

2 include指令包含的资源是静态的,被包含资源与当前JSP页面是一个整体,资源相对路径在JSP页面转换为在Servlet发生;而<jsp:include>标签包含JSP动态资源时,资源相对路径的解析在请求处理时发生;当前页面和被包含的资源是两个独立的实体,被包含的页面会对包含它的JSP页面中的请求对象进行处理,然后将处理结果作为当前JSP页面的包含内容,与当前页面内容一起发送到客户端。

(2)<jsp:forward>

<jsp:forward>是请求转发标签。该标签可以将当前页面的请求转发给其他Web资源,如另一个JSP页面、HTML页面、Servlet等;而当前页面可以不对请求进行处理,或者做一些验证性的工作和其他工作。例如:

  <jsp:forward page="addUser.jsp"/>

(3)<jsp:param>标签

该标签可以作为其他标签的子标签,为其他标签传递参数。例如:

  <jsp:forward page="addUser.jsp">
    <jsp:param name="username"value="qianglei" />
  </jsp:forward>

4. JSP内置对象

内置对象也可以叫作隐含对象,是不需要预先声明就可以在脚本中直接使用的对象。本节介绍几个JSP常用内置对象。

(1)request对象

request对象代表了客户端的请求信息,主要用于接收通过HTTP传送到服务器端的数据(包括头信息、系统信息、请求方式及请求参数等)。request对象的作用域为一次请求。

1 获取请求参数值:在一个请求中,可以通过使用“?”的方式来传递参数,然后通过request对象的getParameter()方法来获取参数的值。例如:

String id = request.getParameter("id")。

2 获取form表单的信息:除了获取请求参数中传递的值之外,我们还可以使用request对象获取从表单中提交过来的信息。对于单一或者单选标签元素都可以使用getParameter()方法来获取其具体的值,但对于复选框及多选列表框被选定的内容就要使用getParameterValues()方法来获取了,该方法会返回一个字符串数组,通过循环遍历这个数组就可以得到用户选定的所有内容。

举例如下,创建index.jsp页面文件,在该页面中创建一个form表单,在表单中包含复选框。关键代码如下。

  <form>
  请选择个人爱好:
    <div style=“width: 400px”>
   <input name=“like”type=“checkbox”value=“唱歌跳舞” />唱歌跳舞
    <input name=“like”type=“checkbox”value=“上网冲浪” />上网冲浪
   <input name=“like”type=“checkbox”value=“户外登山” />户外登山
    <input name=“like”type=“checkbox”value=“体育运动” />体育运动
   <input name=“like”type=“checkbox”value=“读书看报” />读书看报
    <input name=“like”type=“checkbox”value=“欣赏电影” />欣赏电影
   </div>
    </form>

接下来编写show.jsp页面文件,该页面是用来处理请求的,关键代码如下。

<li>
请选择个人爱好:
<% 
String[] like =request.getParameterValues(“like”);
for(int i =0;i<like.length;i++){
%>
<%= new String(like[i].getBytes("ISO8859_1"),"GBK")+"  " %>
<%  }  %>
</li>

3 获取请求客户端信息:在request对象中通过相应的方法还可以获取到客户端的相关信息,如HTTP报头信息、客户信息提交方式、客户端主机IP地址、端口号等。这些方法如表2.11所示。

4 在作用域中管理属性:通过使用setAttribute()方法可以在request对象的属性列表中添加一个属性,然后在request对象的作用域范围内通过使用getAttribute()方法将属性取出;此外,还可以使用removeAttribute()方法将一个属性删除掉。示例代码index.jsp如下。

  <%@ page language="java" contentType="text/html" pageEncoding="GBK"%>
<%@ page import="java.util.*" %>
   <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
  <title>'index.jsp'</title>
 </head>
 <body>
  <% 
   request.setAttribute("date",new Date()); //添加一个属性
  %>
  <ul style="line-height: 24px;">
   <li>获取date属性:<%=request.getAttribute("date") %></li>
   <!-- 将属性删除 -->
   <%request.removeAttribute("date"); %>
   <li>删除后再获取date属性:<%=request.getAttribute("date") %></li>
  </ul>
   </body>
</html>

表2.11 request获取客户端信息方法说明

5 cookie管理:cookie是小段的文本信息,通过使用cookie可以标识用户身份、记录用户名及密码、跟踪重复用户。cookie在服务器端生成并发送给浏览器,浏览器将cookie的key/value保存到某个指定的目录中,服务器的名称与值可以由服务器端定义。通过cookie的getCookies()方法可以获取到所有的cookie对象集合,然后通过cookie对象的getName()方法获取到指定名称的cookie,再通过getValue()方法即可获取cookie对象的值。另外,将一个cookie对象发送到客户端使用了response对象的addCookie()方法。

(2)response对象

response代表的是对客户端的响应,主要是将JSP容器处理过的对象传回客户端。response对象的常用方法如表2.12所示。

1 重定向网页:重定向是通过使用sendRedirect()方法,将响应发送到另一个指定的位置进行处理。例如:response.sendRedirect("www.jumh.cn")。

2 处理HTTP文件头:setHeader()方法通过两个参数——头名称与参数值的方式来设置HTTP文件头。例如,设置网页每5秒自动刷新一次:response.setHeader ("refresh","5");设置2秒后自动跳转至指定的页面:response.setHeader("refresh","2;URL=welcome.jsp")。

表2.12 response对象的常用方法

3 设置输出缓冲:使用response对象的setBufferSize()方法可以设置缓冲区的大小。例如,设置缓冲区大小为0kB,即不缓冲:response.setBufferSize(0)。

(3)session对象

session是与请求有关的会话对象,用于保存和存储页面的请求信息。

session对象的setAttribute()方法可实现将信息保存在session范围内,例如:

  String sessionMessage = "session练习";
  session.setAttribute("message",sessionMessage);

而通过getAttribute()方法可以获取保存在session范围内的信息,例如:

  String message = (String)session.getAttribute("message");
  out.print("保存在session范围内的值为:"+message);

从session中移除指定的绑定对象可以用removeAttribute(String key)实现;

销毁session可以用session.invalidate()实现。

(4)application对象

application对象可将信息保存在服务器中,直到服务器关闭,否则application对象中保存的信息会在整个应用中都有效。与session对象相比,application对象的生命周期更长,类似于系统的“全局变量”。application对象的常用方法如表2.13所示。

表2.13 application对象的常用方法

1 application对象访问应用程序初始化参数的方法介绍如下。

getInitParameter(String name):返回一个已命名的参数值。

getAttributeNames():返回所有已定义的应用程序初始化名称的枚举。

2 与session对象相同,也可以在application对象中设置属性。与session对象不同的是,session只是在当前客户的会话范围内有效,当超过保存时间,session对象就被收回;而application对象在整个应用区域中都有效。application对象管理应用程序环境属性的方法分别介绍如下。

getAttributeNames():获得所有application对象使用的属性名。

getAttribute(String name):从application对象中获取指定对象名。

setAttribute(String key,Object obj):使用指定名称和指定对象在application对象中进行关联。

removeAttribute(String name):从application对象中去掉指定名称的属性。

2.5.4 Servlet

(1)概述

Servlet是使用Java Servlet接口(API)运行在Web应用服务器上的Java程序。Servlet容器也就是Web容器,如Tomcat、Jboss、Resin、WebLogic等,它们对Servlet进行控制。当客户端发送HTTP请求,由容器加载Servlet进行处理并做出响应的流程如图2.23所示。

图2.23 Servlet处理流程

Servlet与JSP的区别如下。

1 Servlet中需要调用Servlet API接口处理HTTP请求,而在JSP页面中,则直接提供了内置对象进行处理。

2 Servlet的使用需要进行一定的配置,而JSP文件通过“.jsp”扩展名部署在容器之中,容器对其自动识别,直接编译成Servlet进行处理。

3 Servlet承担客户请求与业务处理的中间角色,需要调用固定的方法,将动态内容混合到静态之中产生HTML;而在JSP页面中,可直接使用HTML标签输出,这比Servlet更具显示层的意义。

Servlet代码结构如下。

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SimpleServlet extends HttpServlet {
//初始化方法
public void init() throws ServletException {
}
//处理HTTP Get请求
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
//处理HTTP Post请求
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
//处理HTTP Put请求
public void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
//处理HTTP Delete请求
public void doDelete(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
}
//销毁方法
public void destroy() {
super.destroy();}
}

上述代码显示了一个Servlet对象的代码结构,TestServlet类通过继承HttpServlet类被声明为一个Servlet对象。此类包含6种方法,其中init()方法与destroy()方法为Servlet初始化与生命周期结束所调用的方法,其余的4种方法为Servlet针对处理不同的HTTP请求类型所提供的方法,其作用如注释中所示。

(2)Servlet开发

我们通过IDE开发工具Eclipse开发一个使用Servlet的程序:创建一个动态Web项目,然后在包资源管理器中,新建项目名称节点上,单击鼠标右键,在弹出的快捷菜单中,选择“新建/Servlet菜单项”,将打开Create Servlet对话框,在该对话框的Java Package文本框中输入包com.mingrisoft,在Class Name文本框中输入类名FirstServlet,其他的采用默认。然后一直单击“下一步”按钮,最后单击“完成”按钮,完成Servlet的创建。创建的Servlet类的代码如下。

package com.thinkbright;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/ **
 * Servlet implementation class FirstServlet
 * /
@WebServlet("/FirstServlet")
public class FirstServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;
    
  / **
    * @see HttpServlet#HttpServlet()
    * /
  public FirstServlet() {
    super();
    // TODO Auto-generated constructor stub
  }
  / **
   * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
   * /
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, 
IOException {
    // TODO Auto-generated method stub
    response.getWriter().append("Served at: ").append(request.getContextPath());
  }
  / **
   * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
   * /
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, 
IOException {
    // TODO Auto-generated method stub
    doGet(request, response);
  }
}

要使Servlet对象正常地运行,需要进行适当的配置,以告知Web容器哪一个请求调用哪一个Servlet对象处理,对Servlet起到一个注册的作用。Servlet的配置包含在web.xml文件中,主要通过以下两步进行设置。

1 在WebContent\WEB-INF下的web.xml中,通过<servlet>标签声明一个Servlet对象。

<servlet>
 <servlet-name>FirstServlet</servlet-name>
 <servlet-class>com.thinkbright.FirstServlet</servlet-class>
</servlet>

2 映射Servlet,使用<servlet-mapping>标签以映射访问Servlet的URL。

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

在前面生产的FirstServlet.java中,重写doGet()方法如下。

  public void doGet(HttpServletRequest request, HttpServletResponse response)
     throws ServletException, IOException {
    response.setContentType("text/html");
    response.setCharacterEncoding("GBK");
    PrintWriter out = response.getWriter();
    out.println("<HTML>");
    out.println(" <HEAD><TITLE>Servlet实例</TITLE></HEAD>");
    out.println(" <BODY>");
    out.print("  Servlet实例: ");
    out.print(this.getClass());
    out.println(" </BODY>");
    out.println("</HTML>");
    out.flush();
    out.close();
    }

同时在头部引入java.io.PrintWriter:import java.io.PrintWriter。

运行服务后,打开浏览器输入http://localhost:8080/ServletProject/,浏览器显示如下内容:Servlet实例:class com.thinkbright.FirstServlet。

上例中HttpServlet类继承了GenericServlet类,通过对GenericServlet类的扩展,可以很方便地对HTTP请求进行处理及响应。HttpServlet类实现了Service()方法,并针对HTTP定义的7种类型提供了相应的方法:doGet()、doPost()、doPut()、doHead()、doTrace()、doOptions()、doDelete(),需要开发人员在使用过程中根据实际需要对其进行重写。

(3)过滤器

Servlet过滤器是客户端与目标资源间的中间层组件,用于拦截客户端的请求与响应信息,并对请求和响应信息进行处理后再将其交给其他业务进行处理,如图2.24所示。

图2.24 Servlet过滤器

过滤器主要包括三个核心接口:Filter接口、FilterChain接口、FilterConfig接口。

Filter接口的方法如表2.14所示。

表2.14 Filter接口方法

FilterChain接口只包含一个doFilter()方法,此方法主要用于将过滤器处理的请求或响应传递给下一个过滤器对象。在多个过滤器的Web应用中,可以通过此方法进行传递。

FilterConfig接口用于获取过滤器初始化期间的参数信息,其方法如表2.15所示。

表2.15 FilterConfig接口方法

在创建一个过滤器对象之后,需要对其进行配置才可以使用。过滤器的配置也是通过web. xml文件进行配置,具体步骤如下。

1 声明过滤器对象。

<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.thinkbright.util.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
</filter>

2 映射过滤器,通过<dispatcher>来指定过滤器对应的请求方式。

<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/ *</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>

下面通过一个字符编码过滤器的例子来说明过滤器的编程。

1 创建字符编码过滤器类CharacterEncodingFilter类,代码如下。

package com.thinkbright;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
/ **
 * Servlet Filter implementation class CharacterEncodingFilter
 * /
@WebFilter("/CharacterEncodingFilter")
public class CharacterEncodingFilter implements Filter {
  // 字符编码(初始化参数)
  protected String encoding = null;
  // FilterConfig对象
  protected FilterConfig filterConfig = null;
  // 初始化方法
  public void init(FilterConfig filterConfig) throws ServletException {
    // 对filterConfig赋值
   this.filterConfig = filterConfig;
   // 对初始化参数赋值
    this.encoding = filterConfig.getInitParameter("encoding");
    System.out.println("encoding="+encoding);
  }
  // 过滤器处理方法
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws 
IOException, ServletException {
    System.out.println("--doFilter() work!");
   // 判断字符编码是否有效
   if (encoding != null) {
            // 设置request字符编码
      request.setCharacterEncoding(encoding);
      // 设置response字符编码
      response.setContentType("text/html; charset="+encoding);
    }
   // 传递给下一过滤器
    chain.doFilter(request, response);
  }
  // 销毁方法
  public void destroy() {
    // 释放资源
    this.encoding = null;
    this.filterConfig = null;
  }
}

在上面代码中init()方法用于读取过滤器的初始化参数,这个参数为本例中所用到的字符编码。在doFilter()方法中分别将request对象中的编码格式设置为读取到的编码格式。

2 通过请求对过滤器信息验证。本例子中使用表单向Servlet发送中文信息进行测试,其中表单放置在index.jsp页面中,代码如下。

<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/
loose.dtd">
<html>
 <head>
  <title>字符编码过滤器测试</title>
  <style type="text/css">
   body{
            font-size: 12px;
   }
  </style>
 </head>
 
 <body>
  <form action="MyServlet" method="post">
 <p>
          请输入你的中文名字:
          <input type="text" name="name">
          <input type="submit" value="提交">
            </p>
  </form>
 </body>
</html>

这一请求由Servlet对象MyServlet类进行处理,此类使用doPost()方法来接收表单的请求,并将表单中的name属性输出到页面中,代码如下。

package com.thinkbright;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/ **
 * Servlet implementation class MyServlet
 * /
@WebServlet(“/MyServlet”)
public class MyServlet extends HttpServlet {
  private static fnal long serialVersionUID= 1L;
  public void doPost(HttpServletRequest request, HttpServletResponse response)
     throws ServletException, IOException {
   PrintWriter out = response.getWriter();
   // 获取表单参数
   String name = request.getParameter("name");
   if(name != null&& !name.isEmpty()){
    System.out.println("name="+name);
    out.print("你好 " + name);
    out.print(",<br>欢迎来到智慧城市。");
   }else{
     out.print("请输入你的中文名字!");
   }
   out.print("<br><a href=index.jsp>返回</a>");
   out.flush();
   out.close();
 }
}

3 最后,在web.xml文件中对过滤器和Servlet进行配置,代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 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">
  <!-- 声明字符编码过滤器 -->
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>com.lyq.CharacterEncodingFilter</filter-class>
    <!-- 设置初始化参数 -->
    <init-param>
      <param-name>encoding</param-name>
      <param-value>GBK</param-value>
    </init-param>
  </filter>
  <!-- 映射字符编码过滤器 -->
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <!-- 与所有请求关联 ,将/ *换成其他内容,则取消全部关联,字符编码过滤器将失效-->
    <url-pattern>/ *</url-pattern>
    <!-- 设置过滤器对应的请求方式 -->
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
  </filter-mapping>
  <!-- Servlet配置 -->
  <servlet>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>com.lyq.MyServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/MyServlet</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

2.5.5 Spring Boot

1. 概述

上面讲了这么多的Java Web技术,那么如何能够简单地生成一个Web应用并且部署服务以便让使用者可以通过浏览器访问你的Web应用服务呢?本节介绍一个当下最流行的一站式工具包:Spring Boot。

Spring框架就像一个家族,有众多衍生产品如boot、security、jpa等,但它们的基础都是Spring的ioc和aop ioc提供了依赖注入的容器AOP,解决了面向横切面的编程,然后在此基础上实现了其他延伸产品的高级功能。Spring MVC是基于Servlet的一个MVC框架,主要解决Web开发的问题,因为Spring的配置非常复杂,各种XML、JavaConfig、hin处理起来比较烦琐。于是为了方便开发者的使用,创造性地推出了Spring boot,简化了spring的配置流程。说得更简便一些,Spring最初利用“工厂模式”(DI)和“代理模式”(AOP)解耦应用组件,反映很好,于是按照这种模式做了一个MVC框架(一些用Spring解耦的组件),用于开发Web应用(SpringMVC)。然后为了简化工作流程,Spring又开发出了一些“懒人整合包”(starter),这套就是Spring Boot。

(1)Spring MVC的功能

Spring MVC提供了一种轻度耦合的方式来开发Web应用。Spring MVC是Spring的一个模块,是一个Web框架。通过Dispatcher Servlet、ModelAndView和View Resolver,开发Web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等。

(2)Spring Boot的功能

Spring Boot实现了自动配置,降低了项目搭建的复杂度。众所周知Spring框架需要进行大量的配置,Spring Boot引入自动配置的概念,让项目设置变得很容易。Spring Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,它并不是替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson、JDBC、Mongo、Redis、Mail等),Spring Boot应用中这些第三方库几乎可以零配置地开箱即用(out-of-the-box),大部分的Spring Boot应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。Spring Boot只是承载者,辅助你简化项目搭建过程。如果承载的是Web项目,使用Spring MVC作为MVC框架,那么工作流程和上面描述的是完全一样的,因为这部分工作是Spring MVC而不是Spring Boot做的。对使用者来说,换用Spring Boot以后,项目初始化方法变了,配置文件变了,另外就是不需要单独安装Tomcat这类容器服务器了,maven打出jar包直接运行起来就是个网站,但最核心的业务逻辑实现与业务流程实现没有任何变化。所以,用最简练的语言概括就是:Spring是一个“引擎”;Spring MVC是基于Spring的一个MVC框架;Spring Boot是基于Spring4的条件注册的一套快速开发整合包。Spring Boot主要有如下核心功能。

1 独立运行的Spring项目:Spring Boot可以以jar包的形式来运行,运行一个Spring Boot项目我们只需要通过java-jar xx.jar类运行,非常方便。

2 内嵌Servlet容器:Spring Boot可以内嵌Tomcat,这样我们无须以war包的形式部署项目。

3 提供starter简化Maven配置:使用Spring或者SpringMVC需要添加大量的依赖,而这些依赖很多都是固定的,这里Spring Boot通过starter能够帮助简化Maven配置。

4 自动配置Spring。

5 准生产的应用监控。

6 无代码生成和xml配置。

2. 安装和使用Spring Boot

在安装Spring Boot之前需要先安装Java SDK环境、Spring框架包和Maven。Maven是一个通过项目对象模型(POM)的一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。它包含了一个项目对象模型(Project Object Model)、一组标准集合,一个项目生命周期(Project Lifecycle)、一个依赖管理系统(Dependency Management System),以及用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。当使用Maven时,你需用一个明确定义的项目对象模型来描述你的项目,然后Maven可以应用横切的逻辑,这些逻辑来自一组共享的(或者自定义的)插件。

接下来安装Spring Boot的CLI,解压完安装包后运行bin目录下的spring.bat文件,进行配置。如果配置不成功,需要手工设置系统的系统环境变量。

JAVA_HOME=C:\Program Files\Java\jdk-9.0.4
MAVEN_HOME=C:\STUDY\apache-maven-3.5.2
SPRING_HOME=C:\STUDY\spring-2.0.1.BUILD-SNAPSHOT

同时由Path变量指向上述变量的bin目录。如果Java SDK、Maven及Spring Boot的CLI安装成功,运行下面版本测试或出现如下显示,如图2.25所示。

图2.25 Java SDK、Maven及Spring Boot CLI的版本测试

为了测试Spring Boot及CLI是否安装成功,可以编写下面代码的app.groovy文件进行测试。

@RestController
class ThisWillActuallyRun {
  @RequestMapping("/")
  String home() {
    "Hello World!"
  }
}

在命令行运行:$spring run app.groovy,等待一段时间后服务启动。

在浏览器中打开localhost:8080,你将看到如下的Hello World!,如图2.26所示。

3. 开发一个简单的Spring Boot应用

1 创建一个文件夹,SpringBootSample01。

2 新建一个pom.xml文件,内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance"

图2.26 Sprig CLI测试用例app.groovy的执行结果

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>myproject</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.0.1.BUILD-SNAPSHOT</version>
  </parent>
  <!-- Additional lines to be added here... -->
  <!-- (you don't need this if you are using a .RELEASE version) -->
  <repositories>
     <repository>
        <id>spring-snapshots</id>
        <url>https://repo.spring.io/snapshot</url>
        <snapshots><enabled>true</enabled></snapshots>
     </repository>
     <repository>
        <id>spring-milestones</id>
        <url>https://repo.spring.io/milestone</url>
     </repository>
  </repositories>
  <pluginRepositories>
     <pluginRepository>
        <id>spring-snapshots</id>
        <url>https://repo.spring.io/snapshot</url>
     </pluginRepository>
     <pluginRepository>
        <id>spring-milestones</id>
        <url>https://repo.spring.io/milestone</url>
     </pluginRepository>
  </pluginRepositories>
</project>

3 上面没有添加任何依赖,但仍然可以build。命令行:mvn package。注意,当前路径为//SpringBootSample01/。

4 现在需要添加依赖,其实就是把依赖的jar添加到buildpath。由于已经继承了spring-boot-starter-parent,而spring-boot-starter-parent又提供了dependency-management,所以我们可以忽略被选中依赖的版本。在添加依赖之前,先看一下现在已有什么:mvn dependency:tree。该命令会打印一个当前项目的依赖树。结果表明,当前没有任何依赖。因此,现在添加一个Starter模块。为了增加必需的依赖,编辑pom.xml并增加spring-boot-starter-web依赖在parent块的下面。

<dependencies>
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

5 现在就可以开始写代码了。由于Maven默认编译路径为src/main/java下面的源码,所以,默认设置下,需要创建这些文件夹。然后,编写文件src/main/java/Example.java。

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.web.bind.annotation.*;
@RestController
@EnableAutoConfiguration
public class Example {
  @RequestMapping("/")
  String home() {
     return "Hello Smart City!";
  }
  public static void main(String[] args) throws Exception {
     SpringApplication.run(Example.class, args);
  }
}

• @RestController and@RequestMapping注解。

上面例子类中的第一个注解是@RestController。它对阅读人提供了提示并对Spring提供了类的特定职责。本例中,类是一个Web应用的控制器(@Controller),因此,Spring认为它应处理进入的Web请求。

@RequestMapping注解提供了“路由”信息。它告诉Spring任何具备path路径的HTTP请求应该映射到本地的方法。@RestController注解告诉Spring将结果字符串直接返回给调用者,这两个都是Spring MVC注解。

• @EnableAutoConfiguration注解。

第二个类级注解是@EnableAutoConfiguration。该注解基于增加的jar依赖告诉Spring Boot去“猜猜”你想如何去配置Spring。由于spring-boot-starter-web增加了Tomcat和Spring MVC,auto-configuration假定你正在开发一个Web应用并相应的设置Spring。

Auto-configuration分配给“Starters”工作,但是两个概念部署直接关联。你可以在starters之外自由选择jar依赖。Spring Boot将竭尽全能自动配置你的应用。

• main方法。

应用的最后一部分是main方法。这是一个标准的方法遵循应用入口点的Java机制。main方法通过调用run被Spring Boot的SpringApplication类选中。SpringApplication启动应用程序,开始Spring;Spring随后启动自动配置的Tomcat Web服务器。

6 启动项目。由于使用了spring-boot-starter-parent POM,所以我们可以使用mvn spring-boot:run来启动项目(根路径)。项目启动之后就可以访问了,默认地址:http://localhost:8080/。运行成功后显示如图2.27所示。

图2.27 启动测试用例

7 打包。executable jars又称fat jars,是可以直接在生产环境中运行的,包含所有编译生成的class文件以及依赖包。注意,Spring Boot的这种打包方式需要使用Spring Boot提供的spring-boot-maven-plugin。我们需要将spring-boot-maven-plugin增加到pom. xml中,为此,插入下列行在dependencies块下。

<build>
  <plugins>
     <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
     </plugin>
  </plugins>
</build>

注意,spring-boot-starter-parent POM中包含了<executions>的配置信息,绑定了repackage goal(maven)。如果不使用parent POM,你需要自己来声明这个配置信息。现在,可以打包了:mvn package。

打包成功后,你可以在/target目录下看到myproject-0.0.1-SNAPSHOT.jar,大约10 MB,可以通过jar tvf target/myproject-0.0.1-SNAPSHOT.jar来查看其中的内容。此外,在/target目录下,还可以看到myproject-0.0.1-SNAPSHOT.jar.original,这是Maven打包出来的,在Spring Boot repackage之前。

8 执行。正常的jar执行:java-jar target/myproject-0.0.1-SNAPSHOT.jar,服务运行。

2.5.6 Node.js Express框架

(1)概述

Express是一个简洁而灵活的node.js Web应用框架,提供了一系列强大特性帮助你创建各种Web应用和丰富的HTTP工具。使用Express可以快速地搭建一个完整功能的网站。Express框架核心特性如下。

1 可以设置中间件来响应HTTP请求。

2 定义了路由表用于执行不同的HTTP请求动作。

3 可以通过向模板传递参数来动态渲染HTML页面。

(2)安装

首先假定你已经安装了Node.js,接下来为你的应用创建一个目录,然后进入此目录并将其作为当前工作目录。

$ mkdir myapp
$ cd myapp

通过npm init命令为你的应用创建一个package.json文件。

$ npm init

此命令将要求你输入几个参数,例如此应用的名称和版本。你也可以直接按“回车”键接受默认设置即可,下面这个除外。

entry point: (index.js)

键入app.js或者你所希望的名称,这是当前应用的入口文件。如果你希望采用默认的index.js文件名,只需按“回车”键。

接下来安装Express并将其保存到依赖列表中。

$ npm install express --save

(3)Hello world实例

接下来,我们一起创建一个基本的Express应用。注意:这里所创建是一个最简单的Express应用,并且仅仅只有一个文件——与通过Express应用生成器所创建的应用完全不一样,Express应用生成器所创建的应用框架包含多个JavaScript文件、Jade模板和针对不同用途的子目录。

进入myapp目录,创建一个名为app.js的文件,然后将下列代码复制进去。

var express = require('express');
var app = express();
app.get('/', function (req, res) {
 res.send('Hello World!');
});
var server = app.listen(3000, function () {
 var host = server.address().address;
 var port = server.address().port;
 console.log('Example app listening at http://%s:%s', host, port);
});

上面的代码启动一个服务并监听从3000端口进入的所有连接请求。它将对所有(/) URL或路由返回“Hello Smart World!”字符串。对于其他所有路径全部返回“404 Not Found”。req(请求)和res(响应)与Node提供的对象完全一致,因此,你可以调用req.pipe()、req. on('data', callback)以及任何Node提供的方法。

通过如下命令启动此应用。

$ node app.js

然后在浏览器中打开http://localhost:3000/并查看输出结果。

Express应用使用回调函数的参数:request和response对象来处理请求和响应的数据。

  app.get('/', function (req, res) {
    // --
  })

request和response对象的具体介绍如下。

reques对象表示HTTP请求,包含了请求查询字符串、参数、内容、HTTP头部等属性。常见属性如下。

1 req.app:当callback为外部文件时,用req.app访问express的实例。

2 req.baseUrl:获取路由当前安装的URL路径。

3 req.body/req.cookies:获得请求主体/cookies。

4 req.fresh/req.stale:判断请求是否是最新的。

5 req.hostname/req.ip:获取主机名和IP地址。

6 req.originalUrl:获取原始请求URL。

7 req.params:获取路由的parameters。

8 req.path:获取请求路径。

9 req.protocol:获取协议类型。

10 req.query:获取URL的查询参数串。

11 req.route:获取当前匹配的路由。

12 req.subdomains:获取子域名。

13 req.accepts():检查可接收的请求的文档类型。

14 req.acceptsCharsets/req.acceptsEncodings/req.acceptsLanguages:返回指定字符集的第一个可接收字符编码。

15 req.get():获取指定的HTTP请求头。

16 req.is():判断请求头Content-Type的MIME类型。

response对象表示HTTP响应,即在接收到请求时向客户端发送的HTTP响应数据。常见属性如下。

1 res.app:同req.app一样。

2 res.append():追加指定HTTP头。

3 res.set()在res.append()后将重置之前设置的头。

4 res.cookie(name,value [,option]):设置Cookie。opition:domain/expires/httpOnly/maxAge/path/secure/signed。

5 res.clearCookie():清除Cookie。

6 res.download():传送指定路径的文件。

7 res.get():返回指定的HTTP头。

8 res.json():传送JSON响应。

9 res.jsonp():传送JSONP响应。

10 res.location():只设置响应的Location HTTP头,不设置状态码或者close response。

11 res.redirect():设置响应的Location HTTP头,并且设置状态码302。

12 res.render(view,[locals],callback):渲染一个view,同时向callback传递渲染后的字符串,如果在渲染过程中有错误发生next(err)将会被自动调用。callback将会被传入一个可能发生的错误以及渲染后的页面,这样就不会自动输出了。

13 res.send():传送HTTP响应。

14 res.sendFile(path [,options] [,fn]):传送指定路径的文件,自动根据文件extension设定Content-Type。

15 res.set():设置HTTP头,传入object可以一次设置多个头。

16 res.status():设置HTTP状态码。

17 res.type():设置Content-Type的MIME类型。

(4)Express应用生成器

通过应用生成器工具express可以快速创建一个应用的骨架。通过如下命令安装。

$ npm install express-generator -g

-h选项可以列出所有可用的命令行选项。

$ express -h
 Usage: express [options] [dir]
 Options:
  -h, --help     output usage information
  -V, --version    output the version number
  -e, --ejs      add ejs engine support (defaults to jade)
    --hbs      add handlebars engine support
  -H, --hogan     add hogan.js engine support
  -c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
    --git      add .gitignore
  -f, --force     force on non-empty directory

例如,下面的示例就是在当前工作目录下创建一个命名为myApp的应用。

$ express myapp
  create : myapp
  create : myapp/package.json
  create : myapp/app.js
  create : myapp/public
  create : myapp/public/javascripts
  create : myapp/public/images
  create : myapp/routes
  create : myapp/routes/index.js
  create : myapp/routes/users.js
  create : myapp/public/stylesheets
  create : myapp/public/stylesheets/style.css
  create : myapp/views
  create : myapp/views/index.jade
  create : myapp/views/layout.jade
  create : myapp/views/error.jade
  create : myapp/bin
  create : myapp/bin/www

然后,安装所有依赖包。

$ cd myapp 
$ npm install

启动这个应用(MacOS或Linux平台)。

$ DEBUG=myapp npm start

Windows平台使用如下命令。

> set DEBUG=myapp & npm start

然后,在浏览器中打开http://localhost:3000/网址就可以看到这个应用了。

通过Express应用生成器创建的应用一般都有如下目录结构。

├──app.js
├──bin
│  └──www
├──package.json
├──public
│  ├──images
│  ├──javascripts
│  └──stylesheets
│    └──style.css
├──routes
│  ├──index.js
│  └──users.js
└──views
  ├──error.jade
  ├──index.jade
  └──layout.jade
7 directories, 9 files

通过Express应用生长期创建应用只是众多方法中的一种。你可以不使用它,也可以修改它让它符合你的需求。

(5)路由

我们已经了解了HTTP请求的基本应用,而路由决定了由谁(指定脚本)去响应客户端请求。在HTTP请求中,我们可以通过路由提取请求的URL以及GET/POST参数。接下来我们扩展“Hello World”,添加一些功能来处理更多类型的HTTP请求。创建express_demo2.js文件,代码如下所示。

var express = require('express');
var app = express();
// 主页输出 "Hello World"
app.get('/', function (req, res) {
  console.log(" GET req");
  res.send('Hello GET');
})
// POST请求
app.post('/', function (req, res) {
  console.log("POST res");
  res.send('Hello POST');
})
// /del_user页面响应
app.get('/del_user', function (req, res) {
  console.log("/del_user response DELETE req");
  res.send('delete page');
})
// /list_user页面GET请求
app.get('/list_user', function (req, res) {
  console.log("/list_user GET req");
  res.send('user list page');
})
// 对页面abcd, abxcd, ab123cd, 等响应GET请求
app.get('/ab*cd', function(req, res) {  
  console.log("/ab*cd GET req");
  res.send('regular expressing match');
})
var server = app.listen(8081, function () {
 var host = server.address().address
 var port = server.address().port
 console.log("应用实例,访问地址为http://%s:%s", host, port)
})

执行以上代码。

$ node express_demo2.js

应用实例,访问地址为http://0.0.0.0:8081。接下来你可以尝试访问http://127.0. 0.1:8081不同的地址,查看效果。

(6)静态文件

Express提供了内置的中间件express.static来设置静态文件,如图片、CSS、JavaScript等。你可以使用express.static中间件来设置静态文件路径。例如,如果将图片、CSS、JavaScript文件放在public目录下,你可以这么写。

app.use(express.static('public'));

我们可以到public/images目录下放些图片,如下所示。

public/images/logo.png

让我们再修改下"Hello World"应用添加处理静态文件的功能。

创建express_demo3.js文件,代码如下所示。

var express = require('express');
var app = express();
app.use(express.static('public'));
app.get('/', function (req, res) {
  res.send('Hello World');
})
var server = app.listen(8081, function () {
 var host = server.address().address
 var port = server.address().port
 console.log("应用实例,访问地址为http://%s:%s", host, port)
})

执行以上代码。

$ node express_demo3.js

在浏览器中访问http://127.0.0.1:8081/images/logo.png,将显示Logo图标。

(7)GET和PUT例子

以下实例演示了在表单中通过GET方法提交两个参数,我们可以使用server.js文件内的process_get路由器来处理输入。

index.htm文件代码如下。

<html>
<body>
<form action="http://127.0.0.1:8081/process_get" method="GET">
First Name: <input type="text" name="first_name"> <br>
Last Name: <input type="text" name="last_name">
<input type="submit" value="Submit">
</form>
</body>
</html>

server.js文件代码如下。

var express = require('express');
var app = express();
app.use(express.static('public'));
app.get('/index.htm', function (req, res) {
  res.sendFile( __dirname + "/" + "index.htm" );
})
app.get('/process_get', function (req, res) {
  // 输出JSON格式
  var response = {
    "first_name":req.query.first_name,
    "last_name":req.query.last_name
  };
  console.log(response);
  res.end(JSON.stringify(response));
})
var server = app.listen(8081, function () {
 
 var host = server.address().address
 var port = server.address().port
 console.log("应用实例,访问地址为http://%s:%s", host, port)
})

执行以上代码。

  node server.js

浏览器访问http://127.0.0.1:8081/index.htm,如图2.28所示。

图2.28 浏览器访问截图

在两个输入框输入Qiang和Lei后,浏览器显示:{"first_name":"Qiang","last_name":"Lei"}。

下面实例演示了在表单中通过POST方法提交两个参数,我们可以使用server.js文件内的process_post路由器来处理输入。

index.htm文件代码如下。

  <html>
  <body>
  <form action="http://127.0.0.1:8081/process_post" method="POST">
  First Name: <input type="text" name="first_name"> <br>
  Last Name: <input type="text" name="last_name">
  <input type="submit" value="Submit">
  </form>
  </body>
  </html>

server.js文件代码如下。

  var express = require('express');
  var app = express();
  var bodyParser = require('body-parser');
  // 创建application/x-www-form-urlencoded编码解析
  var urlencodedParser = bodyParser.urlencoded({ extended: false })
  app.use(express.static('public'));
  app.get('/index.htm', function (req, res) {
    res.sendFile( __dirname + "/" + "index.htm" );
  })
  app.post('/process_post', urlencodedParser, function (req, res) {
    // 输出JSON格式
    var response = {
      "first_name":req.body.first_name,
      "last_name":req.body.last_name
    };
    console.log(response);
    res.end(JSON.stringify(response));
  })
  var server = app.listen(8081, function () {
   var host = server.address().address
   var port = server.address().port
   console.log("应用实例,访问地址为http://%s:%s", host, port)
  })

执行以上代码。

  $ node server.js

浏览器访问http://127.0.0.1:8081/index.htm,与图2.41一样。现在你可以向表单输入数据,并采用POST提交,浏览器显示:{"first_name":"Qiang","last_name":"Lei"}。

(8)文件上传

下面我们创建一个用于上传文件的表单。使用POST方法,表单enctype属性设置为multipart/form-data。编程前需要安装两个重要的模块:body-parser-node.js中间件,用于处理JSON、Raw、Text和URL编码的数据。multer-node.js中间件,用于处理enctype="multipart/form-data"(设置表单的MIME编码)的表单数据。

安装命令如下。

$ npm install body-parser --save
$ npm install multer --save

index.htm文件代码如下。

<html>
<head>
<title>文件上传表单</title>
</head>
<body>
<h3>文件上传:
选择一个文件上传: <br />
<form action="/file_upload" method="post" enctype="multipart/form-data">
<input type="file" name="image" size="50" />
<br />
<input type="submit" value="上传文件" />
</form>
</body>
</html>

server.js文件代码如下。

var express = require('express');
var app = express();
var fs = require("fs");
var bodyParser = require('body-parser');
var multer = require('multer');
app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(multer({ dest: '/tmp/'}).array('image'));
app.get('/index.htm', function (req, res) {
  res.sendFile( __dirname + "/" + "index.htm" );
})
app.post('/file_upload', function (req, res) {
  console.log(req.files[0]); // 上传的文件信息
  var des_file = __dirname + "/" + req.files[0].originalname;
  fs.readFile( req.files[0].path, function (err, data) {
    fs.writeFile(des_file, data, function (err) {
     if( err ){
       console.log( err );
     }else{
        response = {
          message:'File uploaded successfully', 
          filename:req.files[0].originalname
       };
     }
     console.log( response );
     res.end( JSON.stringify( response ) );
    });
  });
})
var server = app.listen(8081, function () {
 var host = server.address().address
 var port = server.address().port
 console.log("应用实例,访问地址为http://%s:%s", host, port)
})

执行以上代码。

$ node server.js

浏览器访问http://127.0.0.1:8081/index.htm,如图2.29所示。

图2.29 浏览器访问截图

现在你可以向表单输入数据,并提交,浏览器显示:{"message":"File uploaded succes sfully","filename":"package.json"},表示名为package.json的文件上传成功。

(9)cookie管理

首先,安装cookie中间件模块。

$npm install cookie-parser --save

安装成功后,可以使用中间件向Node.js服务器发送cookie信息,以下代码输出了客户端发送的cookie信息。

express_cookie.js文件代码如下。

// express_cookie.js文件
var express   = require('express')
var cookieParser = require('cookie-parser')
var app = express()
app.use(cookieParser())
app.get('/', function(req, res) {
 console.log("Cookies: ", req.cookies)
})
app.listen(8081)

执行以上代码。

$ node express_cookie.js

现在你可以访问http://127.0.0.1:8081并查看终端信息的输出本机cookie信息。