JavaEE学习笔记 - Servlet

本文要点

  这里主要是总结我在学习Servlet这块时的基础知识。其中,实践代码以及用途介绍较多,而原理性的分析则较少,在学习JavaWeb开发这块,我也渐渐了解到,能够把技术用在开发上了才是首要的,之后再深入了解它其中的原理然后举一反三。技术实践与原理研究两个方面我们都要兼顾,技术实践更加重要,但原理研究我们也不能懈怠。

Servlet基础要点

接下来要介绍的是Servlet最基础的知识,都是平日开发中经常要用到的Servlet技术。

第一个Servlet,编写、部署和运行

这里,通过最简单的代码和步骤来演示如何编写Servlet,同时将它部署到Tomcat上再运行它。

使用Java代码编写一个Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.makwan.javaee.a_first;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletOutputStream os = resp.getOutputStream();
os.write(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()).getBytes());
}
}

这个Servlet会通过字节流向浏览器发送当前的时间信息,并显示在页面上。

编写配置文件web.xml

程序代码就是如此简单,没有其他复杂的功能。但我们还需要到web.xml配置文件中针对该Servlet进行配置。

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://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<!-- Servlet别名, 自己可以随意起, 但这个名字在配置文件中必须唯一 -->
<servlet-name>TestServlet</servlet-name>
<!-- Servlet类的全限定类名-->
<servlet-class>com.makwan.javaee.a_first.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- 对应Servlet别名 -->
<servlet-name>TestServlet</servlet-name>
<!-- 映射的访问路径 -->
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>

编译Servlet类

使用控制台的javac命令进行这项工作。首先进入到Servlet类所在的路径,然后输入以下指令

1
2
3
set classpath=tomcat所在路径\lib\servlet-api.jar

javac -d . TestServlet.java

打包并部署

  • 新建一个文件夹,给它一个名称,比如:testproj
  • 然后在testproj里面建立一个文件夹并命名为WEB-INF
  • 接着在WEB-INF新建一个文件夹classes
  • 将包含编译后的.class文件整个目录复制到classes目录中, 而web.xml就复制到WEB-INF目录中
  • 最后将testproj整个文件夹复制到Tomcat目录的webapps文件夹中
  • 启动Tomcat

访问映射路径

Tomcat启动完毕后,我们就可以打开浏览器输入映射路径访问Servlet了。输入路径http://localhost:8080/testproj/test,就能看到效果了。

以上是在没有IDE情况下进行Servlet开发,需要手动做更多的工作,也比较繁琐,但是如果用Eclipse/MyEclipse/IntelliJ IDEA这些IDE那就会轻松很多。

web.xml中配置Servlet的一点小细节:

  • 映射路径必须以/*开头,但不可以/*.do这种形式
  • 一个Servlet可以被多个url路径映射
  • 如果一个url路径映射多个Servlet,那访问url路径访问的肯定是最后一个Servlet

Servlet类型层次

我们首先要了解的是Servlet的层次,这些接口和类就是我们要重点关注的对象。类型层次如下图:

我们可以通过使用这些Servlet类型来创建自定义Servlet。

Servlet接口

1
2
3
4
5
6
7
8
9
10
11
12
public interface Servlet {
public void init(ServletConfig config) throws ServletException;

public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;

public void destroy();

public String getServletInfo();

public ServletConfig getServletConfig();
}

Servlet中的其中3个方法是生命周期方法:initservicedestroy。所以,这3个方法至关重要,而它们作用就如方法名所示。
顺便简单地说明Servlet的生命周期:

  • 客户端请求服务器某个Servlet,Servlet实例被服务器创建
  • Servlet实例创建后就立刻调用init方法做初始化工作
  • 然后Servlet就会调用service方法处理客户端的请求
  • 当服务器认为Servlet没必要存在时,就会调用destroy销毁该Servlet, 比如:关闭服务器

我们想要自定义一个属于自己的Servlet,只要实现该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
39
package com.makwan.javaee.b_life_cycle;

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

public class ServletImplementation implements Servlet {
public ServletImplementation() {
System.out.println("ServletImplementation构造方法被调用");
}

// 初始化
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("ServletImplementation.init被调用");
}

// 处理用户请求
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
System.out.println("ServletImplementation.service被调用");
}

// 销毁
@Override
public void destroy() {
System.out.println("ServletImplementation.destroy被调用");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public ServletConfig getServletConfig() {
return null;
}
}

当然,在方法里纯粹的只是输出一些消息,这样可以让我们知道这些方法的执行流程而已。

GenericServlet抽象类

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
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
//...

private transient ServletConfig config;

@Override
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}

@Override
public Enumeration<String> getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}

@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}

@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}

public void init() throws ServletException {
// NOOP by default
}


@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;

//...
}

GenericServlet抽象类实现Servlet接口,也额外实现了ServletConfig接口,并额外增加了一些重要的方法。

实现Servlet接口来创建自定义Servlet是比较麻烦的,但继承GenericServlet抽象类就更加方便。

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 com.makwan.javaee.b_life_cycle;

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

/**
* GenericServlet抽象类
* - 对于生命周期方法, 它比Servlet接口多增加了一个无参的init方法,
* 该方法在GenericServlet中被有参的init方法调用,
* 所以, 如果用户需要进行额外的初始化工作, 可以重写该无参的init方法
*/
public class GenericServletSubClass extends GenericServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("GenericServletSubClass.init(ServletConfig config)被调用");
}

