目录
1. 概念
2. Servlet接口、GenericServlet抽象类、 HttpServlet抽象类
3. Servlet映射配置
4. 会话
5. 表单
6. 线程安全问题
7. 网络
1. 概念
- Servlet简介
Servlet(实现了Servlet接口的普通Java类)是sun公司提供的用于开发动态web资源的技术。
===》1. 首先需要在项目中创建Servlet类(一个动态网页对应一个Servlet类)
创建Servlet类的3种方式:
1. 直接实现 javax.servlet.Servlet 接口,重写其全部方法。
所有的Servlet类都直接或间接实现了该接口。
使用不方便(基本不使用该方式)。
2. 继承 javax.servlet.GenericServlet 抽象类(实现了Servlet接口),重写 service() 方法。
必须手动分析和封装HTTP协议的请求信息和响应信息。
3. 继承 javax.servlet.http.HttpServlet 抽象类(继承自GenericServlet),重写 doGet() 或 doPost() 方法 或 其他doXXX()方法。
添加了HTTP协议处理方法(通常情况下都会使用该方式)。
创建Servlet类后需要为该Servlet类关联一个URL访问路径供用户访问,2种方式:
1. 在web.xml配置文件中进行配置。
2. 使用注解。
===》2. 用户访问动态网页
用户在浏览器中输入该访问路径(访问动态网页)后会向服务器发送http消息,服务器就会收到该Servlet请求。
===》3. 服务器收到客户端的Servlet请求后的流程
①检查是否已创建该Servlet实例对象(根据web.xml文件中配置的URL映射找到对应的Servlet类)。如果是,则直接执行第③步,否则 执行第②步。
②创建该Servlet实例对象,并调用init()方法。
③创建一个HttpServletRequest请求对象(用于封装HTTP请求消息:存储客户端提交的数据)和一个HttpServletResponse响应对象(用于封装HTTP响应消息:存储向客户端返回的数据),然后调用Servlet的service()方法(将2个对象作为参数,在service()方法中会使用到请求对象中的数据,并向响应对象写入数据)。
④服务器读取响应对象,并向浏览器发送http响应消息,浏览器收到响应消息后(服务器会销毁HttpServletRequest和HttpServletResponse对象)会在网页中展示响应内容。
⑤Web服务器关闭或重新启动或Web应用被移除时,Servlet容器会销毁所有Servlet(卸载前会调用destroy()方法)。
可以看出:
1. Servlet生命周期(从创建到销毁的过程)分为3个阶段:
1. 初始化阶段
1. 加载并创建Servlet实例对象。
当Servlet容器启动或首次请求某个Servlet时,Servlet容器会读取web.xml配置文件或@WebServlet注解中的配置信息,对指定的Servlet进行加载。加载成功后,容器会通过反射创建Servlet实例化对象。
注意:因为Servlet容器是通过Java的反射API来创建Servlet实例的,需要调用Servlet的默认构造方法(无参构造方法),所以在编写Servlet类时,不能只提供带参数的构造方法。
2. 调用init方法进行初始化。
加载和创建Servlet实例化对象后,Servlet容器会调用init方法初始化Servlet实例对象(让Servlet实例在处理请求之前完成一些初始化工作,如:建立数据库连接、获取配置信息等)。
Servlet实例对象可以通过ServletConfig对象(作为init方法的参数传入)获取在web.xml或@WebServlet中配置的初始化参数。
2. 运行时阶段(最重要的阶段)
Servlet容器接收到来自客户端请求时,会针对该请求分别创建一个ServletRequst对象(封装了客户端的相关信息和请求信息)和ServletResponse对象(封装了返回给客户端的响应信息) 作为参数传入service方法并调用该方法对请求进行处理。
当Servlet容器将响应信息返回给客户端后,ServletRequst 对象和ServletResponse对象就会被销毁。
3. 销毁阶段
当Servlet容器关闭或重启时,会调用destory方法(释放Servlet实例对象使用的资源,如:关闭数据库连接、关闭文件的输入流和输出流等,随后该Servlet实例对象会被Java的垃圾收集器所回收)。
2. Servlet生命周期由Servlet容器管理(Servlet不能独立运行,它的运行完全由Servlet容器来管理,Servlet容器会在Servlet生命周期的不同阶段调用相应的方法)。
3. init方法和destory方法都只会被调用一次。Servlet实例对象创建后直至web容器销毁时(即 WEB应用被停止或重新启动时)才会被销毁。
4. service方法在每一次请求时都会调用,并将新建的请求对象和响应对象作为参数传入该方法。
2. Servlet接口、GenericServlet抽象类、 HttpServlet抽象类
- Servlet接口
定义了5个方法:
1. void init(ServletConfig config)
用于在创建Servlet实例对象后 进行初始化(由Servlet容器调用)。
只会被调用一次。
2. void service(ServletRequest req,ServletResponse res)
用于处理客户端请求(由Servlet容器调用)。
每次收到请求时都会被调用。
3. void destroy()
当服务器关闭/重启或Servlet对象被移除时,负责释放Servlet对象占用的资源(由Servlet容器调用)。
只会被调用一次。
4. ServletConfig getServletConfig()
用于获取ServletConfig对象(包含了Servlet的初始化参数)。
5. String getServletInfo()
用于获取Servlet信息(如:作者、版本、版权等)。
示例
package com.sst.cx;
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class MyServlet implements Servlet {
private ServletConfig servletConfig;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig = servletConfig;
}
@Override
public ServletConfig getServletConfig() {
return this.servletConfig;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
servletResponse.setContentType("text/html;charset=UTF-8"); // 设置字符集
PrintWriter writer = servletResponse.getWriter();
writer.write("Hello World!"); // 设置页面的显示内容
writer.close();
}
}
ServletConfig接口(用来获取Servlet的初始化参数、配置信息)
定义了4个方法:
1. String getInitParameter(String name)
获取指定的初始化参数值(初始化参数在web.xml或注解中进行配置)。如果参数不存在,则返回null。
2. Enumeration<String> getInitParameterNames()
获取所有初始化参数名。如果没有初始化参数,则返回一个空的枚举集合。
3. ServletContext getServletContext()
获取Servlet上下文对象(代表当前Web应用)。
4. String getServletName()
获取Servlet实例的名称(web.xml中<servlet-name>元素的值)。
获得ServletConfig对象(2种方式)
通过ServletConfig对象即可获得当前Servlet的初始化参数、配置信息。
一个Servlet只能对应一个ServletConfig对象(即Servlet的初始化参数仅对当前Servlet有效)。
1. 在init()方法中,直接使用其ServletConfig方法参数。
2. 调用getServletConfig()方法。
ServletConfig servletConfig = this.getServletConfig();
ServletContext接口(代表当前web应用)
Servlet容器启动时会为每个Web应用创建一个对应的ServletContext对象(Servlet上下文对象/Context域对象,代表当前web应用,所有Servlet/Jsp共享同一个,可借此实现数据通讯---共享数据),并且将<context-param>元素中的上下文初始化参数以键值对的形式存入该对象中,Servlet容器关闭/重启时会销毁该对象。
获得ServletContext对象(4种方式)
1. 通过getServletContext()方法(GenericServlet提供)
ServletContext servletContext = this.getServletContext();
2. 通过getServletContext()方法(ServletConfig提供)
ServletContext servletContext = this.getServletConfig().getServletContext();
3. 通过getServletContext()方法(HttpSession提供)
ServletContext servletContext = req.getSession().getServletContext();
4. 通过getServletContext()方法(HttpServletRequest提供)
ServletContext servletContext = req.getServletContext();
使用场景
1. 不同Servlet间通讯
ServletContext接口中定义了实现数据通讯的的方法:
1. void setAttribute(String name, Object object)
将name作为属性,object作为属性值存入ServletContext对象中。
2. void removeAttribute(String name)
从ServletContext对象中移除属性名为name的属性。
3. Object getAttribute(String name)
从ServletContext对象中获取指定属性的值。
例:
Servlet1
// 获得ServletContext对象
ServletContext context = this.getServletConfig().getServletContext();
// 设值/改值(作为属性存储到ServletContext对象中)
context.setAttribute("data", data);
// 移除
context.removeAttribute("data");
Servlet2
// 获得ServletContext对象
ServletContext context = this.getServletConfig().getServletContext();
// 取值
String data = (String) context.getAttribute("data");
ServletContext属性和上下文初始化参数的区别
1. 创建方式
ServletContext的属性通过调用ServletContext接口的setAttribute()方法创建。
上下文初始化参数通过web.xml使用<context-param>元素配置。
2. 可进行的操作
ServletContext 的属性可以通过ServletContext接口的方法进行读取、新增、修改、移除等操作。
上下文初始化参数在容器启动后只能被读取,不能进行新增、修改和移除操作。
3. 生命周期
ServletContext中属性的生命周期从创建开始,到该属性被移除(remove)或者容器关闭结束。
上下文初始化参数的生命周期,从容器启动开始,到 Web 应用被卸载或容器关闭结束。
4. 作用
使用ServletContext中的属性可以实现Servlet之间的数据通讯。
使用上下文初始化参数无法实现数据通讯。
2. 获取Web应用的全局初始化参数(上下文初始化参数)所有Servlet共享
上下文初始化参数在Web应用的整个生命周期中会一直存在,且可以随时被任意一个Servlet访问。
ServletContext接口中定义了用于获取上下文初始化参数的方法:
1. String getInitParameter(String name)
获取指定上下文初始化参数值。
2. Enumeration<String> getInitParameterNames()
获取所有上下文初始化参数名。
例:
在web.xml的<web-app>下配置
<!-- 配置Web应用的全局初始化参数 -->
<context-param>
<param-name>url</param-name> <!-- 在当前应用中必须唯一 -->
<param-value>jdbc:mysql://localhost:3306/test</param-value>
</context-param>
Servlet中获取
// 获取ServletContext对象
ServletContext context = this.getServletContext();
// 获取指定全局初始化参数值
String contextInitParam = context.getInitParameter("url");
// 获取所有全局初始化参数名
Enumeration<String> e = context.getInitParameterNames();
3. 请求转发
Web应用在处理客户端的请求时,经常需要多个Web资源共同协作才能生成响应结果。但由于Serlvet 对象无法直接调用其他Servlet的service()方法,所以Servlet规范提供了2种解决方案:
1. 请求转发(属于服务器行为)
容器接收请求后,Servlet会先对请求做一些预处理,然后将请求传递给其他Web资源,来完成包括生成响应在内的后续工作。
RequestDispatcher接口提供了以下方法:
1. void forward(ServletRequest request,ServletResponse response)
用于将请求转发给另一个Web资源。
该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException异常。
2. void include(ServletRequest request,ServletResponse response)
用于将其他的资源作为当前响应内容包含进来。
RequestDispatcher对象(由Servlet容器创建)可以把请求转发给其他的Web资源。
获得RequestDispatcher对象(2种方式)
1. 调用ServletContext的getRequestDispatcher(String path)方法。
path参数:用于指定目标资源的路径,必须为绝对路径。
2. 调用ServletRequest的getRequestDispatcher(String path)方法。
path参数:用于指定目标资源的路径,可以为绝对路径(以符号“/”开头的路径,“/”表示当前Web应用的根目录),也可以为相对路径(相对当前Web资源的路径,不以符号“/”开头)。
请求转发的特点:
1. 请求转发不支持跨域访问,只能跳转到当前应用中的资源。
2. 请求转发之后,浏览器地址栏中的URL不会发生变化,因此浏览器不知道在服务器内部发生了转发行为,更无法得知转发的次数。
3. 参与请求转发的Web资源之间共享同一request对象和response对象。
4. 由于forward()方法会先清空response缓冲区,因此只有转发到最后一个Web资源时,生成的响应才会被发送到客户端。
2. 请求包含(了解即可)
例:
// 获取ServletContext对象
ServletContext context = this.getServletContext();
// 获取请求转发对象(RequestDispatcher)
RequestDispatcher rd = context.getRequestDispatcher("/servlet/ServletContextDemo5");
// 调用forward方法实现请求转发
rd.forward(request, response);
4. 读取资源文件
ServletContext接口定义了一些读取Web资源(配置文件、日志文件等)的方法:
1. Set getResourcePaths(String path)
返回一个Set集合,该集合中包含资源目录中的子目录和文件的名称。
path为虚拟路径,/代表该Web应用的根目录。
2. String getRealPath(String path)
返回资源文件的真实路径(文件的绝对路径)。
3. URL getResource(String path)
返回映射到资源文件的URL对象。
4. InputStream getResourceAsStream(String path)
返回映射到资源文件的 InputStream 输入流对象。
1. 使用ServletContext读取资源文件
// 设置浏览器编码
response.setHeader("content-type","text/html;charset=UTF-8");
// 读取src目录下com.sst.cx包下的db.properties文件
InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/com/sst/cx/db.properties");
// 读取src目录下的db.properties文件:getResourceAsStream("/WEB-INF/classes/db.properties");
// 读取WEB-INF目录下的db.properties文件:getResourceAsStream("db.properties");
/*
读取src目录下com.sst.cx包下的db.properties文件
// 获取web资源的绝对路径
String path = this.getServletContext().getRealPath("/WEB-INF/classes/com/sst/cx/db.properties");
InputStream in = new FileInputStream(path);
*/
Properties prop = new Properties();
prop.load(in);
// 获取文件中指定的属性值
String driver = prop.getProperty("hello");
/*
public void test() throws IOException {
String path = this.getServletContext().getRealPath("/WEB-INF/classes/01.avi");
String filename = path.substring(path.lastIndexOf("/") + 1);// 获取文件名
InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/01.avi");
byte buffer[] = new byte[1024];
int len = 0;
OutputStream out = new FileOutputStream("/Users/cx/" + filename);
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
out.close();
in.close();
}
*/
2. 使用类装载器读取资源文件
不适合装载大文件,否则会导致jvm内存溢出
// 设置浏览器编码
response.setHeader("content-type","text/html;charset=UTF-8");
// 获取 装载当前类的类装载器
ClassLoader loader = HelloServlet.class.getClassLoader();
// 读取src目录下的db.properties文件:loader.getResourceAsStream("db.properties");
// 读取src目录com.sst.cx包下的db.properties
InputStream in = loader.getResourceAsStream("com/sst/cx/db.properties");
Properties prop = new Properties();
prop.load(in);
String driver = prop.getProperty("hello");
ServletRequest接口
使用场景
1. 不同Servlet间通讯
ServletRequest接口中定义了操作属性的方法:
1. void setAttribute(String name, Object object)
将name作为属性,object作为属性值存入request对象中。
2. void removeAttribute(String name)
移除request对象中属性名为name的属性。
3. Object getAttribute(String name)
获取request对象中指定属性的值。
4. Enumeration getAttributeNames()
获取request对象中所有属性名的枚举集合。
例:
Servlet1
// 设值
request.setAttribute("name", "张三");
// 转发
request.getRequestDispatcher("/HelloServlet").forward(request, response);
Servlet2
// 取值
String name = (String) request.getAttribute("name");
- GenericServlet抽象类
实现了Servlet接口,并提供了除service()方法以外的其他4个方法的简单实现,开发员只需重写service()方法即可。
实现了ServletConfig接口的4个方法。
示例
package com.sst.cx;
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class MyServlet extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//
ServletConfig config = getServletConfig(); // 获取ServletConfig对象
String servletName = config.getServletName(); // 获取servletName
//
servletResponse.setContentType("text/html;charset=UTF-8"); // 设置字符集
PrintWriter writer = servletResponse.getWriter();
writer.write("Hello World!"); // 设置页面的显示内容
writer.close();
}
}
- HttpServlet抽象类(能够处理HTTP请求的Servlet)
继承自GenericServlet抽象类,添加了一些HTTP协议处理方法,并覆写了service方法(自动判断用户的请求方式并调用相应的doXXX方法)。
HttpServlet针对GET、POST、HEAD、PUT、DELETE、TRACE、OPTIONS这7种请求方式分别定义了7种方法:doGet()、doPost()、doHead()、doPut()、doDelete()、doTrace()、doOptions()。
示例
package com.sst.cx;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;
// 该注解会自动生成Servlet的映射配置
@WebServlet(name = "MyServlet", value = "/MyServlet")
public class MyServlet extends HttpServlet{
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("hello world");
out.flush();
out.close();
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
HttpServletRequest接口(继承自ServletRequest接口) 用于封装客户端提交的HTTP请求消息(请求行、请求头、请求体)
获取请求行的方法有:
1. String getMethod()
获取HTTP请求方式(如:GET、POST等)。
2. String getRequestURI()
获取请求行中的资源名称部分(URL的主机和端口之后,参数部分之前的部分)。
3. String getQueryString()
获取请求行中的参数部分(URL 中“?”以后的所有内容)。
4. String getContextPath()
获取当前Servlet所在的应用的名字(上下文)。
对于默认(ROOT项目)上下文中的Servlet,此方法返回空字符串""。
5. String getServletPath()
获取Servlet的URL映射路径。
6. String getRemoteAddr()
获取客户端的IP地址。
7. String getRemoteHost()
获取客户端的完整主机名。
如果无法解析出客户机的完整主机名,则该方法将会返回客户端的IP地址。
获取请求头的方法有:
1. String getHeader(String name)
获取一个指定头字段的值。
如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。
2. Enumeration getHeaders(String name)
获取指定头字段的所有值的枚举集合。
3. Enumeration getHeaderNames()
获取请求头中所有头字段的枚举集合。
4. String getContentType()
获取Content-Type头字段的值。
5. int getContentLength()
获取Content-Length头字段的值。
6. String getCharacterEncoding()
获取请求消息的字符集编码。
获取请求参数的方法(获取form表单的数据)有:
1. String getParameter(String name)
获取指定参数名的参数值。
2. String[] getParameterValues (String name)
获取指定参数名的所有参数值(以字符串数组的形式)。
HTTP请求中可以有多个相同参数名的参数。
3. Enumeration getParameterNames()
获取请求中的所有参数名(以枚举集合的形式)。
4. Map getParameterMap()
获取请求中的所有参数(所有参数名和参数值装入一个Map对象中)。
例
1. 获取URL相关的信息
// 1. 获取客户端请求的完整URL
// http://127.0.0.1:8080/hello/HelloServlet?hello=123&world=456
String requestUrl = request.getRequestURL().toString();
// 2. 获取请求行中的资源名部分(URL中端口后边不包括参数的资源路径)
// /hello/HelloServlet
String requestUri = request.getRequestURI();
// 3. 获取请求行中的参数部分(URL中的参数)
// hello=123&world=456
String queryString = request.getQueryString();
// 4. 获取Servlet路径之后、查询参数之前的额外路径
String requestPath = request.getPathInfo();
2. 获取当前访问用户相关的信息
// 1. 获取当前访问用户的IP地址(127.0.0.1)
String remoteAddr = request.getRemoteAddr();
// 2. 获取当前访问用户的主机名(www.baidu.com)
String remoteHost = request.getRemoteHost();
// 3. 获取当前访问用户的端口(8080)
int remotePort = request.getRemotePort();
// 4.
String remoteUser = request.getRemoteUser();
3. 获取客户端的请求方式(GET、POST、)
String method = request.getMethod();
4. WEB服务器
// 1. 获取WEB服务器的IP地址
String localAddr = request.getLocalAddr();
// 2. 获取WEB服务器的主机名
String localName = request.getLocalName();
5. 获取请求头
// 1. 获取所有的请求头,并循环遍历
Enumeration<String> reqHeadInfos = request.getHeaderNames();
while (reqHeadInfos.hasMoreElements()) {
String headName = (String) reqHeadInfos.nextElement();
String headValue = request.getHeader(headName);
}
// 2. 获取指定请求头对应的值
String value = request.getHeader("Accept-Encoding");
// 3. 获取指定请求头对应的值(多个值时),循环遍历
Enumeration<String> e = request.getHeaders("Accept-Encoding");
while (e.hasMoreElements()) {
String string = (String) e.nextElement();
}
6. 获取参数值(最常用)
// 乱码:客户端和服务端的数据编码格式不一致会导致乱码。
// 当客户端以UTF-8编码发送数据到服务端,服务端也需以UTF-8编码接收。request对象默认使用ISO-8859-1编码(该字符集不支持中文)
request.setCharacterEncoding("UTF-8"); // 只能解决POST提交乱码。
/*
GET提交乱码(Tomcat8及以上版本不再需要考虑GET方式提交导致的乱码):
以GET方式(浏览器默认使用的就是GET方式,比如:直接输入网址、点击超链接,除非指定表单以POST方式提交)请求的数据,request即使设置了以指定的编码接收数据也是无效的。服务端默认还是使用ISO8859-1这个字符编码来接收数据,和客户端发送请求数据使用的编码不一致,导致乱码。
解决(3种方式):
方式1(推荐). 获取到数据后先以ISO8859-1转码字节数组,再指定对应的编码(客户端提交数据时使用的编码)构建字符串。
String username = request.getParameter("username");
username = new String(username.getBytes("ISO8859-1"), "UTF-8") ;
方式2. 使用URLEncoder和URLDecoder进行编码和解码的操作(逆向编解码)
// 得到TOMCAT通过ISO8859-1解码的字符串
String username = request.getParameter("username");
// 对字符串使用ISO8859-1进行编码,得到最初浏览器使用UTF-8编码的字符串
username = URLEncoder.encode(username, "ISO8859-1");
// 将使用UTF-8编码的字符串使用UTF-8进行解码,得到正确的字符串
username = URLDecoder.decode(username, "UTF-8");
方式3. 修改Tomcat安装目录/conf/server.xml中的配置
<Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/>
对于jsp中URL的中文数据最好使用URL编码进行处理:<a href="...&name=<%=URLEncoder.encode("哈喽", "UTF-8")%">点击</a>。
*/
// 1. 获取指定参数值(标签name为username的值)
String username = request.getParameter("username");
// 2. 获取指定参数值(多个)
String[] insts = request.getParameterValues("inst");
String instStr="";
for (int i = 0; insts!=null && i < insts.length; i++) {
if (i == insts.length-1) {
instStr+=insts[i];
}else {
instStr+=insts[i]+",";
}
}
// 3. 获取所有的参数名(不常用)
Enumeration<String> paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String name = paramNames.nextElement();
String value = request.getParameter(name);
System.out.println(MessageFormat.format("{0}={1}", name,value));
}
// 4. 获取所有的参数名 (编写框架时常用)
// request对象封装的参数是以Map的形式存储的
Map<String, String[]> paramMap = request.getParameterMap();
for(Map.Entry<String, String[]> entry :paramMap.entrySet()){
String paramName = entry.getKey();
String paramValue = "";
String[] paramValueArr = entry.getValue();
for (int i = 0; paramValueArr!=null && i < paramValueArr.length; i++) {
if (i == paramValueArr.length-1) {
paramValue+=paramValueArr[I];
}else {
paramValue+=paramValueArr[i]+",";
}
}
}
7. 传值(在request域对象中添加/获取对象)
// 1. 添加
request.setAttribute("data", data);
// 2. 移除
request.removeAttribute("data");
// 3. jsp中获取值
<%=(String)request.getAttribute("data")%>
// 4. jsp中获取值(使用EL表达式)
${data}
// 5. jsp中获取所有参数值
Enumeration<String> attrNames = request.getAttributeNames();
8. 转发、重定向
一个web资源收到客户端请求后,通知服务器去调用另外一个web资源进行处理,称之为请求转发/307。
一个web资源收到客户端请求后,通知浏览器去访问另外一个web资源进行处理,称之为请求重定向/302。
请求转发的2种方式
1. this.getServletContext().getRequestDispatcher("/test.jsp").forward(request, response);
2. request.getRequestDispatcher("/test.jsp").forward(request, response); // 在request域对象中添加对象后可在跳转后的jsp中获取该对象
HttpServletResponse接口(继承自ServletResponse接口) 用于封装向客户端返回的HTTP响应消息(响应状态码、响应头、响应体)
响应行相关的方法:
1. void setStatus(int status)
设置HTTP响应消息的状态码,并生成响应状态行。
常用的响应状态码常量:
1. 状态码404:HttpServletResponse.SC_NOT_FOUND
2. 状态码200:HttpServletResponse.SC_OK
3. 状态码500:HttpServletResponse.SC_INTERNAL_SERVER_ERROR
4. 状态码302:HttpServletResponse.SC_FOUND
2. void sendError(int sc)
发送表示错误信息的状态码。
响应头相关的方法:
1. void addHeader(String name,String value)
添加响应头字段。
name参数用于指定响应头字段的名称,value参数用于指定响应头字段的值。
2. void setHeader (String name,String value)
设置响应头字段。
3. void addIntHeader(String name,int value)
添加值为int类型的响应头字段。
4. void setIntHeader(String name, int value)
设置值为int类型的响应头字段。
5. void setContentType(String type)
设置Servlet输出内容的MIME类型以及编码格式。
6. void setCharacterEncoding(String charset)
设置Servlet输出内容使用的字符编码。
7.
response.addDateHeader(arg0, arg1);
response.setDateHeader(arg0, arg1);
8.
if(response.containsHeader(arg0)){}
响应体(向客户端发送的数据)相关的方法:
1. OutputStream getOutputStream()
用于获取OutputStream字节输出流对象(二进制数据,可以处理任意类型的数据)。
例:
// 1. 设置浏览器编码(需要和写入数据的编码保持一致,否则乱码)
response.setHeader("content-type", "text/html;charset=UTF-8");
// 2. 将字符转成字节数组(需要转码),指定以UTF-8编码进行转换。如果不带参数则会根据操作系统的语言环境(中文操作系统使用GB2312)来选择转换编码。
String data = "你好";
byte[] dataByteArr = data.getBytes("UTF-8"); // 响应内容的编码需要和客户端使用的编码一致
// 3. 获取OutputStream输出流
OutputStream outputStream = response.getOutputStream();
// 4. 写入数据
outputStream.write(dataByteArr); // 如果想输出数字1: write((1+"").getBytes())
2. PrintWriter getWriter()
用于获取PrintWriter字符输出流对象(文本数据,只能处理字符数据。如果用字符流处理字节数据,会导致数据丢失)。
例:
// 1. 设置浏览器的编码(需保持一致)
response.setHeader("content-type", "text/html;charset=UTF-8");
// 2. 设置写入数据的编码(必须在获取PrintWriter对象之前设置,否则无效)
response.setCharacterEncoding("UTF-8");
// 步骤1和2可以换成:response.setContentType("text/html;charset=UTF-8");
// 3. 获取PrintWriter输出流
PrintWriter out = response.getWriter();
// 4. 写入数据
String data = "你好";
out.write(data); // 如果想输出数字1: write(1+"")
// 设置浏览器编码的另一种方式
// out.write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'/>");
out.println("");
out.flush();
out.close();
getOutputStream()和getWriter()这2个方法互相冲突(不能同时使用,否则会发生IllegalStateException异常)。
Serlvet的service方法结束后,Servlet引擎将检查getWriter或getOutputStream方法返回的输出流对象是否已经调用过close方法,如果没有,Servlet引擎将调用close方法关闭该输出流对象。
向ServletOutputStream或PrintWriter对象写入的数据将被Servlet引擎从response里面获取,并将这些数据当作响应消息的正文数据(响应体),然后再与响应状态行和各响应头组合成响应消息后输出到客户端。
不能直接输出数字,可以数字+""专为字符串再输出。
例:
1. 禁止浏览器缓存数据
response.setDateHeader("expries", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
2. 缓存
对于不经常变化的数据,在servlet中可以为其设置合理的缓存时间值,避免浏览器频繁向服务器发送请求,提升服务器的性能。
// 缓存时间设置为1天。
response.setDateHeader("expires",System.currentTimeMillis() + 24 * 3600 * 1000);
3. 设置浏览器:每隔5秒刷新一次【定时刷新】
response.setHeader("refresh", "5");
4. 请求重定向(属于客户端行为)支持跨域跳转,浏览器URL地址栏会发生改变
一个web资源收到客户端请求后,通知客户端(状态码302)去访问另外一个web资源(如:登录成功后跳到其他页)。本质上是两次HTTP请求,对应两个request对象和两个response对象。
HttpServletResponse接口中的sendRedirect()方法用于实现重定向。
例:
// sendRedirect内部实现:使用response设置302状态码和设置location响应头实现重定向。
response.sendRedirect(request.getContextPath()+"/index.jsp");
等价
response.setStatus(HttpServletResponse.SC_FOUND);
response.setHeader("Location", request.getContextPath()+"/index.jsp");
示例1(从网站上下载文件 OutputStream)
// 1. 获取要下载的文件的绝对路径
String realPath = this.getServletContext().getRealPath("/download/1.JPG");
// 2. 获取要下载的文件名
String fileName = realPath.substring(realPath.lastIndexOf("\\")+1);
// 3. 设置content-disposition响应头控制浏览器以下载的形式打开文件
response.setHeader("content-disposition", "attachment;filename="+fileName);
// 3. 如果文件名含中文需要+
response.setHeader("content-disposition", "attachment;filename="+URLEncoder.encode(fileName, "UTF-8"));
// 4. 获取要下载的文件输入流
InputStream in = new FileInputStream(realPath);
int len = 0;
// 5. 创建数据缓冲区
byte[] buffer = new byte[1024];
// 6. 获取OutputStream输出流
// 不能使用PrintWriter输出流,会丢失数据造成下载的文件损坏。
OutputStream out = response.getOutputStream();
// 7. 将FileInputStream流写入到buffer缓冲区
while ((len = in.read(buffer)) > 0) {
// 8. 使用OutputStream将缓冲区的数据输出到客户端浏览器
out.write(buffer,0,len);
}
in.close();
示例2(随机数图片 OutputStream、BufferedImage)
1. index.jsp文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>随机验证码</title>
<script type="text/javascript">
// 刷新验证码
function changeImg(obj,createTypeFlag){
document.getElementById(obj.id).src="${pageContext.request.contextPath}/VerificationServlet?createTypeFlag="+createTypeFlag+"&"+Math.random();
}
</script>
</head>
<body>
<form action="${pageContext.request.contextPath}/CheckVerificationServlet" method="post">
数字字母混合验证码:<input type="text" name="validateCode"/>
<img alt="验证码看不清,换一张" src="${pageContext.request.contextPath}/VerificationServlet" id="validateCodeImg1" onclick="changeImg(this,'nl')">
<br/>
中文验证码:<input type="text" name="validateCode"/>
<img alt="验证码看不清,换一张" src="${pageContext.request.contextPath}/VerificationServlet?createTypeFlag=ch" id="validateCodeImg2" onclick="changeImg(this,'ch')">
<br/>
英文验证码:<input type="text" name="validateCode"/>
<img alt="验证码看不清,换一张" src="${pageContext.request.contextPath}/VerificationServlet?createTypeFlag=l" id="validateCodeImg3" onclick="changeImg(this,'l')">
<br/>
数字验证码:<input type="text" name="validateCode"/>
<img alt="验证码看不清,换一张" src="${pageContext.request.contextPath}/VerificationServlet?createTypeFlag=n" id="validateCodeImg4" onclick="changeImg(this,'n')">
<br/>
<input type="submit" value="提交">
</form>
</body>
</html>
2. VerificationServlet.java文件
@WebServlet(name = "VerificationServlet", value = "/VerificationServlet")
public class VerificationServlet extends HttpServlet {
public static final int WIDTH = 120;// 图片宽
public static final int HEIGHT = 30;// 图片高
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 在内存中创建一张图片,并获取
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D)image.getGraphics();
// 1.1 设置图片的背景色、边框、干扰线
setBackGround(g);
setBorder(g);
drawRandomLine(g);
// 1.2 根据不同的createTypeFlag参数值绘制不同类型的随机字符串
// 获取客户端发送的createTypeFlag参数值
String createTypeFlag = request.getParameter("createTypeFlag");
String random = drawRandomNum(g,createTypeFlag);
// 将随机数存在session中,用于在CheckServlet中进行验证。
request.getSession().setAttribute("checkcode", random);
// 2. 设置浏览器
// 2.1 以图片方式打开
// 等同于response.setHeader("Content-Type", "image/jpeg");
response.setContentType("image/jpeg");
// 2.2 禁止缓存图片数据
response.setDateHeader("expries", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
// 3. 将图片写给浏览器
ImageIO.write(image, "jpg", response.getOutputStream());
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
/**
* 设置图片的背景色
*/
private void setBackGround(Graphics g) {
// 设置颜色
g.setColor(Color.WHITE);
// 填充区域
g.fillRect(0, 0, WIDTH, HEIGHT);
}
/**
* 设置图片的边框
*/
private void setBorder(Graphics g) {
// 设置边框颜色
g.setColor(Color.BLUE);
// 边框区域
g.drawRect(1, 1, WIDTH - 2, HEIGHT - 2);
}
/**
* 设置干扰线(在图片上画随机线条)
*/
private void drawRandomLine(Graphics g) {
// 设置颜色
g.setColor(Color.GREEN);
// 设置线条个数并画线
for (int i = 0; i < 5; i++) {
int x1 = new Random().nextInt(WIDTH);
int y1 = new Random().nextInt(HEIGHT);
int x2 = new Random().nextInt(WIDTH);
int y2 = new Random().nextInt(HEIGHT);
g.drawLine(x1, y1, x2, y2);
}
}
/**
生成随机字符
createTypeFlag类型
中文:"ch"
数字和字母:"nl"
纯数字:"n"
纯字母:"l"
例:String random = drawRandomNum((Graphics2D) g,"l");
String... createTypeFlag是可变参数。
可变参数适用于参数个数不确定但类型确定的情况,java会把可变参数当做数组处理。
注意:可变参数必须位于最后一项。
*/
private String drawRandomNum(Graphics2D g,String... createTypeFlag) {
// 设置颜色
g.setColor(Color.RED);
// 设置字体
g.setFont(new Font("宋体", Font.BOLD, 20));
// 常用的汉字
String baseChineseChar = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6";
// 数字和字母的组合
String baseNumLetter = "0123456789ABCDEFGHJKLMNOPQRSTUVWXYZ";
// 纯数字
String baseNum = "0123456789";
// 纯字母
String baseLetter = "ABCDEFGHJKLMNOPQRSTUVWXYZ";
// createTypeFlag[0]==null表示没有传递参数
if (createTypeFlag.length > 0 && null != createTypeFlag[0]) {
switch (createTypeFlag[0]) {
case "ch":
// 截取汉字
return createRandomChar(g, baseChineseChar);
case "nl":
// 截取数字和字母的组合
return createRandomChar(g, baseNumLetter);
case "n":
// 截取数字
return createRandomChar(g, baseNum);
case "l":
// 截取字母
return createRandomChar(g, baseLetter);
}
}else {
// 默认截取数字和字母的组合
return createRandomChar(g, baseNumLetter);
}
return "";
}
/**
* 创建随机字符
*/
private String createRandomChar(Graphics2D g,String baseChar) {
StringBuffer sb = new StringBuffer();
int x = 5;
String ch;
// 控制字数
for (int i = 0; i < 4; i++) {
// 设置字体旋转角度
int degree = new Random().nextInt() % 30;
ch = baseChar.charAt(new Random().nextInt(baseChar.length())) + "";
sb.append(ch);
// 正向角度
g.rotate(degree * Math.PI / 180, x, 20);
g.drawString(ch, x, 20);
// 反向角度
g.rotate(-degree * Math.PI / 180, x, 20);
x += 30;
}
return sb.toString();
}
}
3. CheckVerificationServlet.java文件
@WebServlet(name = "CheckVerificationServlet", value = "/CheckVerificationServlet")
public class CheckVerificationServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取客户端验证码
String clientCheckcode = request.getParameter("validateCode");
// 获取服务端验证码
String serverCheckcode = (String) request.getSession().getAttribute("checkcode");
if (clientCheckcode.equals(serverCheckcode)) {
System.out.println("验证码验证通过!"+clientCheckcode+"\n"+serverCheckcode);
}else {
System.out.println("验证码验证失败!"+clientCheckcode+"\n"+serverCheckcode);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
3. URL地址的推荐写法、Servlet映射配置
- URL地址的推荐写法
在JavaWeb开发中,建议以"/"开头填写URL地址(绝对路径的方式)。
如果"/"是给服务器用的,则代表当前web应用的根目录,如果"/"是给浏览器用的,则代表webapps目录(Web应用所在的目录,即Web应用的根目录的父目录,该目录下部署了该服务器的所有Web应用)。
- "/"代表当前web应用根目录的使用场景
1. 获取服务器上的某个资源的绝对路径。
this.getServletContext().getRealPath("/download/1.JPG");
2. 在服务端forward跳转到其他页面
this.getServletContext().getRequestDispatcher("/index.jsp").forward(request, response);
3. 使用include指令或<jsp:include>标签 引入页面
<%@include file="/jspfragments/head.jsp" %>
<jsp:include page="/jspfragments/demo.jsp" />
- "/"代表webapps目录的使用场景
1. 使用sendRedirect实现请求重定向
// 服务器发送一个URL地址给浏览器,浏览器拿到URL地址之后,再去请求服务器,所以这个"/"是给浏览器使用的,此时"/"代表的就是webapps目录。
response.sendRedirect("/hello_world/index.jsp");
上面这种方式将项目名写死了,不灵活也不安全。推荐使用下面这种方式:
response.sendRedirect(request.getContextPath()+"/index.jsp");
2. 使用超链接跳转
<a href="/hello_world/index.jsp">跳转到首页</a>
推荐使用下面这种方式:
<a href="${pageContext.request.contextPath}/index.jsp">跳转到首页</a>
3. Form表单提交
<form action="/hello_world/servlet/CheckServlet" method="post">
<input type="submit" value="提交">
</form>
推荐使用下面这种方式:
<form action="${pageContext.request.contextPath}/servlet/CheckServlet" method="post">
<input type="submit" value="提交">
</form>
4. 引用js脚本、css样式文件
<%--引用js脚本(绝对路径)--%>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/index.js></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/js/login.js"></script>
<%--引用css样式(绝对路径)--%>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/index.css" type="text/css"/>
- Servlet映射配置
客户端通过URL来访问web服务器中的资源,Servlet若想被外界访问则必须被映射到一个URL地址上,这个映射工作可以在web.xml文件中使用<servlet>和<servlet-mapping>完成或使用注解来完成。
2种方式(使用@WebServlet注解和web.xml)配置Servlet的优缺点
1. 使用@WebServlet注解
优点:直接在Servlet类中使用(配置只对当前类有效,避免了集中管理造成的配置冗长),代码量少,配置简单。适合多人同时开发(每个类只关注自身业务逻辑,与其他Servlet类互不干扰)。
缺点:Servlet 较多时,每个 Servlet 的配置分布在各自的类中,不便于查找和修改。
2. 使用web.xml配置文件
优点:集中管理Servlet配置(便于查找和修改)。
缺点:代码较繁琐,可读性不强(不易于理解)。
Servlet3.0之后的版本
为了简化web.xml对Servlet的配置,增加了@WebServlet、@WebInitParm、@WebFilter、@WebLitener等注解支持。
1. @WebServlet注解
用于将一个类声明为Servlet(该注解会在部署时被容器处理,容器根据其具体的属性配置将相应的类部署为Servlet)。
使用IDE创建Servlet后,会在Servlet类中自动添加@WebServlet("Servlet的相对请求路径") 注解(自动配置映射),不需要再到web.xml中手动添加映射。
常用属性(value和urlPatterns必须设置一个):
1. name(用于指定Servlet的name) String类型,默认值为类的完全限定名。
对应<servlet-name>标签。
2. value(等价于urlPatterns属性,两者同时指定时会忽略value属性值)String[]类型
对应<url-pattern>标签。
3. urlPatterns(用于指定一组Servlet的URL匹配模式)String[]类型
对应<url-pattern>标签。
4. loadOnStartup(用于指定Servlet的加载顺序)int类型
对应<load-on-startup>标签。
5. initParams(用于指定一组Servlet初始化参数)WebInitParam[]类型
对应<init-param>标签。
6. asyncSupported(声明Servlet是否支持异步操作模式)boolean类型
对应<async-supported>标签。
7. description(用于指定该Servlet的描述信息)String类型
对应<description>标签。
8. displayName(用于指定该Servlet的显示名)String类型
对应<display-name>标签。
注意:
1. 只能用于修饰继承自HttpServlet的类,不能用于实现Serlvet接口或继承自GenericServlet的类。
2. 使用@WebServlet注解配置后如果又在web.xml配置文件进行了配置则会忽略注解。
使用:
1. 启用注解支持
在web.xml配置文件的<web-app>顶层标签中有一个metadata-complete属性(用于指定当前web.xml是否是完全的):设置为true则容器在部署时将只依赖web.xml并忽略所有注解,设置为false(默认值)则表示启用注解支持。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
id="WebApp_ID" metadata-complete="false" version="4.0">
<!-- 默认值为false,即不配置该属性时,默认是开启注解支持的 -->
</web-app>
2. 使用注解
1. @WebServlet(属于类级别的注解)
例(常用写法,将Servlet的相对请求路径直接写在注解内):
@WebServlet("/HelloServlet") 等价于@WebServlet(urlPatterns = "/HelloServlet")
例(多重映射)
@WebServlet(urlPatterns = {"/HelloServlet", "/Hello"})
例(支持异步操作、Servlet的注册名称---需唯一、Servlet的描述信息、Servlet容器启动时装载、初始化参数):
@WebServlet(asyncSupported = true, name = "helloServlet", description = "描述", loadOnStartup = 1, urlPatterns = { "/HelloServlet", "/*" }, initParams = { @WebInitParam(name = "param1", value = "hello", description = "初始化参数1"),@WebInitParam(name = "param2", value = "world", description = "初始化参数2") })
Servlet3.0之前的版本
创建Servlet后,会在web.xml中自动生成如下代码(映射配置),而不是使用注解。
<servlet>
<description>描述</description>
<display-name>显示名</display-name>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.sst.cx.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
说明:
1. <servlet>标签(用于注册Servlet)
<description>标签
用于指定Servlet的描述信息。
<display-name>标签
用于指定Servlet的显示名。
<servlet-name>标签(必需)
用于指定Servlet的注册名称(需要唯一)。
<servlet-class>标签(必需)
用于指定Servlet类的全限定名名(包路径+类名)。
2.<servlet-mapping>标签(用于给已注册的Servlet配置URL映射)
<servlet-name>标签(必需)
用于指定Servlet名称(需要和<servlet>标签中的<servlet-name>保持一致)。
<url-pattern>标签(必需)
Servlet的对外访问路径(虚拟路径),Servlet的URL匹配模式(URL映射)。
===》1. 多重映射(同一个Servlet可以被映射到多个URL上)
方式1. 配置多个<servlet-mapping>元素(<servlet-name>值相同)。
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.sst.cx.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/Hello</url-pattern>
</servlet-mapping>
方式2. 在<servlet-mapping>元素下配置多个<url-pattern>元素(Servlet2.5之后支持)。
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.sst.cx.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
<url-pattern>/Hello</url-pattern>
</servlet-mapping>
想访问hello项目的HelloServlet时,可以在浏览器中输入
http://localhost:8080/hello/HelloServlet
或
http://localhost:8080/hello/Hello
===》2. URL匹配规则(4种)
当Servlet容器接收到请求后,Servlet容器会将请求的URL减去当前应用的上下文路径,使用剩余的字符串作为映射URL与Servelt虚拟路径进行匹配(从优先级高的虚拟路径开始匹配),匹配成功后将请求交给相应的Servlet进行处理(不会再关注其他虚拟路径是否匹配成功)。
优先级依次降低:
注意:目录匹配和扩展名匹配无法混合使用(如:/rest/*.do 是不正确的)。
1. 完全路径匹配(精确匹配,必须完全匹配)
以/开始,不能包含通配符*
例:/HelloServlet
2. 目录匹配(用于路径匹配)
以/开始,并以/*结尾
*表示通配符(匹配任意个字符)
例:/* 、/hello/*
3. 扩展名匹配
以*.开头
例:*.do 、*.hello
4. 缺省匹配(默认匹配,映射路径为/,可以匹配任意URL请求)
<url-pattern>/</url-pattern>:表示该Servlet为当前应用的缺省Servlet或默认Servlet,所有无法匹配到虚拟路径的请求都会使用该Servlet。
/*
Tomcat服务器在/Tomcat/conf/web.xml文件中设置了一个缺省Servlet(org.apache.catalina.servlets.DefaultServlet)。当客户端访问某个静态HTML文件或图片时,DefaultServlet会判断该HTML或图片是否存在,若存在则将数据以流的形式返回客户端,否则会报告404错误。
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
可在应用的web.xml中配置缺省Servlet,会覆盖Tomcat服务器中的配置。
*/
===》3. <load-on-startup>标签(在<servlet>元素中添加)
例:<load-on-startup>1</load-on-startup>
取值规则:
1. 必须是一个整数;
2. 当值小于0或没有指定(即默认)时,则表示该Servlet实例对象会在首次被请求时才会被Servlet容器加载创建并调用其init方法进行初始化;
3. 当值大于等于0时(取值越小优先级越高),则表示在Servlet容器启动时就加载创建Servlet的实例对象并调用其init方法进行初始化;
===》4. 初始化参数<init-param>
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象作为参数传递给servlet。
例:
1. 在web.xml中配置(只对当前Servlet有效)
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.sst.cs</servlet-class>
<init-param>
<param-name>name</param-name>
<param-value>hello</param-value>
</init-param>
</servlet>
2. 在Servlet类中获取
private ServletConfig config;
public void init(ServletConfig config) throws ServletException {
this.config = config;
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取指定的初始化参数
String paramVal = config.getInitParameter("name");
// 获取所有的初始化参数
Enumeration<String> e = config.getInitParameterNames();
while(e.hasMoreElements()){
String name = e.nextElement();
String value = config.getInitParameter(name);
response.getWriter().print(name + "=" + value + "<br/>");
}
}
4. 会话
HTTP(超文本传输协议)是一个基于请求与响应模式的无状态协议(无法保存和跟踪用户状态)。
无状态指:
1. 协议对于事务处理没有记忆能力,服务器不能自动维护用户的上下文信息,HTTP协议的请求无法保存用户状态(当浏览器发送HTTP请求到服务器时,服务器会响应客户端的请求,但当同一个浏览器再次发送请求到该服务器时,服务器并不知道它就是刚才那个浏览器)。
2. 每次请求都是独立的,不会受到前面请求的影响,也不会影响后面的请求。
但通常情况下,用户通过浏览器访问Web应用时,服务器都需要保存和跟踪用户的状态,可通过使用会话技术(在会话中帮助服务器记录用户状态和数据的技术)来解决。
用户打开一个浏览器访问该服务器中的web资源直到浏览器关闭前的整个过程称之为一个会话。
有状态会话:会话过程中不可避免地会产生一些数据,可将一些数据进行保存(用来记录最近一次的访问时间、免登录、跨页面共享数据)。
保存会话数据的方式:
1. Cookie(客户端会话技术)是基于HTTP协议实现的
将用户数据以cookie(一小段文本信息,设置在HTTP响应头的Set-Cookie字段中)的形式存储到各自的浏览器的缓存中。当用户使用浏览器再去访问服务器中的web资源时,就会把各自的数据(将Cookie设置在HTTP请求头的Cookie字段中)带给服务器。会话结束后,如果cookie设置了有效期则会从浏览器的缓存中取出并保存(以文本文件的形式)在硬盘中,否则会随着浏览器缓存的清除而被清除掉。
2. Session(服务端会话技术)
服务器为每个用户浏览器创建一个其独享的session对象(可通过调用request对象的getSession方法获取)用来存储数据。当用户再去访问该服务器中的其它web资源时,可以从session对象中获取数据。session对象默认30分钟(可在web.xml中配置)没有使用时会由服务器会自动销毁,也可手动销毁(调用session的invalidate方法)。
实现原理:
1. 当客户端第一次请求会话对象时,服务器会创建一个Session对象,并为该Session对象分配一个唯一的SessionID(用来标识这个Session对象);
2. 服务器将SessionID以Cookie的形式(Cookie名称为:“JSESSIONID”,值为SessionID的值)返回给客户端;
3. 浏览器再去访问服务器的其他资源时都会带着SessionID,服务器会根据SessionID找到对应的Session对象。
注意:
1. 流程中的Cookie是容器自动生成的,它的maxAge属性取值为-1,表示仅当前浏览器有效。浏览器关闭时,对应的Session并没有失效,但此时与此Session对应的Cookie已失效,导致再次打开浏览器时无法再通过Cookie获取服务器端的Session对象(会再次创建新的Session对象、Cookie)。
Session和Cookie的区别
1. 存储位置不同
Cookie将数据存放在客户端浏览器内存中或硬盘上。
Session将数据存储在服务器端。
2. 大小和数量限制不同
浏览器对Cookie的大小和数量有限制。
Session的大小和数量一般不受限制。
3. 存放数据类型不同
Cookie中保存的是字符串。
Session中保存的是对象。
4. 安全性不同
Cookie明文传递,安全性低,他人可以分析存放在本地的Cookie并进行Cookie欺骗。
Session存在服务器端,安全性较高。
5. 对服务器造成的压力不同
Cookie保存在客户端,不占用服务器资源。
Session保存在服务端,每一个用户独占一个 Session。若并发访问的用户十分多,就会占用大量服务端资源。
6. 跨域支持上不同
Cookie支持跨域名访问。
Session不支持跨域名访问。
- Cookie
创建Cookie对象(javax.servlet.http.Cookie类)
// 创建Cookie对象,name(Cookie名)和value(Cookie值)参数中不能包含 [ ] ( ) = , " / ? @ : ; 等字符。
// 一个Cookie包含:名称 和 值,只能标识一种信息。
// 一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。
Cookie c = new Cookie("url", "www.baidu.com");
操作Cookie对象
1. void setMaxAge(int expiry)
设置Cookie的最大有效时间,以s为单位。取值为正值时,表示Cookie在经过指定时间后过期;取值为负值时,表示Cookie不会被持久存储,在 Web浏览器退出时删除;取值为0时,表示删除该Cookie。
默认值为-1,表示该Cookie(保存在内存中)保留到浏览器关闭为止,可设置大于0的值使其保存在硬盘中。使用setMaxAge(0)手动删除Cookie时,需要使用setPath方法指定Cookie的路径,且该路径必须与创建Cookie时的路径保持一致。
2. int getMaxAge()
获取Cookie的最大有效时间,以s为单位。
3. String getName()
获取Cookie的名称。
4. void setPath(String uri)
指定Cookie的路径。
5. String getPath()
获取Cookie的有效路径。
6. void setSecure(boolean flag)
设置浏览器是否只能使用安全协议(如:HTTPS 或 SSL)发送Cookie。
7. boolean getSecure()
如果浏览器只通过安全协议发送Cookie,则返回true;如果浏览器可以使用任何协议发送Cookie,则返回false。
8. void setValue(String newValue)
设置Cookie的值。
9. String getValue()
获取Cookie的值。
10. int getVersion()
获取Cookie遵守的协议版本。
添加、获取Cookie对象
1. void addCookie(Cookie cookie)
用于在响应头中添加(向客户端返回)Cookie(设置Set-Cookie头字段)。
HttpServletResponse接口中定义。
2. Cookie[] getCookies()
用于获取请求头中的(客户端提交的)Cookie(获取Cookie头字段)。
HttpServletRequest接口中定义。
Cookie的缺点
1. 在HTTP请求中,Cookie是明文传递的,容易泄露用户信息,安全性不高。
2. 浏览器可以禁用Cookie,一旦被禁用,Cookie将无法正常工作。
3. Cookie对象中只能设置文本(字符串)信息。
4. 客户端浏览器保存Cookie的数量和长度是有限制的。浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
例:
1. 获取浏览器提交的cookie数据
Cookie[] cookies = request.getCookies();
2. 获取Cookie名、Cookie值
String cookName = cookie.getName();
String cookValue = cookie.getValue();
3. 创建Cookie(name,value)
Cookie cookie = new Cookie("hello", "123456");
4. 更新Cookie值
cookie.setValue("78910JQK");
5. 设置Cookie的有效期(不设置有效期,则默认关闭浏览器就会失效)
cookie.setMaxAge(24*60*60); // 1天
cookie.setMaxAge(0); // 命令浏览器删除Cookie,有效路径要一致
6. 获取cookie的有效期。
int time = cookie.getMaxAge();
7. 将cookie对象添加到response对象中(会存储到Set-Cookie字段中),浏览器在接收到响应消息时 会获取到Cookie。
response.addCookie(cookie);
8. Cookie中含有中文
// 设(将中文进行UTF-8转码)
Cookie cookie = new Cookie("userName", URLEncoder.encode("你好", "UTF-8"));
// 取(使用UTF-8解码)
String value = URLDecoder.decode(cookie.getValue(), "UTF-8");
9. 设置cookie的有效路径(仅在该路径下携带cookie数据)
// 设置为/hello,则只会在访问hello应用下的资源时携带cookie
// 设置为/hello/world,则只会在访问hello应用下的world目录下的资源时携带cookie
cookie.setPath(request.getContextPath());
10. 获取cookie的有效路径
String path = cookie.getPath();
11. 设置cookie的有效域
cookie.setDomain("request");
12. 获取cookie的有效域
String domain = cookie.getDomain();
示例
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置服务端以UTF-8编码进行输出
response.setCharacterEncoding("UTF-8");
// 设置浏览器以UTF-8编码进行接收。避免浏览器乱码问题。
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 获取浏览器提交的的cookie数组,第一次访问访问时为null。
Cookie[] cookies = request.getCookies();
if (cookies!=null) {
out.write("您上次访问的时间是:");
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[I];
if (cookie.getName().equals("lastAccessTime")) {
Long lastAccessTime =Long.parseLong(cookie.getValue());
Date date = new Date(lastAccessTime);
out.write(date.toLocaleString());
}
}
}else {
out.write("这是您第一次访问本站!");
}
// 重新设置用户的访问时间。
// 1. 创建cookie
Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");
// 2.1 设置Cookie的有效期为1天(否则关闭浏览器则失效)
cookie.setMaxAge(24*60*60);
// 2.2 设置有效路径
cookie.setPath("/sessionHello");
// 3. 将cookie对象添加到response对象中(会存储到Set-Cookie字段中)
response.addCookie(cookie);
}
// 删除Cookie
public void removeCookie(HttpServletResponse response) {
// 获取cookie
Cookie cookie = new Cookie("lastAccessTime", "");
// 设置有效时间为0(删除cookie)
cookie.setMaxAge(0);
// 设置有效路径,必须与要删除的Cookie的路径一致
cookie.setPath("/sessionHello");
// 回写
response.addCookie(cookie);
}
- Session
获取Session对象
创建
第一次使用HttpServletRequest.getSession()方法时服务器会创建Session对象。
销毁
Session过期;调用session.invalidate()方法手动销毁Session;服务器关闭或应用被卸载。
// 获取Session对象
HttpSession session=request.getSession();
操作Session对象(HttpSession接口中定义)
1. long getCreationTime()
获取创建Session的时间。
2. String getId()
获取Seesion的唯一ID。
3. long getLastAccessedTime()
获取客户端上一次发送与此Session关联的请求的时间。
4. ServletContext getServletContext()
获取Session所属的ServletContext对象。
5. void setMaxInactiveInterval(int interval)
指定在无任何操作的情况下Session失效的时间(session对象的销毁时机),以秒为单位。值为负数表示Session永远不会失效。默认过期时间30分钟(没有使用则服务器会自动销毁session)。
另一种设置过期时间的方式
在web.xml文件中可以配置session的有效时间(以分钟为单位,必须为整数,0或负数表示永不失效)
<session-config>
<session-timeout>15</session-timeout>
</session-config>
6. int getMaxInactiveInterval()
获取在无任何操作的情况下Session失效的时间,以秒为单位。
7. void invalidate()
主动使Session失效。
HttpSession接口中定义了操作属性的方法:
Session对象也是一种域对象,它可以对属性进行操作。
1. void setAttribute(String name, Object object)
将name作为属性,object作为属性值存入Session对象中。
2. void removeAttribute(String name)
移除Session对象中属性名为name的属性。
3. Object getAttribute(String name)
获取Session对象中指定属性的值。
4. Enumeration getAttributeNames()
获取Session对象中所有属性名的枚举集合。
context域对象、request域对象、session域对象的区别
1. 类型不同
context:javax.servlet.ServletContext
request:javax.servlet.http.HttpServletRequest
session:javax.servlet.http.HttpSession
2. 生命周期不同
context域对象在容器启动时创建,在容器关闭或Web应用被移除时销毁。
request域对象在容器收到客户端发送的请求时创建,在向客户端返回响应后销毁。
session域对象在容器第一次调用getSession()方法时创建,在Session过期或调用session.invalidate()方法手动销毁Session或服务器关闭或应用被卸载时销毁。
2. 作用域不同
context域对象对整个Web应用内的所有Servlet都有效。
request域对象只对本次请求涉及的Servlet有效。
request域对象只对本次会话涉及的Servlet有效。
3. 数量不同
整个Web应用中只有一个context域对象;
由于Servlet能处理多个请求,因此Web应用中的每个Servlet实例都可以有多个request域对象。
Web应用中可以有多个Session域对象,多个Servet实例可以共享同一session域对象。
4. 实现数据共享的方式不同
context域对象可以独立完成动态资源之间的数据共享(作用于该Web应用所有Servlet)。
request域对象需要与请求转发配合使用,才可以实现在动态资源之间共享数据(作用于本次请求涉及的所有Servlet)。
session域对象可以独立完成动态资源之间共享数据(作用于本次会话涉及的所有Servlet)。
例:
1. 获取session对象(如果不存在则创建)
HttpSession session = request.getSession(); // 通过session.isNew() 可判断是否是新创建的。
2. 获取session的Id
String sessionId = session.getId();
3. 获取session中的数据
List<Person> list = (List) session.getAttribute("list");
4. 向session中存储数据
session.setAttribute("hello", "123456");
5. session销毁时机
100s没有使用则失效
session.setMaxInactiveInterval(100);
主动失效session对象
session.invalidate();
6. 浏览器禁用Cookie后,需要对URL进行重写才能正常使用session。
URL重写(encodeURL和sendRedirect方法在检测到浏览器没有禁用cookie时则不进行URL重写)。
1. 使用encodeURL方法 对表单action和超链接的url地址进行重写(会在超链接地址后拼接sessionId)
String url = request.getContextPath()+ "/servlet/BuyServlet?id=" + book.getId();
url = response.encodeURL(url);
2. 使用encodeRedirectURL方法 对sendRedirect方法中的url地址进行重写
String url = request.getContextPath()+"/servlet/ListCartServlet";
url = response.encodeRedirectURL(url);
response.sendRedirect(url);
示例
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 避免乱码
response.setCharacterEncoding("UTF=8");
response.setContentType("text/html;charset=UTF-8");
// 获取session,如果session不存在则创建
HttpSession session = request.getSession();
// 判断session是不是新创建的
if (session.isNew()) {
response.getWriter().print("session创建成功,session的id是:"+sessionId);
}else {
response.getWriter().print("服务器已经存在该session了,session的id是:"+sessionId);
}
}
示例(浏览器禁用Cookie后的session处理)
BookListServlet
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 创建Session
request.getSession();
out.write("本网站有如下书:<br/>");
Set<Map.Entry<String,Book>> set = DB.getAll().entrySet();
for(Map.Entry<String,Book> me : set){
Book book = me.getValue();
// 将超链接的url地址进行重写
String url =request.getContextPath()+ "/servlet/BuyServlet?id=" + book.getId();
url = response.encodeURL(url);
out.println(book.getName() + " <a href='"+url+"'>购买</a><br/>");
}
BuyServlet
String id = request.getParameter("id");
Book book = DB.getAll().get(id); // 得到用户想买的书
HttpSession session = request.getSession();
List<Book> list = (List) session.getAttribute("list"); // 得到用户用于保存所有书的容器
if(list==null){
list = new ArrayList<Book>();
session.setAttribute("list", list);
}
list.add(book);
String url = response.encodeRedirectURL(request.getContextPath()+"/servlet/ListCartServlet");
System.out.println(url);
response.sendRedirect(url);
ListCartServlet
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
HttpSession session = request.getSession();
List<Book> list = (List) session.getAttribute("list");
if(list==null || list.size()==0){
out.write("您还没有购买任何商品");
return;
}
// 显示用户买过的商品
out.write("您买过如下商品:<br>");
for(Book book : list){
out.write(book.getName() + "<br/>");
}
模拟数据
class DB{
private static Map<String,Book> map = new LinkedHashMap<String,Book>();
static{
map.put("1", new Book("1","Javaweb开发"));
map.put("2", new Book("2","iOS开发"));
map.put("3", new Book("3","Android开发"));
map.put("4", new Book("4","Cordova开发"));
map.put("5", new Book("5","Flutter开发"));
}
public static Map<String,Book> getAll(){
return map;
}
}
class Book{
private String id;
private String name;
public Book() {
super();
}
public Book(String id, String name) {
super();
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
5. 表单
示例
index.jsp
<!--
form表单
action属性:指定将表单数据发送到哪个路径。
method属性:指定表单的提交方式(get/post),默认为get。
-->
<form action="${pageContext.request.contextPath}/servlet/RequestDemo03" method="post">
<!--
input输入框
1. type属性:指定input标签的类型。
1. text:文本框。
2. password:密码框(以···密文显示)。
3. radio:单选按钮。在一组多选按钮中,name属性必须一致,可通过checked属性指定哪一个单选按钮为默认选中,value属性为单选按钮的值,元素内容为页面上的显示内容。
4. checkbox:复选框。在一组复选框中,name属性必须一致,可通过checked属性指定哪些复选框为默认选中,value属性为复选框的值,元素内容为页面上的显示内容。
5. hidden:隐藏域(不在页面上显示,专门用来传递参数或保存参数)。
6. submit:提交按钮(用来提交表单),当点击提交后,所有填写的表单内容都会被传输到服务器端。
7. reset:重置表单按钮(用来重置表单),当点击重置后,所有表单恢复原始显示内容。
2. name属性:指定标签名(在后端可通过该标签名获取标签值)。
3. value属性:指定默认显示值。
4. size属性:指定显示长度。
5. maxlength属性:指定最多输入长度。
-->
<!-- 表示的是一个空格,用于UI间距微调-->
编 号(文本框):
<input type="text" name="userid" value="NO." size="2" maxlength="2"><br>
用户名(文本框):<input type="text" name="username" value="请输入用户名"><br>
密 码(密码框):
<input type="password" name="userpass" value="请输入密码"><br>
性 别(单选框):
<input type="radio" name="sex" value="男" checked>男
<input type="radio" name="sex" value="女">女<br>
兴 趣(复选框):
<input type="checkbox" name="inst" value="唱歌">唱歌
<input type="checkbox" name="inst" value="游泳">游泳
<input type="checkbox" name="inst" value="跳舞">跳舞
<input type="checkbox" name="inst" value="编程" checked>编程
<input type="checkbox" name="inst" value="上网">上网
<br>
<!--
select下拉列表框
通过option元素指定下拉的选项,可通过SELECTED属性指定哪一个下拉选项为默认,value属性为下拉选项的值,元素内容为下拉选项在页面上的显示内容,
-->
部 门(下拉框):
<select name="dept">
<option value="技术部">技术部</option>
<option value="销售部" SELECTED>销售部</option>
<option value="财务部">财务部</option>
</select>
<br>
<!--
textarea大文本输入框
cols属性:指定宽度为34列。
rows属性:指定高度为5行。
-->
说 明(文本域):
<textarea name="note" cols="34" rows="5">
</textarea>
<br>
<input type="hidden" name="hiddenField" value="hiddenvalue"/>
<input type="submit" value="提交"/>
<input type="reset" value="重置"/>
</form>
HelloServlet.java
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 客户端是以UTF-8编码提交表单数据的,所以需要设置服务器端以UTF-8的编码进行接收,否则对于中文数据就会产生乱码。
request.setCharacterEncoding("UTF-8");
// 通过文本框的name属性获取其value值。
String userid = request.getParameter("userid"); // 编号
String username = request.getParameter("username"); // 用户名
String userpass = request.getParameter("userpass");// 密码
String sex = request.getParameter("sex"); // 性别
String dept = request.getParameter("dept");// 部门
String[] insts = request.getParameterValues("inst"); // 多个值
String note = request.getParameter("note"); // 说明信息
String hiddenField = request.getParameter("hiddenField"); // 获取隐藏域的内容
//
String instStr="";
for (int i = 0; insts!=null && i < insts.length; i++) {
if (i == insts.length-1) {
instStr+=insts[I];
}else {
instStr+=insts[i]+",";
}
}
String htmlStr = "<table>" +
"<tr><td>填写的编号:</td><td>{0}</td></tr>" +
"<tr><td>填写的用户名:</td><td>{1}</td></tr>" +
"<tr><td>填写的密码:</td><td>{2}</td></tr>" +
"<tr><td>选中的性别:</td><td>{3}</td></tr>" +
"<tr><td>选中的部门:</td><td>{4}</td></tr>" +
"<tr><td>选中的兴趣:</td><td>{5}</td></tr>" +
"<tr><td>填写的说明:</td><td>{6}</td></tr>" +
"<tr><td>隐藏域的内容:</td><td>{7}</td></tr>" +
"</table>";
htmlStr = MessageFormat.format(htmlStr, userid,username,userpass,sex,dept,instStr,note,hiddenField);
response.setCharacterEncoding("UTF-8"); // 设置服务器端以UTF-8编码输出数据到客户端
response.setContentType("text/html;charset=UTF-8"); // 设置客户端浏览器以UTF-8编码解析数据
response.getWriter().write(htmlStr); // 输出htmlStr里面的内容到客户端浏览器显示
}
表单防重提交(避免表单重复提交)
重复提交的场景
1. 网络卡顿时重复点击按钮进行提交。
2. 点击浏览器的刷新按钮后,会弹框提示是否重复提交,在弹框中点击提交。
3. 提交后再返回到当前页面再点提交。
对于场景1,可使用js脚本来避免重复提交(只对场景1有效)
方式1.
用js控制Form表单只能提交一次
<script type="text/javascript">
// 表单是否已经提交标识,默认为false
var isCommitted = false;
function dosubmit(){
if(isCommitted==false){
// 提交表单后,将表单是否已经提交标识设置为true
isCommitted = true;
// 返回true让表单正常提交
return true;
}else{ // 返回false时,表单不会被提交。
return false;
}
}
</script>
<form action="${pageContext.request.contextPath}/servlet/DoFormServlet" onsubmit="return dosubmit()" method="post">
用户名:<input type="text" name="username">
<input type="submit" value="提交" id="submit">
</form>
方式2. (不推荐)
点击按钮后改变按钮状态为不可用
<script type="text/javascript">
function dosubmit(){
// 获取表单提交按钮
var btnSubmit = document.getElementById("submit");
// 将表单提交按钮设置为不可用,这样就可以避免用户再次点击提交按钮
btnSubmit.disabled= "disabled";
// 返回true让表单可以正常提交
return true;
}
</script>
方式3. (不推荐)
点击按钮后隐藏提交按钮
对于场景2、3,客户端无法解决,需要服务端通过session解决。
在服务器端生成一个唯一的随机标识号:Token(令牌),保存在Session中。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果一致则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:
1. 用户提交的表单数据中没有Token时。
2. session对象中不存在Token时。
3. session对象中的Token和表单提交的Token不同时。
使用
1. FormServlet(用于生成Token并跳转到表单form.jsp)
// 1. 创建令牌(TokenProccessor为普通自定义类)
String token = TokenProccessor.getInstance().makeToken();
// 2. session保存token(令牌)
request.getSession().setAttribute("token", token);
// 3. 跳转到form.jsp页面
request.getRequestDispatcher("/form.jsp").forward(request, response);
2. form.jsp中
<input type="hidden" name="token" value="${token}"/>
或
<input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
3. DoFormServlet(处理表单提交)
boolean b = isRepeatSubmit(request); // 判断用户是否是重复提交
if(b==true){
System.out.println("重复提交");
return;
}
request.getSession().removeAttribute("token"); // 移除session中的Token
/**
* 判断客户端提交的令牌和服务器端存储的令牌是否一致
*/
private boolean isRepeatSubmit(HttpServletRequest request) {
String client_token = request.getParameter("token");
// 1、如果用户提交的表单数据中没有Token,则用户是重复提交了表单
if(client_token==null){
return true;
}
// 取出存储在Session中的Token
String server_token = (String) request.getSession().getAttribute("token");
// 2、如果session对象中的Token不存在,则用户是重复提交了表单
if(server_token==null){
return true;
}
// 3、如果存储在Session中的Token与表单提交的Token不同,则用户是重复提交了表单
if(!client_token.equals(server_token)){
return true;
}
return false;
}
4.创建Token类(生成Token的工具类,单例)
public class TokenProccessor {
/*
单例设计模式(保证类的对象在内存中只有一个)
1、把类的构造函数设置为私有。
2、创建私有对象。
3、提供一个对外的公共方法,返回创建的私有对象。
*/
private TokenProccessor(){}
private static final TokenProccessor instance = new TokenProccessor();
public static TokenProccessor getInstance(){
return instance;
}
// 生成Token
public String makeToken(){
String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
try {
MessageDigest md = MessageDigest.getInstance("md5");
byte md5[] = md.digest(token.getBytes());
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
6. 线程安全问题
当多个客户端同时访问同一个Servlet对象时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet对象的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题(如果是访问局部变量则不会有问题)。
解决:
synchronized (this) {
// 在java中,每一个对象都有一把锁,这里的this指的就是Servlet对象
}
假如有9999个人同时访问这个Servlet,则需要按先后顺序排队轮流访问,这种解决会非常糟糕,编写Servlet时万不能用这种方式处理线程安全问题。
7. 网络
计算机网络:把分布在不同地区的计算机相互连起来就组成了一个网络,计算机之间可以资源共享、传递信息。
网络通信协议:计算机网络实现通讯必须要遵循一定的通信协议。
- IP协议(属于网络层)
172.10.2.10
每台计算机都有一个唯一的IP地址
IP地址用点分成四段。在计算机内部IP地址是用四个字节来表示的,一个字节代表一段,每一个字节代表的数最大只能到达255。
- TCP协议和UDP协议(属于传输层)
TCP
面向连接,三次握手,可靠,点对点
UDP
不面向连接,不可靠,点对多点(速度快)
- HTTP协议(属于应用层)
HTTP(hypertext transfer protocol,超文本传输协议)
是使用TCP/IP协议的一个应用层协议。
用于WEB浏览器与WEB服务器之间交换数据。客户端连上web服务器后,若想获得web服务器中的某个web资源,需遵守一定的通讯格式,HTTP协议用于定义客户端与web服务器通迅的格式。
HTTP协议的版本
HTTP/1.0、HTTP/1.1
区别在于:客户端与web服务器建立连接后,HTTP/1.0在一个连接上只能获得一个web资源,HTTP1.1可以获取多个web资源。
HTTP请求
客户端连上服务器后,向服务器请求某个web资源,称为客户端向服务器发送了一个HTTP请求。
一个完整的HTTP请求包括:一个请求行、若干消息头、实体内容。
请求行(请求方式、请求资源名、协议版本号):
请求方式有:POST、GET、HEAD、OPTIONS、DELETE、TRACE、PUT
GET方式在URL地址后附带的参数是有限制的,其数据容量通常不能超过1K。
消息头:
1. Accept: 告诉服务器 浏览器支持的数据类型(以,分割)
application/x-ms-application,
image/jpeg,
application/xaml+xml,
image/gif,
image/pjpeg,
application/x-ms-xbap,
application/vnd.ms-excel,
application/vnd.ms-powerpoint,
application/msword,
2. Accept-Charset: 告诉服务器 浏览器支持的字符集
3. Accept-Encoding:告诉服务器 浏览器支持的压缩格式
4. Accept-Language:告诉服务器 浏览器的语言环境
5. Host:告诉服务器 浏览器想访问哪台主机
6. If-Modified-Since: 告诉服务器 浏览器缓存数据的时间
7. Referer:告诉服务器 浏览器是从哪个页面来的【防盗链】
8. Connection:告诉服务器 浏览器请求完后是断开链接,还是持有链接。
实体内容:
发送给服务端的数据。
例(消息头):
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg,
application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost:8080/JavaWebDemoProject/Web/2.jsp
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.3)
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive
HTTP响应
一个HTTP响应代表服务器向客户端返回的数据。包括: 一个状态行、若干消息头、实体内容。
状态行(HTTP协议版本、状态码、原因):
状态码
100~199 成功接收请求,要求客户端再次提交请求才能完成整个过程。
200~299 成功接收请求并完成处理过程
300~399 为完成请求,用户需要进一步细化请求
400~499 客户端请求有错误
500~599 服务端出错
消息头(描述服务器的基本信息):
1. Location: 告诉浏览器 跳到哪里(跳转至其他页面)
2. Server:告诉浏览器 服务器的型号
3. Content-Encoding:告诉浏览器 返回数据的压缩格式
4. Content-Length: 告诉浏览器 返回数据的大小
5. Content-Language: 告诉浏览器 语言环境
6. Content-Type:告诉浏览器 返回数据的类型
7. Refresh:告诉浏览器 定时刷新
8. Content-Disposition: 告诉浏览器 以下载方式打开文件
9. Transfer-Encoding:告诉浏览器 数据是以分块方式返回的
10. Expires: -1 控制浏览器 不要缓存
11. Cache-Control: no-cache
12. Pragma: no-cache
实体内容:
返回给客户端的显示数据
例(响应消息):
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 105
Date: Tue, 27 May 2014 16:23:28 GMT
<html>
<head>
<title>Hello World JSP</title>
</head>
<body>
Hello World!
</body>
</html>
- 测试
在src下新建包名com.sst.cx,包下新建TestServlet会在web.xml中生成相应的映射。
package com.sst.cx;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
告诉浏览器 服务器的响应状态码
// 状态302告诉浏览器这个资源我没有
response.setStatus(302);
// Location:去跳转到别的资源。
response.setHeader("Location", "http://www.baidu.com");
告诉浏览器 数据的压缩格式
String data = "张三李四王五麻六邹七" + "你们好";
System.out.println("原始数据的大小为:" + data.getBytes().length);
// 压缩后的数据会存储到bout中。
ByteArrayOutputStream bout = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(bout); // buffer
gout.write(data.getBytes());
gout.close();
byte g[] = bout.toByteArray();
// 压缩格式、大小
response.setHeader("Content-Encoding", "gzip");
response.setHeader("Content-Length",g.length +"");
response.getOutputStream().write(g);
告诉浏览器 返回数据的类型
// 数据类型为"image/jpeg"
response.setHeader("content-type", "image/jpeg");
// 读取项目根目录/img目录下的hello.jpg,返回一个输入流
InputStream in = this.getServletContext().getResourceAsStream("/img/hello.jpg");
byte buffer[] = new byte[1024];
int len = 0;
OutputStream out = response.getOutputStream();// 得到输出流
while ((len = in.read(buffer)) > 0) {// 读取输入流(in)里面的内容存储到缓冲区(buffer)
out.write(buffer, 0, len);// 将缓冲区里面的内容输出到浏览器
}
设置refresh响应头
// 让浏览器每隔3秒定时刷新
response.setHeader("refresh", "3");
// 3秒后跳转
response.setHeader("refresh", "3;url='http://www.baidu.com'");
设置content-disposition响应头,让浏览器下载文件
response.setHeader("content-disposition", "attachment;filename=xxx.jpg");
InputStream in = this.getServletContext().getResourceAsStream("/img/1.jpg");
byte buffer[] = new byte[1024];
int len = 0;
OutputStream out = response.getOutputStream();
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request,response);
}
}