4.12、Session
由于 Cookie 会保存在客户端上,所以有安全隐患问题。还有一个问题,Cookie 的大小与个数有限制,为了解决这个问题,于是就有了 Session,Session 是基于 Cookie 的一种会话机制。Cookie 是服务器返回一小份数据给客户端,并且存放在客户端上。Session 是数据存放在服务器端。
常见用法:
HttpSession session = request.getSession();
String id = session.getId();
session.setAttribute(name, value);
session.getAttribute(name);
session.removeAttribute(name);
生命周期:
- 创建:如果有在 servlet 里面调用了 request.getSession()
- 销毁:关闭服务器或者 session 会话时间过期。默认有效期: 30 分钟。
Listener 是监听器,监听 Servlet 某一个事件的发生或者状态的改变,它的内部其实就是接口回调。
5.1、监听三个作用域创建和销毁
5.1.1、ServletContextListener
监听对象:
ServletContextListener用于监听ServletContext对象作用域创建和销毁,利用它可以完成自己想要的初始化工作。
生命周期:
servletcontext创建:
1、启动服务器的时候
servletContext销毁:
1、关闭服务器
2、从服务器移除项目
如何创建:
package com.caochenlei.servlet.demo;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("contextInitialized ...");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("contextDestroyed ...");
}
}
如何配置:
<listener>
<listener-class>com.caochenlei.servlet.demo.MyContextListener</listener-class>
</listener>
5.1.2、ServletRequestListener
监听对象:
ServletRequestListener用于监听ServletRequest对象作用域创建和销毁,利用它可以判断当前受否存在请求。
生命周期:
request创建:
1、访问服务器上的任意资源都会有请求出现。
访问 html :会
访问 jsp :会
访问 servlet :会
request销毁:
1、服务器已经对这次请求作出了响应。
如何创建:
package com.caochenlei.servlet.demo;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
public class MyRequestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("requestInitialized ...");
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println("requestDestroyed ...");
}
}
如何配置:
<listener>
<listener-class>com.caochenlei.servlet.demo.MyRequestListener</listener-class>
</listener>
5.1.3、HttpSessionListener
监听对象:
HttpSessionListener用于监听HttpSession对象作用域创建和销毁,利用它可以统计在线人数。
生命周期:
session的创建:
1、只要调用getSession()方法。
html :不会
jsp :会
servlet :会
session的销毁:
1、会话超时30分钟。
2、非正常关闭服务器。
3、正常关闭服务器(序列化)
如何创建:
package com.caochenlei.servlet.demo;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class MySessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println("sessionCreated ...");
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("sessionDestroyed ...");
}
}
如何配置:
<listener>
<listener-class>com.caochenlei.servlet.demo.MySessionListener</listener-class>
</listener>
5.2、监听三个作用域属性状态变更
5.2.1、ServletContextAttributeListener
主要作用:
监听ServletContext存值状态变更。
主要方法:
如何创建:
package com.caochenlei.servlet.demo;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
public class MyContextAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("attributeAdded ...");
}
@Override
public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("attributeRemoved ...");
}
@Override
public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("attributeReplaced ...");
}
}
如何配置:
<listener>
<listener-class>com.caochenlei.servlet.demo.MyContextAttributeListener</listener-class>
</listener>
5.2.2、ServletRequestAttributeListener
主要作用:
监听ServletRequest存值状态变更。
主要方法:
如何创建:
package com.caochenlei.servlet.demo;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
public class MyRequestAttributeListener implements ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) {
System.out.println("attributeAdded ...");
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent) {
System.out.println("attributeRemoved ...");
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent) {
System.out.println("attributeReplaced ...");
}
}
如何配置:
<listener>
<listener-class>com.caochenlei.servlet.demo.MyRequestAttributeListener</listener-class>
</listener>
5.2.3、HttpSessionAttributeListener
主要作用:
监听HttpSession存值状态变更。
主要方法:
如何创建:
package com.caochenlei.servlet.demo;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
public class MySessionAttributeListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("attributeAdded ...");
}
@Override
public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("attributeRemoved ...");
}
@Override
public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("attributeReplaced ...");
}
}
如何配置:
<listener>
<listener-class>com.caochenlei.servlet.demo.MySessionAttributeListener</listener-class>
</listener>
5.3、监听 HttpSession 存值状态变更
5.3.1、HttpSessionBindingListener
主要作用:
监听对象与session绑定和解除绑定的动作。
如何创建:
package com.caochenlei.servlet.demo;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
public class MySessionBindingListener implements HttpSessionBindingListener {
@Override
public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("valueBound ...");
}
@Override
public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("valueUnbound ...");
}
}
如何配置:
这一类监听器不用注册。
5.3.2、HttpSessionActivationListener
主要作用:
用于监听现在session的值是钝化(序列化)还是活化(反序列化)的动作。
钝化(序列化) :把内存中的数据存储到硬盘上。
活化(反序列化):把硬盘中的数据读取到内存中。
如何钝化:
1. 在tomcat的 conf/context.xml 里面配置
对所有的运行在这个服务器的项目生效。
2. 在tomcat的 conf/Catalina/localhost/context.xml 里面配置
对localhost生效。
3. 在自己的web工程项目中的 META-INF/context.xml 里面配置
只对当前的工程生效。
具体配置信息如下:
maxIdleSwap : 1分钟不用就钝化。
directory : 钝化后的那个文件存放的目录位置。
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="D:/Passivate"/>
</Manager>
</Context>
如何创建:
package com.caochenlei.servlet.demo;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
public class MySessionActivationListener implements HttpSessionActivationListener {
@Override
public void sessionWillPassivate(HttpSessionEvent httpSessionEvent) {
System.out.println("sessionWillPassivate ...");
}
@Override
public void sessionDidActivate(HttpSessionEvent httpSessionEvent) {
System.out.println("sessionDidActivate ...");
}
}
如何配置:
这一类监听器不用注册。
6.1、Filter 概述
Filter 是过滤器,就是对客户端发出来的请求进行过滤。浏览器发出请求,然后服务器派 servlet 处理。在中间就可以过滤,其实过滤器起到的是拦截的作用。使用过滤器可以对一些敏感词汇进行过滤、统一设置编码、实现自动登录等功能。
6.2、Filter 生命周期
- 创建:在服务器启动的时候就创建。
- 销毁:在服务器停止的时候就销毁。
6.3、Filter 语法
如何定义:
package com.caochenlei.servlet.demo;
import javax.servlet.*;
import java.io.IOException;
public class MyFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
System.out.println("MyFilter init ...");
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws ServletException, IOException {
chain.doFilter(req, resp);
}
public void destroy() {
System.out.println("MyFilter destroy ...");
}
}
如何配置:
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.caochenlei.servlet.demo.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
6.4、Filter 执行顺序
- 客户端发出请求,先经过过滤器,如果过滤器放行,那么才能到 servlet。
- 如果有多个过滤器,那么他们会按照注册的映射顺序来排队。只要有一个过滤器不放行,那么后面排队的过滤器以及咱们的 servlet 都不会收到请求。如果全部放行了,那么回来的时候将会是反向执行,比如以下顺序:
filter01 ...
filter02 ...
filter03 ...
filter03 ...
filter02 ...
filter01 ...
注意:init 方法的参数 FilterConfig , 可以用于获取 Filter 在注册的名字以及初始化参数,其实这里的设计的初衷与 ServletConfig 是一样的。
6.5、Filter 匹配规则
- 全路径匹配:
/a - 前半段匹配:
/a/b/c/* - 扩展名匹配:
*.action
6.6、Filter 拦截类型
注意:针对 dispatcher 设置的选项。
- REQUEST : 只要是请求过来都拦截,默认就是 REQUEST。
- FORWARD : 只要是转发过来都拦截。
- ERROR : 页面出错发生跳转。
- INCLUDE : 包含页面的时候就拦截。
6.7、Filter 统一编码
package com.caochenlei.servlet.demo;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
chain.doFilter(new MyRequest(request), response);
}
@Override
public void destroy() {
}
}
class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
private boolean flag = true;
public MyRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public String getParameter(String name) {
if (name == null || name.trim().length() == 0) {
return null;
}
String[] values = getParameterValues(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
@Override
public String[] getParameterValues(String name) {
if (name == null || name.trim().length() == 0) {
return null;
}
Map<String, String[]> map = getParameterMap();
if (map == null || map.size() == 0) {
return null;
}
return map.get(name);
}
@Override
public Map<String, String[]> getParameterMap() {
String method = request.getMethod();
if ("post".equalsIgnoreCase(method)) {
try {
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else if ("get".equalsIgnoreCase(method)) {
Map<String, String[]> map = request.getParameterMap();
if (flag) {
for (String key : map.keySet()) {
String[] arr = map.get(key);
for (int i = 0; i < arr.length; i++) {
try {
arr[i] = new String(arr[i].getBytes("utf-8"), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
flag = false;
}
return map;
}
return super.getParameterMap();
}
}
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.caochenlei.servlet.demo.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
7.1、JSP 概述
JSP(全称 Java Server Pages)是由 Sun Microsystems 公司倡导和许多公司参与共同创建的一种使软件开发者可以响应客户端请求,而动态生成 HTML、XML 或其他格式文档的 Web 网页的技术标准。从用户角度看待,就是是一个网页,从程序员角度看待,其实是一个 Java 类,它继承了 Servlet,所以可以直接说 JSP 就是一个 Servlet。
那为什么会有 JSP?
HTML 多数情况下用来显示一成不变的静态内容,但是有时候我们需要在网页上显示一些动态数据,比如:查询所有的学生信息、根据姓名去查询具体某个学生,这些动作都需要去查询数据库,然后在网页上显示,HTML 是不支持写 Java 代码 ,JSP 里面可以写 Java 代码。
7.2、JSP 生命周期
JSP 生命周期就是从创建到销毁的整个过程,类似于 Servlet 生命周期,区别在于 JSP 生命周期还包括将 JSP 文件编译成 Servlet。
以下是 JSP 生命周期中所走过的几个阶段:
- 编译阶段:Servlet 容器编译 Servlet 源文件生成 Servlet 类。
- 初始化阶段:加载与 JSP 对应的 Servlet 类,创建其实例并调用它的初始化方法。
- 执行阶段:调用与 JSP 对应的 Servlet 实例的服务方法。
- 销毁阶段:调用与 JSP 对应的 Servlet 实例的销毁方法销毁 Servlet 实例。
7.3、JSP 语法
7.3.1、JSP 脚本程序
第一种格式:
格式:
<% Java代码片段 %>
示例:
<% System.out.println("Hello"); %>
第二种格式:
格式:
<jsp:scriptlet>
Java代码片段
</jsp:scriptlet>
示例:
<jsp:scriptlet>
System.out.println("Hello");
</jsp:scriptlet>
7.3.2、JSP 变量声明
第一种格式:
格式:
<%! 变量声明 %>
示例:
<%! int a = 10; int b = 20; %>
第二种格式:
格式:
<jsp:declaration>
变量声明
</jsp:declaration>
示例:
<jsp:declaration>
int c = 30;
int d = 40;
</jsp:declaration>
7.3.3、JSP 表达式
一个 JSP 表达式中包含的脚本语言表达式,先被转化成 String,然后插入到表达式出现的地方。
由于表达式的值会被转化成 String,所以您可以在一个文本行中使用表达式而不用去管它是否是 HTML 标签。
表达式元素中可以包含任何符合 Java 语言规范的表达式,但是不能使用分号来结束表达式。
第一种格式:
格式:
<%= 表达式 %>
示例:
<%= a %>
第二种格式:
格式:
<jsp:expression>
表达式
</jsp:expression>
示例:
<jsp:expression>
a
</jsp:expression>
7.3.4、JSP 注释
不同情况下使用注释的语法规则:
| 语法 | 描述 |
|---|---|
| <%-- 注释 --%> | JSP 注释,注释内容不会被发送至浏览器甚至不会被编译 |
| HTML 注释,通过浏览器查看网页源代码时可以看见注释内容 | |
| <% | 代表静态 <% 常量 |
| %> | 代表静态 %> 常量 |
| \’ | 在属性中使用的单引号 |
| " | 在属性中使用的双引号 |
7.3.5、JSP 指令元素
JSP 指令用来设置整个 JSP 页面相关的属性,如网页的编码方式和脚本语言。
语法格式:
<%@ 指令 属性="值" %>
三种指令:
| 指令 | 描述 |
|---|---|
| <%@ page … %> | 定义网页依赖属性,比如脚本语言、error 页面、缓存需求等等 |
| <%@ include … %> | 包含其他文件 |
| <%@ taglib … %> | 引入标签库的定义 |
7.3.5.1、page 指令
page 指令为容器提供当前页面的使用说明,一个 JSP 页面可以包含多个 page 指令。
第一种格式:
<%@ page 属性="值" %>
第二种格式:
<jsp:directive.page 属性="值" />
属性列表:
| 属性 | 描述 |
|---|---|
| buffer | 指定 out 对象使用缓冲区的大小 |
| autoFlush | 控制 out 对象的缓存区 |
| contentType | 指定当前 JSP 页面的 MIME 类型和字符编码 |
| errorPage | 指定当 JSP 页面发生异常时需要转向的错误处理页面 |
| isErrorPage | 指定当前页面是否可以作为另一个 JSP 页面的错误处理页面 |
| extends | 指定 servlet 从哪一个类继承 |
| import | 导入要使用的 Java 类 |
| info | 定义 JSP 页面的描述信息 |
| isThreadSafe | 指定对 JSP 页面的访问是否为线程安全 |
| language | 定义 JSP 页面所用的脚本语言,默认是 Java |
| session | 指定 JSP 页面是否使用 session |
| isELIgnored | 指定是否执行 EL 表达式 |
| isScriptingEnabled | 确定脚本元素能否被使用 |
7.3.5.2、include 指令
JSP 可以通过 include 指令来包含其他文件,被包含的文件可以是 JSP 文件、HTML 文件或文本文件,包含的文件就好像是该 JSP 文件的一部分,会被同时编译执行。
第一种格式:
<%@ include file="文件相对url地址" %>
第二种格式:
<jsp:directive.include file="文件相对url地址" />
7.3.5.3、taglib 指令
JSP 允许用户自定义标签,一个自定义标签库就是自定义标签的集合,taglib 指令引入一个自定义标签集合的定义,包括库路径、自定义标签。
第一种格式:
<%@ taglib uri="uri" prefix="标签前缀" %>
第二种格式:
<jsp:directive.taglib uri="uri" prefix="标签前缀" />
7.3.6、JSP 动作元素
与 JSP 指令元素不同的是,JSP 动作元素在请求处理阶段起作用,利用 JSP 动作可以动态地插入文件、重用 JavaBean 组件、把用户重定向到另外的页面、为 Java 插件生成 HTML 代码。
语法格式:
<jsp:动作名称 属性="值" />
常见动作:
| 语法 | 描述 |
|---|---|
| jsp:include | 在页面被请求的时候引入一个文件。 |
| jsp:useBean | 寻找或者实例化一个 JavaBean。 |
| jsp:setProperty | 设置 JavaBean 的属性。 |
| jsp:getProperty | 输出某个 JavaBean 的属性。 |
| jsp:forward | 把请求转到一个新的页面。 |
常见属性:
| 语法 | 描述 |
|---|---|
| id | id 属性是动作元素的唯一标识,可以在 JSP 页面中引用。 动作元素创建的 id 值可以通过 PageContext 来调用。 |
| scope | 该属性用于识别动作元素的生命周期。 id 属性和 scope 属性有直接关系,scope 属性定义了相关联 id 对象的寿命。 scope 属性有四个可能的值:page、request、session 和 application。 |
7.3.6.1、jsp:include 动作
jsp:include 动作元素用来包含静态和动态的文件,该动作把指定文件插入正在生成的页面。
语法格式:
注意:前面已经介绍过 include 指令,它是在 JSP 文件被转换成 Servlet 的时候引入文件,而这里的 jsp:include 动作不同,插入文件的时间是在页面被请求的时候。
<jsp:include page="相对URL地址" flush="true" />
属性列表:
| 属性 | 描述 |
|---|---|
| page | 包含在页面中的相对 URL 地址。 |
| flush | 布尔属性,定义在包含资源前是否刷新缓存区。 |
相关示例:
<jsp:include page="myInfo.html" flush="true" />
7.3.6.2、jsp:useBean 动作
jsp:useBean 动作用来加载一个将在 JSP 页面中使用的 JavaBean,这个功能非常有用,因为它使得我们可以发挥 Java 组件复用的优势。
语法格式:
<jsp:useBean id="ID名称" class="具体的类" />
属性列表:
| 属性 | 描述 |
|---|---|
| class | 指定 Bean 的完整包名。 |
| type | 指定将引用该对象变量的类型。 |
| beanName | 通过 java.beans.Beans 的 instantiate() 方法指定 Bean 的名字。 |
相关示例:
<jsp:useBean id="user" class="com.caochenlei.servlet.demo.User" />
7.3.6.3、jsp:setProperty 动作
jsp:setProperty 动作用来设置已经实例化的 Bean 对象的属性。
语法格式:
注意:jsp:setProperty 只有在新建 Bean 实例时才会执行,如果是使用现有实例则不执行 jsp:setProperty。
第一种格式:
<jsp:useBean id="myName" class="..." />
<jsp:setProperty name="myName" property="属性名" value="值"/>
第二种格式:
<jsp:useBean id="myName" class="...">
<jsp:setProperty name="myName" property="属性名" value="值"/>
</jsp:useBean>
属性列表:
| 属性 | 描述 |
|---|---|
| name | name 属性是必需的。它表示要设置属性的是哪个 Bean。 |
| property | property 属性是必需的。它表示要设置哪个属性。有一个特殊用法:如果 property 的值是 "*",表示所有名字和 Bean 属性名字匹配的请求参数都将被传递给相应的属性 set 方法。 |
| value | value 属性是可选的。该属性用来指定 Bean 属性的值。字符串数据会在目标类中通过标准的 valueOf 方法自动转换成数字、boolean、Boolean、 byte、Byte、char、Character。例如,boolean 和 Boolean 类型的属性值(比如 "true")通过 Boolean.valueOf 转换,int 和 Integer 类型的属性值(比如 "42")通过 Integer.valueOf 转换。value 和 param 不能同时使用,但可以使用其中任意一个。 |
| param | param 是可选的。它指定用哪个请求参数作为 Bean 属性的值。如果当前请求没有参数,则什么事情也不做,系统不会把 null 传递给 Bean 属性的 set 方法。因此,你可以让 Bean 自己提供默认属性值,只有当请求参数明确指定了新值时才修改默认属性值。 |
相关示例:
第一种格式:
<jsp:useBean id="user1" class="com.caochenlei.servlet.demo.User" />
<jsp:setProperty name="user1" property="username" value="zhangsan"/>
第二种格式:
<jsp:useBean id="user2" class="com.caochenlei.servlet.demo.User">
<jsp:setProperty name="user2" property="username" value="lisi"/>
</jsp:useBean>
7.3.6.4、jsp:getProperty 动作
jsp:getProperty 动作提取指定 Bean 属性的值,转换成字符串,然后输出。
语法格式:
<jsp:getProperty name="myName" property="属性值" />
属性列表:
| 属性 | 描述 |
|---|---|
| name | 要检索的 Bean 属性名称,Bean 必须已定义。 |
| property | 表示要提取 Bean 属性的值。 |
相关示例:
<jsp:getProperty name="user2" property="username" />
7.3.6.5、jsp:forward 动作
jsp:forward 动作把请求转到另外的页面。
语法格式:
<jsp:forward page="相对URL地址" />
属性列表:
| 属性 | 描述 |
|---|---|
| page | page 属性包含的是一个相对 URL。 page 的值既可以直接给出,也可以在请求的时候动态计算,可以是一个 JSP 页面或者一个 Java Servlet。 |
相关示例:
<jsp:forward page="myJSP.jsp" />
7.3.7、JSP 隐含对象
JSP 隐式对象是 JSP 容器为每个页面提供的 Java 对象,开发者可以直接使用它们而不用显式声明,JSP 隐式对象也被称为预定义变量。
JSP 所支持的九大隐式对象:
| 对象 | 描述 |
|---|---|
| request | HttpServletRequest 接口的实例 |
| response | HttpServletResponse 接口的实例 |
| session | HttpSession 类的实例 |
| application | ServletContext 类的实例,与应用上下文有关 |
| config | ServletConfig 类的实例 |
| out | JspWriter 类的实例,用于把结果输出至网页上 |
| pageContext | PageContext 类的实例,提供对 JSP 页面所有对象以及命名空间的访问 |
| page | 类似于 Java 类中的 this 关键字 |
| Exception | Exception 类的对象,代表发生错误的 JSP 页面中对应的异常对象 |
JSP 所支持的四大作用域:
- pageContext 【PageContext】
作用域仅限于当前的页面,还可以获取到其他八个内置对象。
- request 【HttpServletRequest】
作用域仅限于一次请求, 只要服务器对该请求做出了响应,这个域中存的值就没有了。
- session 【HttpSession】
作用域限于一次会话(多次请求与响应) 当中。
- application 【ServletContext】
整个工程都可以访问,服务器关闭后就不能访问了
7.3.8、JSP 常见控制
if 语句:
<%! int age = 19; %>
<% if ( age > 18 ) { %>
<p>已成年</p><br />
<% } else { %>
<p>未成年</p><br />
<% } %>
for 语句:
<%--for语句--%>
<%! int fontSize1 = 1; %>
<% for (fontSize1 = 1; fontSize1 <= 3; fontSize1++){ %>
<font color="green" size="<%= fontSize1 %>">fontSize1</font><br />
<% } %>
7.4、EL 表达式
7.4.1、EL 概述
EL 是为了简化咱们的 jsp 代码,具体一点就是为了简化在 jsp 里面写的那些 java 代码。
7.4.2、EL 语法
注意:如果从作用域中取值,会先从小的作用域开始取,如果没有,就往下一个作用域取,一直把四个作用域取完都没有,就没有显示。
${ 表达式 }
7.4.3、EL 隐含对象
EL 表达式的 11 个内置对象:
| 隐含对象 | 描述 |
|---|---|
| pageScope | page 作用域 |
| requestScope | request 作用域 |
| sessionScope | session 作用域 |
| applicationScope | application 作用域 |
| param | Request 对象的参数,字符串 |
| paramValues | Request 对象的参数,字符串集合 |
| header | HTTP 信息头,字符串 |
| headerValues | HTTP 信息头,字符串集合 |
| initParam | 上下文初始化参数 |
| cookie | Cookie 值 |
| pageContext | 当前页面的 pageContext |
注意:您可以在表达式中使用这些对象,就像使用变量一样。
7.4.4、EL 案例演示
如果域中所存的是对象:
<%
User u = new User();
u.setUsername("zhangsan");
u.setPassword("123456");
pageContext.setAttribute("u", u);
request.setAttribute("u", u);
session.setAttribute("u", u);
application.setAttribute("u", u);
%>
<br>使用普通手段取出作用域中的值<br>
<%= ((User) pageContext.getAttribute("u")).getUsername() %>
<%= ((User) request.getAttribute("u")).getUsername() %>
<%= ((User) session.getAttribute("u")).getUsername() %>
<%= ((User) application.getAttribute("u")).getUsername() %>
<br>使用EL表达式取出作用域中的值<br>
<p>${ pageScope.u.username }</p>
<p>${ requestScope.u.username }</p>
<p>${ sessionScope.u.username }</p>
<p>${ applicationScope.u.username }</p>
如果域中所存的是键值:
<%
pageContext.setAttribute("name", "page");
request.setAttribute("name", "request");
session.setAttribute("name", "session");
application.setAttribute("name", "application");
%>
<br>使用普通手段取出作用域中的值<br>
<%= pageContext.getAttribute("name") %>
<%= request.getAttribute("name") %>
<%= session.getAttribute("name") %>
<%= application.getAttribute("name") %>
<br>使用EL表达式取出作用域中的值<br>
${ pageScope.name }
${ requestScope.name }
${ sessionScope.name }
${ applicationScope.name }
如果域中所存的是数组:
<%
String[] array = {"aa","bb","cc","dd"};
pageContext.setAttribute("array", array);
request.setAttribute("array", array);
session.setAttribute("array", array);
application.setAttribute("array", array);
%>
<br>使用普通手段取出作用域中的值<br>
<p><%= ((String[]) pageContext.getAttribute("array"))[0] %></p>
<p><%= ((String[]) request.getAttribute("array"))[0] %></p>
<p><%= ((String[]) session.getAttribute("array"))[0] %></p>
<p><%= ((String[]) application.getAttribute("array"))[0] %></p>
<br>使用EL表达式取出作用域中的值<br>
<p>${ pageScope.array[0] }</p>
<p>${ requestScope.array[0] }</p>
<p>${ sessionScope.array[0] }</p>
<p>${ applicationScope.array[0] }</p>
如果域中锁存的是集合:
<%
Map map = new HashMap();
map.put("name", "zhangsan");
map.put("age",18);
pageContext.setAttribute("map", map);
request.setAttribute("map", map);
session.setAttribute("map", map);
application.setAttribute("map", map);
%>
<br>使用普通手段取出作用域中的值<br>
<p><%= ((Map) pageContext.getAttribute("map")).get("name") %></p>
<p><%= ((Map) request.getAttribute("map")).get("name") %></p>
<p><%= ((Map) session.getAttribute("map")).get("name") %></p>
<p><%= ((Map) application.getAttribute("map")).get("name") %></p>
<br>使用EL表达式取出作用域中的值<br>
<p>${ pageScope.map.name }</p>
<p>${ requestScope.map.name }</p>
<p>${ sessionScope.map['name'] }</p>
<p>${ applicationScope.map['name'] }</p>
7.5、JSTL 表达式
7.5.1、JSTL 概述
JSTL(JSP Standard Tag Library,标准标签库)主要也是为了简化 jsp 的代码编写,替换 <%%> 写法,一般与 EL 表达式配合使用。
7.5.2、JSTL 依赖
- 导入依赖库:jstl.jar、standard.jar
- 引入标签库:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
7.5.3、JSTL 常用标签
- c:set
<!-- 声明一个对象 myname,对象的值 zhangsan,存储到了 page(默认),指定是 session 域 -->
<c:set var="myname" value="zhangsan" scope="session"></c:set>
${ sessionScope.myname }
- c:if
<c:set var="age" value="18" ></c:set>
<c:if test="${ age > 26 }">
年龄大于了26岁...
</c:if>
<c:if test="${ age <= 26 }">
年龄小于了26岁...
</c:if>
<%--定义一个变量名 flag,去接收前面表达式的值,然后存在 session 域中--%>
<c:if test="${ age > 26 }" var="flag" scope="session">
年龄大于了26岁...
</c:if>
- c:forEach
<%--从1开始遍历到10,得到的结果,赋值给 i,并且会存储到page域中,step代表增幅为2--%>
<c:forEach begin="1" end="10" var="i" step="2">
${i}
</c:forEach>
<%
List<User> list = new ArrayList<User>();
User u1 = new User();
u1.setUsername("zhangsan");
u1.setPassword("123456");
list.add(u1);
User u2 = new User();
u2.setUsername("lisi");
u2.setPassword("123456");
list.add(u2);
request.setAttribute("list",list);
%>
<!--items:表示遍历哪一个对象,注意这里必须写EL表达式。
var:遍历出来的每一个元素用user去接收。 -->
<c:forEach var="user" items="${ list }">
${ user.username } ---- ${ user.password } <br />
</c:forEach>
Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布。该版本在前一版本(Servlet 2.5)的基础上提供了若干新特性用于简化 Web 应用的开发和部署。其中有几项特性的引入让开发者感到非常兴奋,同时也获得了 Java 社区的一片赞誉之声:
- 新增的注解支持:该版本新增了若干注解,用于简化 Servlet、过滤器(Filter)和监听器(Listener)的声明,这使得 web.xml 部署描述文件从该版本开始不再是必选的了。
- 文件上传 API 简化:从该版本开始,极大地简化了文件上传的操作。
- 异步处理支持:有了该特性,Servlet 线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该 Servlet 线程。在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。
- 动态注册组件:在初始化 ServletContext 容器的时候,可以支持动态注册三大组件。
- 可插性支持:如果说 3.0 版本新增的注解支持是为了简化 Servlet/ 过滤器 / 监听器的声明,从而使得 web.xml 变为可选配置, 那么新增的可插性 (pluggability) 支持则将 Servlet 配置的灵活性提升到了新的高度。熟悉 Struts2 的开发者都知道,Struts2 通过插件的形式提供了对包括 Spring 在内的各种开发框架的支持,开发者甚至可以自己为 Struts2 开发插件,而 Servlet 的可插性支持正是基于这样的理念而产生的。使用该特性,现在我们可以在不修改已有 Web 应用的前提下,只需将按照一定格式打成的 JAR 包放到 WEB-INF/lib 目录下,即可实现新功能的扩充,不需要额外的配置。
8.1、注解开发
8.1.1、servlet 注解
如何创建:
package com.caochenlei.servlet3.annotation;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(
name = "AnnotationServlet",
value = {"/AnnotationServlet"},
loadOnStartup = 2,
initParams = {@WebInitParam(name = "user",value = "zhangsan")},
asyncSupported = false
)
public class AnnotationServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("doPost ...");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("doGet ...");
}
}
属性列表:
| 属性名 | 类型 | 描述 |
|---|---|---|
| name | String | 指定 Servlet 的 name 属性,如果没有显式指定,则该 Servlet 的取值即为类的全限定名 |
| value | String[] | 该属性等价于 urlPatterns 属性,两个属性不能同时使用 |
| urlPatterns | String[] | 指定一组 Servlet 的 URL 匹配模式 |
| loadOnStartup | int | 指定 Servlet 的加载顺序 |
| initParams | WebInitParam[] | 指定一组 Servlet 初始化参数 |
| asyncSupported | boolean | 声明 Servlet 是否支持异步操作模式 |
| description | String | 该 Servlet 的描述信息 |
| displayName | String | 该 Servlet 的显示名,通常配合工具使用 |
如何测试:
打开浏览器输入:http://localhost:8080/servlet3_0_war_exploded/AnnotationServlet
检测控制台输出:
8.1.2、filter 注解
如何创建:
package com.caochenlei.servlet3.annotation;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import java.io.IOException;
@WebFilter(
filterName = "AnnotationFilter",
value = {"/*"},
dispatcherTypes = {DispatcherType.REQUEST},//代表filter拦截类型
initParams = {@WebInitParam(name = "user", value = "zhansan")},//代表filter初始化参数,可以写多个
asyncSupported = false,//代表filter是否支持异步,默认为false
servletNames = {"AnnotationServlet"}//代表filter指定拦截哪几个servlet
)
public class AnnotationFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws ServletException, IOException {
System.out.println("doFilter ...");
chain.doFilter(req, resp);
}
public void destroy() {
}
}
属性列表:
| 属性名 | 类型 | 描述 |
|---|---|---|
| filterName | String | 指定过滤器的 name 属性 |
| value | String[] | 该属性等价于 urlPatterns 属性,但是两者不应该同时使用 |
| urlPatterns | String[] | 指定一组过滤器的 URL 匹配模式 |
| servletNames | String[] | 指定过滤器将应用于哪些 Servlet |
| dispatcherTypes | DispatcherType | 指定过滤器的转发模式 具体取值包括: ASYNC、ERROR、FORWARD、INCLUDE、REQUEST |
| initParams | WebInitParam[] | 指定一组过滤器初始化参数 |
| asyncSupported | boolean | 声明过滤器是否支持异步操作模式 |
| description | String | 该过滤器的描述信息 |
| displayName | String | 该过滤器的显示名,通常配合工具使用 |
如何测试:
打开浏览器输入:http://localhost:8080/servlet3_0_war_exploded/AnnotationServlet
检测控制台输出:
8.1.3、listener 注解
如何创建:
package com.caochenlei.servlet3.annotation;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener()
public class AnnotationListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("contextInitialized ...");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("contextDestroyed ...");
}
}
如何测试:
重启服务器,观察控制台:
8.1.4、两种配置同时存在
- 对于 servlet 来说:
- 若两种配置方式的 url-pattern 值相同,则应用无法启动。
- 若两种配置方式的 url-pattern 值相同,那么相当该 servlet 具有两个映射 url-pattern。
- 对于 filter 来说:
- 无论两种配置方式的 url-pattern 值是否相同,其都是作为独立的 filter 出现的。
- 对于 listener 来说:
- 如果两种配置方式都进行了同一个 listener 注册,那么也只能算一个 listener。
8.1.5、如何禁用注解组件
如果只想要使用 web.xml 中的配置而忽略注解注册的组件,只需要在 web.xml 跟标签添加一个属性即可。
<?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_4_0.xsd"
version="4.0" metadata-complete="false">
</web-app>
metadata-complete="false":表示 web.xml 配置和注解配置同时生效,默认是 false。
metadata-complete="true":表示 web.xml 配置有效,而注解配置则被忽略。
8.2、文件上传
前台页面:
<form action="uploadServlet" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="myfile" /> <br />
上传文件:<input type="submit" value="上传" />
</form>
上传模块:
package com.caochenlei.servlet3.annotation;
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;
import java.io.IOException;
@WebServlet("/uploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Part part = request.getPart("myfile");
part.write("D:/xxx.txt");
response.setHeader("Content-Type", "text/html;charset=UTF-8");
response.getWriter().println("文件上传成功!");
}
}
@MultipartConfig 注解:
| 属性名 | 类型 | 是否可选 | 描述 |
|---|---|---|---|
| fileSizeThreshold | int | 是 | 当数据量大于该值时,内容将被写入文件 |
| location | String | 是 | 存放生成的文件地址 |
| maxFileSize | long | 是 | 允许上传的文件最大值。默认值为 - 1,表示没有限制 |
| maxRequestSize | long | 是 | 针对该 multipart/form-data 请求的最大数量,默认值为 - 1,表示没有限制 |
如何测试:
8.3、异步处理
注意问题:
如果你的 servlet 开启了异步支持,那么你的 filter 也必须开启异步支持,否则会报错!
经典场景:
在用户注册的时候,通常会发送一封注册通知邮件,这里就是用到了异步处理的技术。
如何实现:
第一步:修改 AnnotationFilter 的 asyncSupported 为 true
第二步:创建异步支持的注册 servlet
package com.caochenlei.servlet3.annotation;
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;
import java.io.IOException;
@WebServlet(value = "/RegisterServlet",asyncSupported = true)
public class RegisterServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
AsyncContext asyncContext = request.startAsync();
EmailSendThread est = new EmailSendThread(asyncContext);
asyncContext.setTimeout(3000);
asyncContext.start(est);
response.setHeader("Content-Type", "text/html;charset=UTF-8");
response.getWriter().println("恭喜您注册成功,请检查您的邮箱进行激活!");
}
}
第三步:创建发送邮件的子进程类
package com.caochenlei.servlet3.annotation;
import javax.servlet.AsyncContext;
public class EmailSendThread implements Runnable {
private AsyncContext ac;
public EmailSendThread(AsyncContext ac) {
this.ac = ac;
}
public AsyncContext getAc() {
return ac;
}
public void setAc(AsyncContext ac) {
this.ac = ac;
}
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("确认邮件已经发送,请及时查收!");
}
}
如何测试:
打开浏览器输入:http://localhost:8080/servlet3_0_war_exploded/RegisterServlet
检测控制台输出:
三秒后页面输出:
十秒后控制台输出:
8.4、动态注册
动态注册就是在 tomcat 启动的时候,利用 ServletContext 进行组件动态注册的技术。
修改 AnnotationListener,以后三大组建的配置都是在 contextInitialized 方法中进行。
package com.caochenlei.servlet3.annotation;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.WebListener;
@WebListener()
public class AnnotationListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("contextInitialized ...");
ServletContext servletContext = servletContextEvent.getServletContext();
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("contextDestroyed ...");
}
}
8.4.1、servlet 动态注册
创建一个普通的 servlet:
package com.caochenlei.servlet3.annotation;
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 NormalServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("NormalServlet doPost ...");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("NormalServlet doGet ...");
}
}
利用 ServletContextListener 进行动态注册:
String servletName = "NormalServlet";
String servletClass = "com.caochenlei.servlet3.annotation.NormalServlet";
ServletRegistration.Dynamic srd = servletContext.addServlet(servletName, servletClass);
srd.addMapping("/NormalServlet");
如何测试:
打开浏览器输入:http://localhost:8080/servlet3_0_war_exploded/NormalServlet
检测控制台输出:
8.4.2、filter 动态注册
创建一个普通的 filter:
package com.caochenlei.servlet3.annotation;
import javax.servlet.*;
import java.io.IOException;
public class NormalFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws ServletException, IOException {
chain.doFilter(req, resp);
System.out.println("NormalFilter doFilter ...");
}
public void destroy() {
}
}
利用 ServletContextListener 进行动态注册:
注意:addMappingForServletNames 的第二个参数为 true 代表,在以前的过滤之后过滤,为 false,代表在以前的过滤之前过滤。
String filterName = "NormalFilter";
String filterClass = "com.caochenlei.servlet3.annotation.NormalFilter";
FilterRegistration.Dynamic frd = servletContext.addFilter(filterName, filterClass);
frd.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/a");
如何测试:
打开浏览器输入:http://localhost:8080/servlet3_0_war_exploded/a
检测控制台输出:
8.4.3、listener 动态注册
创建一个普通的 listener:
package com.caochenlei.servlet3.annotation;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
public class NormalListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("NormalListener requestInitialized ...");
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println("NormalListener requestDestroyed ...");
}
}
利用 ServletContextListener 进行动态注册:
servletContext.addListener("com.caochenlei.servlet3.annotation.NormalListener");
如何测试:
打开浏览器输入:http://localhost:8080/servlet3_0_war_exploded/index.jsp
检测控制台输出:
8.5、可插性支持
如何实现模块化开发?
- 编写一个类继承自 HttpServlet,并且在该类上使用 @WebServlet 注解将该类声明为 Servlet,将该类放在 classes 目录下的对应包结构中,无需修改 web.xml 文件。
- 编写一个类继承自 HttpServlet,将该类打成 JAR 包,并且在 JAR 包的 META-INF 目录下放置一个 web-fragment.xml 文件,该文件中声明了相应的 Servlet 配置。web-fragment.xml 文件示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd">
</web-fragment>
从上面的示例可以看出,web-fragment.xml 与 web.xml 除了在头部声明的 XSD 引用不同之外,其主体配置与 web.xml 是完全一致的。
由于一个 Web 应用中可以出现多个 web-fragment.xml 声明文件,加上一个 web.xml 文件,加载顺序问题便成了不得不面对的问题。Servlet 规范的专家组在设计的时候已经考虑到了这个问题,并定义了加载顺序的规则。
web-fragment.xml 包含了两个可选的顶层标签, 如果希望为当前的文件指定明确的加载顺序,通常需要使用这两个标签, 主要用于标识当前的文件,而 则用于指定先后顺序。一个简单的示例如下:
<web-fragment...>
<name>FragmentA</name>
<ordering>
<after>
<name>FragmentB</name>
<name>FragmentC</name>
</after>
<before>
<others/>
</before>
</ordering>
...
</web-fragment>
接下来我们使用一个完整示例来演示:
我们先看下我们目前已经做了哪些工程:
创建一个 JavaWeb 片段工程,可以算是一个功能模块,具体步骤如下图:
把以下代码拷贝到配置文件中:
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd">
</web-fragment>
创建一个需要测试的 Servlet,这里我们直接采用注解开发
接下来我们需要把当前这个工程编译为一个 jar 包,以方便嵌入到别的工程中
编译完成,就会出现这个 jar 包,把它拷贝到桌面,备用,然后关闭当前工程
打开之前的 servlet3.0 的项目,我们把刚才编译好的 jar 包放到这个工程中,用来测试是不是可行
把刚才复制到桌子上的 fragment.jar 复制到 lib 中
启动服务器,然后输入 FragmentServlet 的映射网址,看看控制台会不会输出
打开浏览器,输入:http://localhost:8080/servlet3_0_war_exploded/FragmentServlet