// 用户需要额外的初始化工作都可以在这个无参init方法里实现
@Override
public void init() throws ServletException {
System.out.println("GenericServletSubClass.init()被调用");
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("GenericServletSubClass.service被调用");
}
}

这里重写了父类的无参init方法,这样,我们自己可以在生命周期方法init(ServletConfig)执行时根据我们自身的需求作出一些特殊的初始化工作。

HttpServlet抽象类

首先来看看这个类源码中比较重要的部分。

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
114
115
116
117
118
119
120
121
122
public abstract class HttpServlet extends GenericServlet {
/**
* Dispatches client requests to the protected
* <code>service</code> method. There's no need to
* override this method.
*
* @param req the {@link HttpServletRequest} object that
* contains the request the client made of
* the servlet
*
* @param res the {@link HttpServletResponse} object that
* contains the response the servlet returns
* to the client
*
* @exception IOException if an input or output error occurs
* while the servlet is handling the
* HTTP request
*
* @exception ServletException if the HTTP request cannot
* be handled
*
* @see javax.servlet.Servlet#service
*/
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;

if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}

request = (HttpServletRequest) req;
response = (HttpServletResponse) res;

service(request, response);
}

/**
* Receives standard HTTP requests from the public
* <code>service</code> method and dispatches
* them to the <code>do</code><i>XXX</i> methods defined in
* this class. This method is an HTTP-specific version of the
* {@link javax.servlet.Servlet#service} method. There's no
* need to override this method.
*
* @param req the {@link HttpServletRequest} object that
* contains the request the client made of
* the servlet
*
* @param resp the {@link HttpServletResponse} object that
* contains the response the servlet returns
* to the client
*
* @exception IOException if an input or output error occurs
* while the servlet is handling the
* HTTP request
*
* @exception ServletException if the HTTP request
* cannot be handled
*
* @see javax.servlet.Servlet#service
*/
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();

if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);

} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);

} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);

} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);

} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);

} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);

resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
}

HttpServlet最重要的就是这两个service方法:

  • 参数类型为ServletRequestServletResponseservice我们最清楚不过了,它是Servlet接口用于处理用户请求的生命周期方法
  • 参数类型为HttpServletRequestHttpServletResponseserviceHttpServlet中根据用户的请求方式进行相应的处理的方法

官方提供HttpServlet更加方便于开发者进行Web开发,一般情况下,我们只要继承该类并重写相应的doXXX方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HttpServletSubClass extends HttpServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 调用HttpServlet生命周期方法service, 在里面再调用HttpServletSubClass处理请求方法service
super.service(req, res);
System.out.println("HttpServletSubClass.service(ServletRequest req, ServletResponse res)被调用");
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 调用HttpServlet处理请求方法service, 在里面再调用了处理对应请求方式的doXXX方法
super.service(req, resp);
System.out.println("HttpServletSubClass.service(HttpServletRequest req, HttpServletResponse resp)被调用");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HttpServletSubClass.doGet");
}
}

自启动Servlet

  自启动Servlet指的是在Tomcat服务器启动的时候,这个Servlet就会被自动实例化,并执行Servlet的生命周期方法init执行初始化操作。当我们有这些需求:在Web项目启动的时候预先加载资源或者做一些比较耗时的初始化操作等等,自启动Servlet就非常有用了。
  在web.xml将某个Servlet配置上一个load-on-startup元素,那么它就是一个自启动Servlet。其中,load-on-startup所指定的值必须是正整数,比如:<load-on-startup>1</load-on-startup>,并且,数值越小表示启动顺序越靠前或优先级越高

