阐述 Servlet 和 CGI 的区别?

答:Servlet 与 CGI 的区别在于 Servlet 处于服务器进程中,它通过多线程方式运行其 service() 方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而 CGI 对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于 Servlet。

补充:Sun Microsystems 公司在 1996 年发布 Servlet 技术就是为了和 CGI 进行竞争,Servlet 是一个特殊的 Java 程序,一个基于 Java 的 Web 应用通常包含一个或多个 Servlet 类。Servlet 不能够自行创建并执行,它是在 Servlet 容器中运行的,容器将用户的请求传递给 Servlet 程序,并将 Servlet 的响应回传给用户。通常一个 Servlet 会关联一个或多个 JSP 页面。以前 CGI 经常因为性能开销上的问题被诟病,然而 Fast CGI 早就已经解决了 CGI 效率上的问题,所以面试的时候大可不必信口开河的诟病 CGI,事实上有很多你熟悉的网站都使用了 CGI 技术。

Servlet 接口中有哪些方法?

答:Servlet 接口定义了 5 个方法,其中前三个方法与 Servlet 生命周期相关:

  • void init(ServletConfig config) throws ServletException
  • void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException
  • void destroy()
  • java.lang.String getServletInfo()
  • ServletConfig getServletConfig()

Web 容器加载 Servlet 并将其实例化后,Servlet 生命周期开始,容器运行其 init() 方法进行 Servlet 的初始化;请求到达时调用 Servlet 的 service() 方法,service() 方法会根据需要调用与请求对应的 doGetdoPost 等方法;当服务器关闭或项目被卸载时服务器会将 Servlet 实例销毁,此时会调用 Servlet 的 destroy() 方法。

转发(forward)和重定向(redirect)的区别?

答:forward 是容器中控制权的转向,是服务器请求资源,服务器直接访问目标地址的 URL,把那个 URL 的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。redirect 就是服务器端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,因此从浏览器的地址栏中可以看到跳转后的链接地址,很明显 redirect 无法访问到服务器保护起来资源,但是可以从一个网站 redirect 到其他网站。forward 更加高效,所以在满足需要时尽量使用 forward(通过调用 RequestDispatcher 对象的 forward() 方法,该对象可以通过 ServletRequest 对象的 getRequestDispatcher() 方法获得),并且这样也有助于隐藏实际的链接;在有些情况下,比如需要访问一个其它服务器上的资源,则必须使用重定向(通过 HttpServletResponse 对象调用其 sendRedirect() 方法实现)。

JSP 有哪些内置对象?作用分别是什么?

答:JSP 有 9 个内置对象:

  • request:封装客户端的请求,其中包含来自 GET 或 POST 请求的参数;
  • response:封装服务器对客户端的响应;
  • pageContext:通过该对象可以获取其他对象;
  • session:封装用户会话的对象;
  • application:封装服务器运行环境的对象;
  • out:输出服务器响应的输出流对象;
  • config:Web 应用的配置对象;
  • page:JSP 页面本身(相当于 Java 程序中的 this);
  • exception:封装页面抛出异常的对象。

补充:如果用 Servlet 来生成网页中的动态内容无疑是非常繁琐的工作,另一方面,所有的文本和 HTML 标签都是硬编码,即使做出微小的修改,都需要进行重新编译。JSP 解决了 Servlet 的这些问题,它是 Servlet 很好的补充,可以专门用作为用户呈现视图(View),而 Servlet 作为控制器(Controller)专门负责处理用户请求并转发或重定向到某个页面。基于 Java 的 Web 开发很多都同时使用了 Servlet 和 JSP。JSP 页面其实是一个 Servlet,能够运行 Servlet 的服务器(Servlet 容器)通常也是 JSP 容器,可以提供 JSP 页面的运行环境,Tomcat 就是一个 Servlet/JSP 容器。第一次请求一个 JSP 页面时,Servlet/JSP 容器首先将 JSP 页面转换成一个 JSP 页面的实现类,这是一个实现了 JspPage 接口或其子接口 HttpJspPage 的 Java 类。JspPage 接口是 Servlet 的子接口,因此每个 JSP 页面都是一个 Servlet。转换成功后,容器会编译 Servlet 类,之后容器加载和实例化 Java 字节码,并执行它通常对 Servlet 所做的生命周期操作。对同一个 JSP 页面的后续请求,容器会查看这个 JSP 页面是否被修改过,如果修改过就会重新转换并重新编译并执行。如果没有则执行内存中已经存在的 Servlet 实例。我们可以看一段 JSP 代码对应的 Java 程序就知道一切了,而且 9 个内置对象的神秘面纱也会被揭开。

JSP 页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>

<!DOCTYPE html>
<html>
<head>
<base href="<%=basePath%>">
<title>首页</title>
<style type="text/css">
* { font-family: "Arial"; }
</style>
</head>

<body>
<h1>Hello, World!</h1>
<hr/>
<h2>Current time is: <%= new java.util.Date().toString() %></h2>
</body>
</html>

对应的 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/*
* Generated by the Jasper component of Apache Tomcat
* Version: Apache Tomcat/7.0.52
* Generated at: 2014-10-13 13:28:38 UTC
* Note: The last modified time of this file was set to
* the last modified time of the source file after
* generation to assist with modification tracking.
*/
package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {

private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory
.getDefaultFactory();

private static java.util.Map<java.lang.String, java.lang.Long> _jspx_dependants;

private javax.el.ExpressionFactory _el_expressionfactory;
private org.apache.tomcat.InstanceManager _jsp_instancemanager;

public java.util.Map<java.lang.String, java.lang.Long> getDependants() {
return _jspx_dependants;
}

public void _jspInit() {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(
getServletConfig().getServletContext()).getExpressionFactory();
_jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory
.getInstanceManager(getServletConfig());
}

public void _jspDestroy() {
}

public void _jspService(
final javax.servlet.http.HttpServletRequest request,
final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
// 内置对象就是在这里定义的
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;

try {
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;

out.write('\r');
out.write('\n');

String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";

// 以下代码通过输出流将 HTML 标签输出到浏览器中
out.write("\r\n");
out.write("\r\n");
out.write("<!DOCTYPE html>\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <base href=\"");
out.print(basePath);
out.write("\">\r\n");
out.write(" <title>首页</title>\r\n");
out.write(" <style type=\"text/css\">\r\n");
out.write(" \t* { font-family: \"Arial\"; }\r\n");
out.write(" </style>\r\n");
out.write(" </head>\r\n");
out.write(" \r\n");
out.write(" <body>\r\n");
out.write(" <h1>Hello, World!</h1>\r\n");
out.write(" <hr/>\r\n");
out.write(" <h2>Current time is: ");
out.print(new java.util.Date().toString());
out.write("</h2>\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)) {
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try {
out.clearBuffer();
} catch (java.io.IOException e) {
}
if (_jspx_page_context != null)
_jspx_page_context.handlePageException(t);
else
throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}

get 和 post 请求的区别?

答:

  1. get 请求用来从服务器上获得资源,而 post 是用来向服务器提交数据;
  2. get 将表单中数据按照 name=value 的形式,添加到 action 所指向的 URL 后面,并且两者使用 ? 连接,而各个变量之间使用 & 连接;post 是将表单中的数据放在 HTTP 协议的请求头或消息体中,传递到 action 所指向 URL;
  3. get 传输的数据要受到 URL 长度限制(1024 字节);而 post 可以传输大量的数据,上传文件通常要使用 post 方式;
  4. 使用 get 时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用 get;对于敏感数据还是应用使用 post;
  5. get 使用 MIME 类型 application/x-www-form-urlencoded 的 URL 编码(也叫百分号编码)文本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是 %20

常用的 Web 服务器有哪些?

答:Unix 和 Linux 平台下使用最广泛的免费 HTTP 服务器是 Apache 服务器,而 Windows 平台的服务器通常使用 IIS 作为 Web 服务器。选择 Web 服务器应考虑的因素有:性能、安全性、日志和统计、虚拟主机、代理服务器、缓冲服务和集成应用程序等。下面是对常见服务器的简介:

  • IIS:Microsoft 的 Web 服务器产品,全称是 Internet Information Services。IIS 是允许在公共 Intranet 或 Internet 上发布信息的 Web 服务器。IIS 是目前最流行的 Web 服务器产品之一,很多著名的网站都是建立在 IIS 的平台上。IIS 提供了一个图形界面的管理工具,称为 Internet 服务管理器,可用于监视配置和控制 Internet 服务。IIS 是一种 Web 服务组件,其中包括 Web 服务器、FTP 服务器、NNTP 服务器和 SMTP 服务器,分别用于网页浏览、文件传输、新闻服务和邮件发送等方面,它使得在网络(包括互联网和局域网)上发布信息成了一件很容易的事。它提供 ISAPI(Intranet Server API)作为扩展 Web 服务器功能的编程接口;同时,它还提供一个 Internet 数据库连接器,可以实现对数据库的查询和更新。

  • Kangle:Kangle Web 服务器是一款跨平台、功能强大、安全稳定、易操作的高性能 Web 服务器和反向代理服务器软件。此外,Kangle 也是一款专为做虚拟主机研发的 Web 服务器。实现虚拟主机独立进程、独立身份运行。用户之间安全隔离,一个用户出问题不影响其他用户。支持 PHP、ASP、ASP.NET、Java、Ruby 等多种动态开发语言。

  • WebSphere:WebSphere Application Server 是功能完善、开放的 Web 应用程序服务器,是 IBM 电子商务计划的核心部分,它是基于 Java 的应用环境,用于建立、部署和管理 Internet 和 Intranet Web 应用程序,适应各种 Web 应用程序服务器的需要。

  • WebLogic:WebLogic Server 是一款多功能、基于标准的 Web 应用服务器,为企业构建企业应用提供了坚实的基础。针对各种应用开发、关键性任务的部署,各种系统和数据库的集成、跨 Internet 协作等 Weblogic 都提供了相应的支持。由于它具有全面的功能、对开放标准的遵从性、多层架构、支持基于组件的开发等优势,很多公司的企业级应用都选择它来作为开发和部署的环境。WebLogic Server 在使应用服务器成为企业应用架构的基础方面一直处于领先地位,为构建集成化的企业级应用提供了稳固的基础。

  • Apache:目前 Apache 仍然是世界上用得最多的 Web 服务器,其市场占有率很长时间都保持在 60% 以上(目前的市场份额约 40% 左右)。世界上很多著名的网站都是 Apache 的产物,它的成功之处主要在于它的源代码开放、有一支强大的开发团队、支持跨平台的应用(可以运行在几乎所有的 Unix、Windows、Linux 系统平台上)以及它的可移植性等方面。

  • Tomcat:Tomcat 是一个开放源代码、运行 Servlet 和 JSP 的容器。Tomcat 实现了 Servlet 和 JSP 规范。此外,Tomcat 还实现了 Apache-Jakarta 规范而且比绝大多数商业应用软件服务器要好,因此目前也有不少的 Web 服务器都选择了 Tomcat。

  • Nginx:读作"engine x",是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。其将源代码以类 BSD 许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。在 2014 年下半年,Nginx 的市场份额达到了 14%。

JSP 和 Servlet 是什么关系?

答:其实这个问题在上面已经阐述过了,Servlet 是一个特殊的 Java 程序,它运行于服务器的 JVM 中,能够依靠服务器的支持向浏览器提供显示内容。JSP 本质上是 Servlet 的一种简易形式,JSP 会被服务器处理成一个类似于 Servlet 的 Java 程序,可以简化页面内容的生成。Servlet 和 JSP 最主要的不同点在于,Servlet 的应用逻辑是在 Java 文件中,并且完全从表示层中的 HTML 分离开来。而 JSP 的情况是 Java 和 HTML 可以组合成一个扩展名为 .jsp 的文件。有人说,Servlet 就是在 Java 中写 HTML,而 JSP 就是在 HTML 中写 Java 代码,当然这个说法是很片面且不够准确的。JSP 侧重于视图,Servlet 更侧重于控制逻辑,在 MVC 架构模式中,JSP 适合充当视图(view)而 Servlet 适合充当控制器(controller)。

讲解 JSP 中的四种作用域。

答:JSP 中的四种作用域包括 pagerequestsessionapplication,具体来说:

  • page 代表与一个页面相关的对象和属性。
  • request 代表与 Web 客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件;需要在页面显示的临时数据可以置于此作用域。
  • session 代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的 session 中。
  • application 代表与整个 Web 应用程序相关的对象和属性,它实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域。

如何实现 JSP 或 Servlet 的单线程模式?

答:

对于 JSP 页面,可以通过 page 指令进行设置。

1
<%@page isThreadSafe=”false”%>

对于 Servlet,可以让自定义的 Servlet 实现 SingleThreadModel 标识接口。

说明:如果将 JSP 或 Servlet 设置成单线程工作模式,会导致每个请求创建一个 Servlet 实例,这种实践将导致严重的性能问题(服务器的内存压力很大,还会导致频繁的垃圾回收),所以通常情况下并不会这么做。

实现会话跟踪的技术有哪些?

答:由于 HTTP 协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记,为用户分配唯一的 ID,下一次用户在请求中包含此 ID,服务器据此判断到底是哪一个用户。

  1. URL 重写:在 URL 中添加用户会话的信息作为请求的参数,或者将唯一的会话 ID 添加到 URL 结尾以标识一个会话。
  2. 设置表单隐藏域:将和会话跟踪相关的字段添加到隐式表单域中,这些信息不会在浏览器中显示但是提交表单时会提交给服务器。 这两种方式很难处理跨越多个页面的信息传递,因为如果每次都要修改 URL 或在页面中添加隐式表单域来存储用户会话相关信息,事情将变得非常麻烦。
  3. cookie:cookie 有两种,一种是基于窗口的,浏览器窗口关闭后,cookie 就没有了;另一种是将信息存储在一个临时文件中,并设置存在的时间。当用户通过浏览器和服务器建立一次会话后,会话 ID 就会随响应信息返回存储在基于窗口的 cookie 中,那就意味着只要浏览器没有关闭,会话没有超时,下一次请求时这个会话 ID 又会提交给服务器让服务器识别用户身份。会话中可以为用户保存信息。会话对象是在服务器内存中的,而基于窗口的 cookie 是在客户端内存中的。如果浏览器禁用了 cookie,那么就需要通过下面两种方式进行会话跟踪。当然,在使用 cookie 时要注意几点:首先不要在 cookie 中存放敏感信息;其次 cookie 存储的数据量有限(4k),不能将过多的内容存储 cookie 中;再者浏览器通常只允许一个站点最多存放 20 个 cookie。当然,和用户会话相关的其他信息(除了会话 ID)也可以存在 cookie 方便进行会话跟踪。
  4. HttpSession:在所有会话跟踪技术中,HttpSession 对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession,每个用户可以访问他自己的 HttpSession。可以通过 HttpServletRequest 对象的 getSession 方法获得 HttpSession,通过 HttpSession 的 setAttribute 方法可以将一个值放在 HttpSession 中,通过调用 HttpSession 对象的 getAttribute 方法,同时传入属性名就可以获取保存在 HttpSession 中的对象。与上面三种方式不同的是,HttpSession 放在服务器的内存中,因此不要将过大的对象放在里面,即使目前的 Servlet 容器可以在内存将满时将 HttpSession 中的对象移到其他存储设备中,但是这样势必影响性能。添加到 HttpSession 中的值可以是任意 Java 对象,这个对象最好实现了 Serializable 接口,这样 Servlet 容器在必要的时候可以将其序列化到文件中,否则在序列化时就会出现异常。

补充:HTML5 中可以使用 Web Storage 技术通过 JavaScript 来保存数据,例如可以使用 localStoragesessionStorage 来保存用户会话的信息,也能够实现会话跟踪。

过滤器有哪些作用和用法?

答:Java Web 开发中的过滤器(filter)是从 Servlet 2.3 规范开始增加的功能,并在 Servlet 2.4 规范中得到增强。对 Web 应用来说,过滤器是一个驻留在服务器端的 Web 组件,它可以截取客户端和服务器之间的请求与响应信息,并对这些信息进行过滤。当 Web 容器接受到一个对资源的请求时,它将判断是否有过滤器与这个资源相关联。如果有,那么容器将把请求交给过滤器进行处理。在过滤器中,你可以改变请求的内容,或者重新设置请求的报头信息,然后再将请求发送给目标资源。当目标资源对请求作出响应时候,容器同样会将响应先转发给过滤器,在过滤器中你可以对响应的内容进行转换,然后再将响应发送到客户端。

常见的过滤器用途主要包括:对用户请求进行统一认证、对用户的访问请求进行记录和审核、对用户发送的数据进行过滤或替换、转换图象格式、对响应内容进行压缩以减少传输量、对请求或响应进行加解密处理、触发资源访问事件、对 XML 的输出应用 XSLT 等。

和过滤器相关的接口主要有:FilterFilterConfigFilterChain

编码过滤器的例子:

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
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;
import javax.servlet.annotation.WebInitParam;

@WebFilter(urlPatterns = { "*" }, initParams = {@WebInitParam(name="encoding", value="utf-8")})
public class CodingFilter implements Filter {
private String defaultEncoding = "utf-8";

@Override
public void destroy() {
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
req.setCharacterEncoding(defaultEncoding);
resp.setCharacterEncoding(defaultEncoding);
chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {
String encoding = config.getInitParameter("encoding");
if (encoding != null) {
defaultEncoding = encoding;
}
}
}

下载计数过滤器的例子:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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;
import javax.servlet.http.HttpServletRequest;

@WebFilter(urlPatterns = {"/*"})
public class DownloadCounterFilter implements Filter {

private ExecutorService executorService = Executors.newSingleThreadExecutor();
private Properties downloadLog;
private File logFile;

@Override
public void destroy() {
executorService.shutdown();
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
final String uri = request.getRequestURI();
executorService.execute(new Runnable() {

@Override
public void run() {
String value = downloadLog.getProperty(uri);
if(value == null) {
downloadLog.setProperty(uri, "1");
}
else {
int count = Integer.parseInt(value);
downloadLog.setProperty(uri, String.valueOf(++count));
}
try {
downloadLog.store(new FileWriter(logFile), "");
}
catch (IOException e) {
e.printStackTrace();
}
}
});
chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {
String appPath = config.getServletContext().getRealPath("/");
logFile = new File(appPath, "downloadLog.txt");
if(!logFile.exists()) {
try {
logFile.createNewFile();
}
catch(IOException e) {
e.printStackTrace();
}
}
downloadLog = new Properties();
try {
downloadLog.load(new FileReader(logFile));
} catch (IOException e) {
e.printStackTrace();
}
}

}

说明:这里使用了 Servlet 3 规范中的注解来部署过滤器,当然也可以在 web.xml 中使用 <filter><filter-mapping> 标签部署过滤器,如 108 题中所示。

监听器有哪些作用和用法?

答:Java Web 开发中的监听器(listener)就是 applicationsessionrequest 三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件,如下所示:

  1. ServletContextListener:对 Servlet 上下文的创建和销毁进行监听。
  2. ServletContextAttributeListener:监听 Servlet 上下文属性的添加、删除和替换。
  3. HttpSessionListener:对 Session 的创建和销毁进行监听。
  4. HttpSessionAttributeListener:对 Session 对象中属性的添加、删除和替换进行监听。
  5. ServletRequestListener:对请求对象的初始化和销毁进行监听。
  6. ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。

补充:session 的销毁有两种情况:(1)session 超时(可以在 web.xml 中通过 <session-config>/<session-timeout> 标签配置超时时间);(2)通过调用 session 对象的 invalidate() 方法使 session 失效。

下面是一个统计网站最多在线人数监听器的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
上下文监听器,在服务器启动时初始化 onLineCount 和 maxOnLineCount 两个变量
并将其置于服务器上下文(ServletContext)中,其初始值都是 0
*/
@WebListener
public class InitListener implements ServletContextListener {

@Override
public void contextDestroyed(ServletContextEvent evt) {
}

@Override
public void contextInitialized(ServletContextEvent evt) {
evt.getServletContext().setAttribute("onLineCount", 0);
evt.getServletContext().setAttribute("maxOnLineCount", 0);
}

}

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
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
会话监听器,在用户会话创建和销毁的时候根据情况
修改 onLineCount 和 maxOnLineCount 的值
*/
@WebListener
public class MaxCountListener implements HttpSessionListener {

@Override
public void sessionCreated(HttpSessionEvent event) {
ServletContext ctx = event.getSession().getServletContext();
int count = Integer.parseInt(ctx.getAttribute("onLineCount").toString());
count++;
ctx.setAttribute("onLineCount", count);
int maxOnLineCount = Integer.parseInt(ctx.getAttribute("maxOnLineCount").toString());
if (count > maxOnLineCount) {
ctx.setAttribute("maxOnLineCount", count);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ctx.setAttribute("date", df.format(new Date()));
}
}

@Override
public void sessionDestroyed(HttpSessionEvent event) {
ServletContext app = event.getSession().getServletContext();
int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
count--;
app.setAttribute("onLineCount", count);
}
}

说明:这里使用了 Servlet 3 规范中的 @WebListener 注解配置监听器,当然你可以在 web.xml 文件中用 <listener> 标签配置监听器,如 108 题中所示。

web.xml 文件中可以配置哪些内容?

答:web.xml 用于配置 Web 应用的相关信息,如:监听器(listener)、过滤器(filter)、Servlet、相关参数、会话超时时间、安全验证方式、错误页面等,下面是一些开发中常见的配置:

  1. 配置 Spring 上下文加载监听器加载 Spring 配置文件并创建 IoC 容器:

1
2
3
4
5
6
7
8
9
10
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

  1. 配置 Spring 的 OpenSessionInView 过滤器来解决延迟加载和 Hibernate 会话关闭的矛盾:

1
2
3
4
5
6
7
8
9
10
11
<filter>
<filter-name>openSessionInView</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
</filter>

<filter-mapping>
<filter-name>openSessionInView</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

  1. 配置会话超时时间为 10 分钟:

1
2
3
<session-config>
<session-timeout>10</session-timeout>
</session-config>

  1. 配置 404 和 Exception 的错误页面:

1
2
3
4
5
6
7
8
9
<error-page>
<error-code>404</error-code>
<location>/error.jsp</location>
</error-page>

<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error.jsp</location>
</error-page>

  1. 配置安全认证方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<security-constraint>
<web-resource-collection>
<web-resource-name>ProtectedArea</web-resource-name>
<url-pattern>/admin/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>

<login-config>
<auth-method>BASIC</auth-method>
</login-config>

<security-role>
<role-name>admin</role-name>
</security-role>

说明:对 Servlet(小服务)、Listener(监听器)和 Filter(过滤器)等 Web 组件的配置,Servlet 3 规范提供了基于注解的配置方式,可以分别使用 @WebServlet@WebListener@WebFilter 注解进行配置。

补充:如果 Web 提供了有价值的商业信息或者是敏感数据,那么站点的安全性就是必须考虑的问题。安全认证是实现安全性的重要手段,认证就是要解决“Are you who you say you are?”的问题。认证的方式非常多,简单说来可以分为三类:

  • A. What you know? — 口令
  • B. What you have? — 数字证书(U 盾、密保卡)
  • C. Who you are? — 指纹识别、虹膜识别

在 Tomcat 中可以通过建立安全套接字层(Secure Socket Layer, SSL)以及通过基本验证或表单验证来实现对安全性的支持。

你的项目中使用过哪些 JSTL 标签(JSP 标准标签库)?

答:项目中主要使用了 JSTL 的核心标签库,包括 <c:if><c:choose><c: when><c: otherwise><c:forEach> 等,主要用于构造循环和分支结构以控制显示逻辑。

说明:虽然 JSTL 标签库提供了 coresqlfmtxml 等标签库,但是实际开发中建议只使用核心标签库(core),而且最好只使用分支和循环标签并辅以表达式语言(EL),这样才能真正做到数据显示和业务逻辑的分离,这才是最佳实践。

使用标签库有什么好处?如何自定义 JSP 标签?

答:使用标签库的好处包括以下几个方面:

  • 分离 JSP 页面的内容和逻辑,简化了 Web 开发;
  • 开发者可以创建自定义标签来封装业务逻辑和显示逻辑;
  • 标签具有很好的可移植性、可维护性和可重用性;
  • 避免了对 Scriptlet(小脚本)的使用(很多公司的项目开发都不允许在 JSP 中书写小脚本)。

自定义 JSP 标签包括以下几个步骤:

  1. 编写一个 Java 类实现实现 Tag/BodyTag/IterationTag 接口(开发中通常不直接实现这些接口而是继承 TagSupport/BodyTagSupport/SimpleTagSupport 类,这是对缺省适配模式的应用),重写 doStartTag()doEndTag() 等方法,定义标签要完成的功能;
  2. 编写扩展名为 tld 的标签描述文件对自定义标签进行部署,tld 文件通常放在 WEB-INF 文件夹下或其子目录中;
  3. 在 JSP 页面中使用 taglib 指令引用该标签库。

下面是一个自定义标签库的例子。

  1. 标签类源代码 TimeTag.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
    42
    43
    package com.jackfrued.tags;

    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;

    import javax.servlet.jsp.JspException;
    import javax.servlet.jsp.JspWriter;
    import javax.servlet.jsp.tagext.TagSupport;

    public class TimeTag extends TagSupport {
    private static final long serialVersionUID = 1L;

    private String format = "yyyy-MM-dd hh:mm:ss";
    private String foreColor = "black";
    private String backColor = "white";

    public int doStartTag() throws JspException {
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    JspWriter writer = pageContext.getOut();
    StringBuilder sb = new StringBuilder();
    sb.append(String.format("<span style='color:%s;background-color:%s'>%s</span>",
    foreColor, backColor, sdf.format(new Date())));
    try {
    writer.print(sb.toString());
    } catch(IOException e) {
    e.printStackTrace();
    }
    return SKIP_BODY;
    }

    public void setFormat(String format) {
    this.format = format;
    }

    public void setForeColor(String foreColor) {
    this.foreColor = foreColor;
    }

    public void setBackColor(String backColor) {
    this.backColor = backColor;
    }
    }

  2. 编写标签库描述文件 my.tld:

    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
    <?xml version="1.0" encoding="UTF-8" ?>
    <taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">

    <description>定义标签库</description>
    <tlib-version>1.0</tlib-version>
    <short-name>MyTag</short-name>
    <tag>
    <name>time</name>
    <tag-class>com.jackfrued.tags.TimeTag</tag-class>
    <body-content>empty</body-content>
    <attribute>
    <name>format</name>
    <required>false</required>
    </attribute>
    <attribute>
    <name>foreColor</name>
    </attribute>
    <attribute>
    <name>backColor</name>
    </attribute>
    </tag>
    </taglib>

  3. 在 JSP 页面中使用自定义标签:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <%@ page pageEncoding="UTF-8"%>
    <%@ taglib prefix="my" uri="/WEB-INF/tld/my.tld" %>
    <%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
    %>

    <!DOCTYPE html>
    <html>
    <head>
    <base href="<%=basePath%>">
    <title>首页</title>
    <style type="text/css">
    * { font-family: "Arial"; font-size:72px; }
    </style>
    </head>

    <body>
    <my:time format="yyyy-MM-dd" backColor="blue" foreColor="yellow"/>
    </body>
    </html>

注意:如果要将自定义的标签库发布成 JAR 文件,需要将标签库描述文件(tld 文件)放在 JAR 文件的 META-INF 目录下,可以用 JDK 中的 jar 工具完成 JAR 文件的生成,如果不清楚如何操作,可以请教谷老师百老师

说一下表达式语言(EL)的隐式对象及其作用。

答:EL 的隐式对象包括:pageContext、initParam(访问上下文参数)、param(访问请求参数)、paramValues、header(访问请求头)、headerValues、cookie(访问 cookie)、applicationScope(访问 application 作用域)、sessionScope(访问 session 作用域)、requestScope(访问 request 作用域)、pageScope(访问 page 作用域)。

用法如下所示:

1
2
3
4
5
6
7
8
9
${pageContext.request.method}
${pageContext["request"]["method"]}
${pageContext.request["method"]}
${pageContext["request"].method}
${initParam.defaultEncoding}
${header["accept-language"]}
${headerValues["accept-language"][0]}
${cookie.jsessionid.value}
${sessionScope.loginUser.username}

补充:表达式语言的 .[] 运算作用是一致的,唯一的差别在于如果访问的属性名不符合 Java 标识符命名规则,例如上面的 accept-language 就不是一个有效的 Java 标识符,那么这时候就只能用 [] 运算符而不能使用 . 运算符获取它的值。

表达式语言(EL)支持哪些运算符?

答:除了 .[] 运算符,EL 还提供了:

  • 算术运算符:+-*/div%mod
  • 关系运算符:==eq!=ne>gt>=ge<lt<=le
  • 逻辑运算符:&&and||or!not
  • 条件运算符:${statement? A : B}(跟 Java 的条件运算符类似);
  • empty 运算符:检查一个值是否为 null 或者 (数组长度为 0 或集合中没有元素也返回 true)。

Java Web 开发的 Model 1 和 Model 2 分别指的是什么?

答:Model 1 是以页面为中心的 Java Web 开发,使用 JSP+JavaBean 技术将页面显示逻辑和业务逻辑处理分开,JSP 实现页面显示,JavaBean 对象用来保存数据和实现业务逻辑。Model 2 是基于 MVC(模型-视图-控制器,Model-View-Controller)架构模式的开发模型,实现了模型和视图的彻底分离,利于团队开发和代码复用,如下图所示。

MVC 架构模式

Servlet 3 中的异步处理指的是什么?

答:在 Servlet 3 中引入了一项新的技术可以让 Servlet 异步处理请求。有人可能会质疑,既然都有多线程了,还需要异步处理请求吗?答案是肯定需要的,因为如果一个任务处理时间相当长,那么 Servlet 或 Filter 会一直占用着请求处理线程直到任务结束,随着并发用户的增加,容器将会遭遇线程超出的风险,这这种情况下很多的请求将会被堆积起来而后续的请求可能会遭遇拒绝服务,直到有资源可以处理请求为止。异步特性可以帮助应用节省容器中的线程,特别适合执行时间长而且用户需要得到结果的任务,如果用户不需要得到结果则直接将一个 Runnable 对象交给 Executor 并立即返回即可。

补充:多线程在 Java 诞生初期无疑是一个亮点,而 Servlet 单实例多线程的工作方式也曾为其赢得美名,然而技术的发展往往会颠覆我们很多的认知,就如同当年爱因斯坦的相对论颠覆了牛顿的经典力学一般。事实上,异步处理绝不是 Serlvet 3 首创,如果你了解 Node.js 的话,对 Servlet 3 的这个重要改进就不以为奇了。

下面是一个支持异步处理请求的 Servlet 的例子。

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
import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = {"/async"}, asyncSupported = true)
public class AsyncServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 开启 Tomcat 异步 Servlet 支持
req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

final AsyncContext ctx = req.startAsync(); // 启动异步处理的上下文
// ctx.setTimeout(30000);
ctx.start(new Runnable() {

@Override
public void run() {
// 在此处添加异步处理的代码

ctx.complete();
}
});
}
}

如何在基于 Java 的 Web 项目中实现文件上传和下载?

答:在 Sevlet 3 以前,Servlet API 中没有支持上传功能的 API,因此要实现上传功能需要引入第三方工具从 POST 请求中获得上传的附件或者通过自行处理输入流来获得上传的文件,我们推荐使用 Apache 的 commons-fileupload。

从 Servlet 3 开始,文件上传变得无比简单,相信看看下面的例子一切都清楚了。

上传页面 index.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Photo Upload</title>
</head>
<body>
<h1>Select your photo and upload</h1>
<hr/>
<div style="color:red;font-size:14px;">${hint}</div>
<form action="UploadServlet" method="post" enctype="multipart/form-data">
Photo file: <input type="file" name="photo" />
<input type="submit" value="Upload" />
</form>
</body>
</html>

支持上传的 Servlet:

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
package com.jackfrued.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// 可以用 request.getPart() 方法获得名为 photo 的上传附件
// 也可以用 request.getParts() 获得所有上传附件(多文件上传)
// 然后通过循环分别处理每一个上传的文件
Part part = request.getPart("photo");
if (part != null && part.getSubmittedFileName().length() > 0) {
// 用 ServletContext 对象的 getRealPath()方法获得上传文件夹的绝对路径
String savePath = request.getServletContext().getRealPath("/upload");
// Servlet 3.1 规范中可以用 Part 对象的 getSubmittedFileName()方法获得上传的文件名
// 更好的做法是为上传的文件进行重命名(避免同名文件的相互覆盖)
part.write(savePath + "/" + part.getSubmittedFileName());
request.setAttribute("hint", "Upload Successfully!");
} else {
request.setAttribute("hint", "Upload failed!");
}
// 跳转回到上传页面
request.getRequestDispatcher("index.jsp").forward(request, response);
}

}