更详细权威的描述那就得看官方文档的描述:

Instantiate an instance of each servlet identified by a <servlet> element that
includes a <load-on-startup> element in the order defined by the load-on-startup
element values, and call each servlet instance’s init() method.

the load-on-startup element specifies the order in which the component is
initialized relative to other Web components in the Web container

官方描述大意就是说:

<servlet>元素中配置了load-on-startup元素并在里头指定其顺序值,
在启动服务器时, 那就会按照在load-on-startup所指定的顺序值对这些servlet会实例化并且调用init方法

并且不只有servlet可以这样做,Web容器中的其他Web组件通过配置load-on-startup也可以达到这种效果

给两个Servlet都配置上load-on-startup,一个指定10,一个指定3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<servlet>
<servlet-name>AutoStartUpServletA</servlet-name>
<servlet-class>com.makwan.javaee.c_auto_start_up.AutoStartUpServletA</servlet-class>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet>
<servlet-name>AutoStartUpServletB</servlet-name>
<servlet-class>com.makwan.javaee.c_auto_start_up.AutoStartUpServletB</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>AutoStartUpServletA</servlet-name>
<url-pattern>/asu_servlet_a</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>AutoStartUpServletB</servlet-name>
<url-pattern>/asu_servlet_b</url-pattern>
</servlet-mapping>

两个在同一个包内做相同的事情的Servlet。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.makwan.javaee.c_auto_start_up;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import java.text.SimpleDateFormat;
import java.util.Date;

public class AutoStartUpServletA extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " - AutoStartUpServletA init....");
}
}

public class AutoStartUpServletB extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ " - AutoStartUpServletA init....");
}
}

最终启动服务器,控制台上会输出:

1
2
2017-10-21 00:17:39 - AutoStartUpServletB init....
2017-10-21 00:17:39 - AutoStartUpServletA init....

从这可以更清楚地看出:给load-on-startup配置的正整数数值越小,该Servlet的实例化并执行初始化的顺序就越靠前。

通过自启动Servlet来加载同一个包内的db.properties

db.properties的内容如下

1
2
3
4
driver=com.mysql.Driver
url=jdbc:mysql//localhost:3306/day05-servlet
name=root
password=root

Servlet在web.xml中对应的配置:

1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>LoadPropertiesServlet</servlet-name>
<servlet-class>com.makwan.javaee.c_auto_start_up.LoadPropertiesServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>LoadPropertiesServlet</servlet-name>
<url-pattern>/lpServlet</url-pattern>
</servlet-mapping>

Servlet具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.makwan.javaee.c_auto_start_up;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import java.io.IOException;
import java.util.Properties;

public class LoadPropertiesServlet extends HttpServlet {
@Override
public void init() throws ServletException {
try {
Properties p = new Properties();
// 加载配置文件
p.load(getClass().getResourceAsStream("db.properties"));
// 加载成功后输出
p.list(System.out);
} catch (IOException e) {
e.printStackTrace();
}
}
}

默认Servlet

当用户请求的资源不存在,而我们想要项目使用某个Servlet对这种情况作出统一处理时,默认Servlet就能发挥它的用处了。

配置默认Servlet步骤:

  • 将Servlet映射的url路径设置为/
  • 将Servlet配置为自启动Servlet

这两步

1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>DefaultServlet</servlet-name>
<servlet-class>com.makwan.javaee.d_default.DefaultServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>DefaultServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.makwan.javaee.d_default;

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

/**
* 默认Servlet
* - 必须在web.xml中给对应的Servlet映射访问路径/
*/
public class DefaultServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getOutputStream().write(("This resource: " + req.getRequestURI()
+ " is not available!").getBytes());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

ServletConfig接口

这个接口的主要作用是获取Servlet的各种信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<servlet>
<servlet-name>scp</servlet-name>
<servlet-class>com.makwan.javaee.d_config.ServletConfigPrinter</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>ip</param-name>
<param-value>192.168.0.1</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>scp</servlet-name>
<url-pattern>/servletConfigPrinter</url-pattern>
</servlet-mapping>

在配置web.xml配置该Servlet的时候,同时配置属于该Servlet的初始化参数,这样在Servlet代码中就可以通过ServletConfig对象获取。

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
package com.makwan.javaee.d_config;