服务器收到用户提交的表单数据,到底是调用 Servlet 的 doGet() 还是 doPost() 方法?

答:HTML 的 <form> 元素有一个 method 属性,用来指定提交表单的方式,其值可以是 getpost。我们自定义的 Servlet 一般情况下会重写 doGet()doPost() 两个方法之一或全部,如果是 GET 请求就调用 doGet() 方法,如果是 POST 请求就调用 doPost() 方法,那为什么为什么这样呢?我们自定义的 Servlet 通常继承自 HttpServletHttpServlet 继承自 GenericServlet 并重写了其中的 service() 方法,这个方法是 Servlet 接口中定义的。HttpServlet 重写的 service() 方法会先获取用户请求的方法,然后根据请求方法调用 doGet()doPost()doPut()doDelete() 等方法,如果在自定义 Servlet 中重写了这些方法,那么显然会调用重写过的(自定义的)方法,这显然是对模板方法模式的应用(如果不理解,请参考阎宏博士的《Java 与模式》一书的第 37 章)。当然,自定义 Servlet 中也可以直接重写 service() 方法,那么不管是哪种方式的请求,都可以通过自己的代码进行处理,这对于不区分请求方法的场景比较合适。

JSP 中的静态包含和动态包含有什么区别?

答:静态包含是通过 JSP 的 include 指令包含页面,动态包含是通过 JSP 标准动作 <jsp:forward> 包含页面。

  • 静态包含是编译时包含,如果包含的页面不存在则会产生编译错误,而且两个页面的 contentType 属性应保持一致,因为两个页面会合二为一,只产生一个 class 文件,因此被包含页面发生的变动再包含它的页面更新前不会得到更新。

  • 动态包含是运行时包含,可以向被包含的页面传递参数,包含页面和被包含页面是独立的,会编译出两个 class 文件,如果被包含的页面不存在,不会产生编译错误,也不影响页面其他部分的执行。代码如下所示:

1
2
3
4
5
6
7
<%-- 静态包含 --%>
<%@ include file="..." %>

<%-- 动态包含 --%>
<jsp:include page="...">
<jsp:param name="..." value="..." />
</jsp:include>

Servlet 中如何获取用户提交的查询参数或表单数据?

答:可以通过请求对象 HttpServletRequestgetParameter() 方法通过参数名获得参数值。如果有包含多个值的参数(例如复选框),可以通过请求对象的 getParameterValues() 方法获得。当然也可以通过请求对象的 getParameterMap() 获得一个参数名和参数值的映射(Map)。

Servlet 中如何获取用户配置的初始化参数以及服务器上下文参数?

答:可以通过重写 Servlet 接口的 init(ServletConfig) 方法并通过 ServletConfig 对象的 getInitParameter() 方法来获取 Servlet 的初始化参数。可以通过 ServletConfig 对象的 getServletContext() 方法获取 ServletContext 对象,并通过该对象的 getInitParameter() 方法来获取服务器上下文参数。当然,ServletContext 对象也在处理用户请求的方法(如 doGet() 方法)中通过请求对象的 getServletContext()方法来获得。

如何设置请求的编码以及响应内容的类型?

答:通过请求对象 ServletRequestsetCharacterEncoding(String) 方法可以设置请求的编码,其实要彻底解决乱码问题就应该让页面、服务器、请求和响应、Java 程序都使用统一的编码,最好的选择当然是 UTF-8;通过响应对象 ServletResponsesetContentType(String) 方法可以设置响应内容的类型,当然也可以通过 HttpServletResponsed 对象的 setHeader(String, String) 方法来设置。