import javax.servlet.ServletConfig;
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.util.Enumeration;

/**
* ServletConfig接口
* - 这个接口就代表着Servlet的配置信息
*/
public class ServletConfigPrinter extends HttpServlet {
private ServletConfig config;

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.config = config;
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取Servlet配置的别名
String servletName = config.getServletName();
System.out.println("servletName : " + servletName);

// 获取该Servlet配置内所有的参数
Enumeration<String> initParameterNames = config.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String parameterName = initParameterNames.nextElement();
String parameterValue = config.getInitParameter(parameterName);
System.out.println(parameterName + " = " + parameterValue);
}

String encoding = config.getInitParameter("encoding");
resp.getOutputStream().write("这是一个演示ServletConfig接口的例子".getBytes(encoding));
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

每一个Servlet都可以配置属于自身的参数,这就要根据我们的需求而定了。

ServletContext接口

ServletContext从字面意思来讲就是Servlet上下文,更通俗地讲这个接口代表整个网站应用。它有很多用途,很多功能在开发中都是经常用到的。

如何获取ServletContext对象?

要使用ServletContext所附带的功能,我们首先就要搞清楚这个对象是如何获取的。常规方法有以下3种,当然还有其他方法就不一一列举。

  1. GenericServlet抽象类中的getServletContext方法
  2. ServletRequest接口中的getServletContext方法,而HttpServletRequest接口是它的子接口
  3. ServletCongig接口中的getServletContext方法

作用1:作为域对象使用

域对象是用于存取数据的,而ServletContext对象也是4大域对象之一。
这个域对象通常称它为application域,它的作用域是最大的,所以这个存储在这个域的数据是可以被整个网站应用所共享的。

如果你需要某些公共数据能被整个网站应用的各个部分访问到,那用这个域来存取是最合适不过了。

StoreDataServlet这个Servlet是把数据存储到application域中。

1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>StoreDataServlet</servlet-name>
<servlet-class>com.makwan.javaee.e_servlet_context.StoreDataServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>StoreDataServlet</servlet-name>
<url-pattern>/sds</url-pattern>
</servlet-mapping>
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
package com.makwan.javaee.e_servlet_context;

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

/**
* ServletContext用作application域对象
*/
public class StoreDataServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取网站对象
ServletContext context = getServletContext();
// 将数据放到application域中
context.setAttribute("username", "makwan");
context.setAttribute("age", 25);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

而在GetDataServlet里则是取出之前在StoreDataServlet里存储于application域的数据并显示在页面上。

1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>GetDataServlet</servlet-name>
<servlet-class>com.makwan.javaee.e_servlet_context.GetDataServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>GetDataServlet</servlet-name>
<url-pattern>/gds</url-pattern>
</servlet-mapping>
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
package com.makwan.javaee.e_servlet_context;

import javax.servlet.ServletContext;
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.util.Enumeration;

/**
* ServletContext用作application域对象
*/
public class GetDataServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");

// 获取网站对象
ServletContext context = getServletContext();
// 从application域中获取数据, 并输出
Enumeration<String> attributeNames = context.getAttributeNames();
while (attributeNames.hasMoreElements()) {
String attributeName = attributeNames.nextElement();
Object attributeValue = context.getAttribute(attributeName);
resp.getWriter().write("<br/>" + attributeName + " = " + attributeValue + "<br/>");
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

作用2:获取网站资源

既然ServletContext 是代表整个网站应用,这样我们就可以通过它来获取网站中的某些资源。
这个接口中有两个方法是经常用于获取网站资源的:

  • URL getResource(String path)
  • InputStream getResourceAsStream(String path)

以下是一个文件下载的例子,这里我先在WEB-INF目录下新建一个文件夹download_resources,然后在里面存放一个文件即可,这里我放的是一张图片。

1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>WebResourcesDownload</servlet-name>
<servlet-class>com.makwan.javaee.e_servlet_context.WebResourcesDownload</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>WebResourcesDownload</servlet-name>
<url-pattern>/wrd</url-pattern>
</servlet-mapping>
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
package com.makwan.javaee.e_servlet_context;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;

/**
* 通过ServletContext获取网站内部的资源
* - 一般都是通过这两个方法来获取
* URL getResource(String path)
* InputStream getResourceAsStream(String path)
*/
public class WebResourcesDownload extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取网站内资源文件的路径
ServletContext context = getServletContext();
URL url = context.getResource("/WEB-INF/download_resources/龙珠-DragonBall.jpg");
String path = url.getPath();

// 获取下载文件的文件名
String fileName = path.substring(path.lastIndexOf("/") + 1);
// 使用utf-8对文件名进行编码
fileName = URLEncoder.encode(fileName, "utf-8");

// 通知浏览器以文件下载的形式处理该内容
resp.setHeader("Content-Type", "application/octet-stream");
resp.setHeader("Content-Disposition", "attachment;filename=" + fileName);

// 获取输入流
FileInputStream fis = new FileInputStream(path);

// 获取输出流
ServletOutputStream sos = resp.getOutputStream();

// 输出资源文件数据
byte[] b = new byte[1024];
int len = 0;
while ((len = fis.read(b)) != -1) {
sos.write(b, 0, len);
}

// 释放资源
fis.close();
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

作用3:获取网站路径

有时候我们要构造重定向路径,这时候就需要知道网站应用的名字,这样路径就不会写死了,重定向路径会根据我们所更改的网站名而变化。

1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>ContextPathServlet</servlet-name>
<servlet-class>com.makwan.javaee.e_servlet_context.ContextPathServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ContextPathServlet</servlet-name>
<url-pattern>/contextPathServlet</url-pattern>
</servlet-mapping>
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 com.makwan.javaee.e_servlet_context;

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

/**
* 获取网站路径
* - ServletContext.getContextPath
*/
public class ContextPathServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取网站对象
ServletContext context = getServletContext();
// 获取网站路径
String contextPath = context.getContextPath();
resp.getWriter().write("<h1>" + contextPath + "</h1>");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

作用4:获取全局参数

有时候,我们需要多个Servlet使用同一个配置参数,这样就可以配置全局参数了,通过ServletContext对象进行获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 配置全局参数 -->
<context-param>
<param-name>author</param-name>
<param-value>plaYwiThsouL</param-value>
</context-param>

<servlet>
<servlet-name>ContextParamServlet</servlet-name>
<servlet-class>com.makwan.javaee.e_servlet_context.ContextParamServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ContextParamServlet</servlet-name>
<url-pattern>/contextParamServlet</url-pattern>
</servlet-mapping>
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
package com.makwan.javaee.e_servlet_context;

import javax.servlet.ServletContext;
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 ContextParamServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = getServletContext();
String author = context.getInitParameter("author");
resp.getWriter().write("<h1>" + author + "</h1>");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

作用5:转发

通过ServletContext可以实现转发的效果。

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>ForwardServlet</servlet-name>
<servlet-class>com.makwan.javaee.e_servlet_context.ForwardServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ForwardServlet</servlet-name>
<url-pattern>/forward</url-pattern>
</servlet-mapping>
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 com.makwan.javaee.e_servlet_context;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
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 ForwardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取网站对象
ServletContext context = getServletContext();
// 获取转发器
RequestDispatcher dispatcher = context.getRequestDispatcher("/index.jsp");
// 进行转发
dispatcher.forward(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

以上都是Servlet基本的内容,但很多需求都是可以通过这些技术来实现的,虽然它们简单,但我们更应该重视。

小细节

Q:如何修改MyEclipse生成Servlet源码所用的模版?

  • A:到MyEclipse的安装目录中,找到plugins这个文件夹,然后进去在里面找到com.genuitec.eclipse.wizards_xxxxxx.yyyyyyyyyy.jar这个文件,你可以查找以com.genuitec.eclipse.wizards_开头的文件,接着用rar的形式打开这个文件,最后按照你自己所想修改里面的Servlet.java模版文件即可。

Q:MyEclipse中如何修改网站应用的网站名?

  • A:直接修改网站项目的名字是没用的,发布到Tomcat还是原来的名字;这时,只需要 右键项目 - Properties - 在MyEclipse下找到Web,在这里便可以真正地修改网站名了。

Q:Web项目中的访问路径是如何正确书写得?

  • A:访问路径在是Web项目中经常出现的,我们只要遵循一个原则即可:书写任何访问路径时首先以/开头,然后再分析这个路径是给哪里用的。
    1.如果这个路径是给服务器用的,那么/就是代表当前网站路径:/网站名
    2.如果这个路径是给浏览器用的,那么/就是代表服务器里存放网站应用的目录,比如使用的是Tomcat,/则代表/webapps