说明:现在如果还有公司在面试的时候问 JSP 的声明标记、表达式标记、小脚本标记这些内容的话,这样的公司也不用去了,其实 JSP 内置对象、JSP 指令这些东西基本上都可以忘却了,关于 Java Web 开发的相关知识,可以看一下我的 Servlet&JSP 思维导图,上面有完整的知识点的罗列。

解释一下网络应用的模式及其特点。

答:典型的网络应用模式大致有三类:B/S、C/S、P2P。其中 B 代表浏览器(Browser)、C 代表客户端(Client)、S 代表服务器(Server),P2P 是对等模式,不区分客户端和服务器。B/S 应用模式中可以视为特殊的 C/S 应用模式,只是将 C/S 应用模式中的特殊的客户端换成了浏览器,因为几乎所有的系统上都有浏览器,那么只要打开浏览器就可以使用应用,没有安装、配置、升级客户端所带来的各种开销。P2P 应用模式中,成千上万台彼此连接的计算机都处于对等的地位,整个网络一般来说不依赖专用的集中服务器。网络中的每一台计算机既能充当网络服务的请求者,又对其它计算机的请求作出响应,提供资源和服务。通常这些资源和服务包括:信息的共享和交换、计算资源(如 CPU 的共享)、存储共享(如缓存和磁盘空间的使用)等,这种应用模式最大的阻力安全性、版本等问题,目前有很多应用都混合使用了多种应用模型,最常见的网络视频应用,它几乎把三种模式都用上了。

补充:此题要跟“电子商务模式”区分开,因为有很多人被问到这个问题的时候马上想到的是 B2B(如阿里巴巴)、B2C(如当当、亚马逊、京东)、C2C(如淘宝、拍拍)、C2B(如威客)、O2O(如美团、饿了么)。对于这类问题,可以去百度上面科普一下。

什么是 Web Service(Web 服务)?

答:从表面上看,Web Service 就是一个应用程序,它向外界暴露出一个能够通过 Web 进行调用的 API。这就是说,你能够用编程的方法透明的调用这个应用程序,不需要了解它的任何细节,跟你使用的编程语言也没有关系。例如可以创建一个提供天气预报的 Web Service,那么无论你用哪种编程语言开发的应用都可以通过调用它的 API 并传入城市信息来获得该城市的天气预报。之所以称之为 Web Service,是因为它基于 HTTP 协议传输数据,这使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件,就可相互交换数据或集成。

补充:这里必须要提及的一个概念是 SOA(Service-Oriented Architecture,面向服务的架构),SOA 是一种思想,它将应用程序的不同功能单元通过中立的契约联系起来,独立于硬件平台、操作系统和编程语言,使得各种形式的功能单元能够更好的集成。显然,Web Service 是 SOA 的一种较好的解决方案,它更多的是一种标准,而不是一种具体的技术。

概念解释:SOAP、WSDL、UDDI。

答:

  • SOAP:简单对象访问协议(Simple Object Access Protocol),是 Web Service 中交换数据的一种协议规范。
  • WSDL:Web 服务描述语言(Web Service Description Language),它描述了 Web 服务的公共接口。这是一个基于 XML 的关于如何与 Web 服务通讯和使用的服务描述;也就是描述与目录中列出的 Web 服务进行交互时需要绑定的协议和信息格式。通常采用抽象语言描述该服务支持的操作和信息,使用的时候再将实际的网络协议和信息格式绑定给该服务。
  • UDDI:统一描述、发现和集成(Universal Description, Discovery and Integration),它是一个基于 XML 的跨平台的描述规范,可以使世界范围内的企业在互联网上发布自己所提供的服务。简单的说,UDDI 是访问各种 WSDL 的一个门面(可以参考设计模式中的门面模式)。

Java 规范中和 Web Service 相关的规范有哪些?

答:Java 规范中和 Web Service 相关的有三个:

  • JAX-WS(JSR 224):这个规范是早期的基于 SOAP 的 Web Service 规范 JAX-RPC 的替代版本,它并不提供向下兼容性,因为 RPC 样式的 WSDL 以及相关的 API 已经在 Java EE5 中被移除了。WS-MetaData 是 JAX-WS 的依赖规范,提供了基于注解配置 Web Service 和 SOAP 消息的相关 API。
  • JAXM(JSR 67):定义了发送和接收消息所需的 API,相当于 Web Service 的服务器端。
  • JAX-RS(JSR 311 & JSR 339 & JSR 370):是 Java 针对 REST(Representation State Transfer)架构风格制定的一套 Web Service 规范。REST 是一种软件架构模式,是一种风格,它不像 SOAP 那样本身承载着一种消息协议, (两种风格的 Web Service 均采用了 HTTP 做传输协议,因为 HTTP 协议能穿越防火墙,Java 的远程方法调用(RMI)等是重量级协议,通常不能穿越防火墙),因此可以将 REST 视为基于 HTTP 协议的软件架构。REST 中最重要的两个概念是资源定位和资源操作,而 HTTP 协议恰好完整的提供了这两个点。HTTP 协议中的 URI 可以完成资源定位,而 GET、POST、OPTION、DELETE 方法可以完成资源操作。因此 REST 完全依赖 HTTP 协议就可以完成 Web Service,而不像 SOAP 协议那样只利用了 HTTP 的传输特性,定位和操作都是由 SOAP 协议自身完成的,也正是由于 SOAP 消息的存在使得基于 SOAP 的 Web Service 显得笨重而逐渐被淘汰。

介绍一下你了解的 Java 领域的 Web Service 框架。

答:Java 领域的 Web Service 框架很多,包括 Axis2(Axis 的升级版本)、Jersey(RESTful 的 Web Service 框架)、CXF(XFire 的延续版本)、Hessian、Turmeric、JBoss SOA 等,其中绝大多数都是开源框架。

注意:面试被问到这类问题的时候一定选择自己用过的最熟悉的作答,如果之前没有了解过就应该在面试前花一些时间了解其中的两个,并比较其优缺点,这样才能在面试时给出一个漂亮的答案。