Servlet总结
Http协议
Http协议版本
- Http1.0: 请求数据,服务器返回后,将会断开连接.
- Http1.1: 请求数据,服务器返回后,连接还会保持着.除非服务器或客户端中有一方关掉或超过一定的时间限制,链接自动断开.
Http请求
请求的数据里面包含三个部分内容: 请求行,请求头,请求体
- 请求行包含 请求方式, 目标地址路径, HTTP版本
- 其中请求方式常用的有GET和POST,区别在于:
- POST请求在url后面没有任何数据, GET请求在地址后面加上要提交的数据.
- GET以url提交数据,因此提交的数据大小有限,不能超过1kb.
- POST以流的方式返回数据,提交数据大小无限制.
- GET请求会在地址栏上显示数据,有安全隐患.
Servlet基本使用
Servlet概述
- Servlet其实就是一个java程序,运行web服务器上,用于接收和响应客户端的http请求.
- Tomcat本质上就是一个Servlet容器.
- 更多的是配合动态资源来做.当然静态资源也需要使用到servlet,只不过是Tomcat里面已经定义好了一个 DefaultServlet.
Servlet简单使用
- 创建一个Web工程,并配置一个服务器。
- 测试运行Web工程
1. 新建一个类,实现Servlet接口
2. 配置Servlet,在/工程目录/webContent/WEB-INF/web.xml
的根元素下添加如下内容:<!-- 向项目注册Servlet --> <servlet> <servlet-name>实现Servlet的类名</servlet-name> <servlet-class>实现Servlet的完整包路径</servlet-class> </servlet> <!-- 将servlet映射到url, url路径必须以/开始 --> <servlet-mapping> <servlet-name>实现Servlet的类名</servlet-name> <url-pattern>/url路径</url-pattern> </servlet-mapping>
- 在浏览器输入
localhost:8080/项目名称/Servlet的url路径
,访问Servlet.
HttpServlet的使用
- 在建立Http服务器时,我们可以创建
HttpServlet
的子类而不用创建Servlet
子类. HttpServlet
是GenericServlet
的子类,其中重写了service方法,判断Http的请求方式是POST和GET,并执行doGet()
或doPost()
方法 因此只要重写其doGet和doPost方法即可.
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 若Http请求方式为GET,执行此方法
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 若Http请求方式为POST,执行此方法
}
}
Servlet的生命周期
init()
方法:- 在创建该servlet的实例时执行该方法.
- 一个servlet只会初始化一次,
init()
方法只会执行一次 - 默认情况下是: 初次访问该servlet,才会创建实例
service()
方法:- 只要客户端发来一个请求,那么就执行一次
service()
方法了 - 该方法可以被执行很多次. 一次请求,对应一次service方法的调用
- 只要客户端发来一个请求,那么就执行一次
destroy()
方法:- servlet销毁的时候,就会执行该方法
- 销毁Servlet有两种情况:
- 该项目从tomcat的里面移除.
- 正常关闭tomcat就会执行.
doGet()
和doPost()
不算生命周期方法. 所谓的生命周期方法是指,从对象的创建到销毁一定会执行的方法,但是这两个方法不一定会执行
让Servlet提前创建实例
- 默认情况下,只有在初次访问Servlet的时候,才会执行
init()
方法. 有的时候,我们可能需要在这个方法里面执行一些初始化工作,甚至是做一些比较耗时的逻辑. - 那么这个时候,初次访问时可能会在init方法中逗留太久的时间.
- 在配置的时候,可以使用
load-on-startup
元素来指定Servlet的加载时机.给定的数字越小,加载的时机就越早.一般不写负数,从2开始即可.
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.itheima.servlet.HelloServlet04</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
Servlet高级配置
Servlet URL路径配置方式
- 全路径匹配:
<url-pattern>/具体路径</url-pattern>
URL:localhost:8080/项目名称/具体路径/
- 前半段路径匹配:
<url-pattern>/前半段路径/*</url-pattern>
. *是一个通配符,可以匹配任意文字
URL:localhost:8080/项目名称/前半段路径/任意文字
- 以扩展名匹配:
<url-pattern>*.拓展名</url-pattern>
, 这种配置方式不用/打头
URL:localhost:8080/项目名称/文件名.拓展名
ServletContext
- ServletContext: 每个web工程都只有一个ServletContext对象. 不管在哪个servlet里面,获取到的这个类的对象都是同一个.
使用ServletContext获取全局数据
- ServletContext存储在
/工程目录/webContent/WEB-INF/web.xml
内,在跟元素下存储ServletContext.<context-param> <param-name>address</param-name> <param-value>北京</param-value> </context-param>
- 在Java中,使用
getServletContext()
方法获取ServletContext对象ServletContext context = getServletContext(); String address = context.getInitParameter("address");
使用ServletContext获取web资源
运行Tomcat时,JRE是由Tomcat托管的,因此在Web工程进行文件读写时,默认路径是Tomcat的/bin
目录,而非工程目录.
-
可以用
ServeletContext
对象的getRealPath()
方法来获取Web工程里根目录的实际路径,通过getResourceAsStream()
方法对Web工程根目录下的文件进行读写.// 获取ServletContext对象 ServletContext context = getServletContext(); String path = context.getRealPath(""); // 得到项目下/WebContent的实际存储路径 InputStream is = context.getResourceAsStream("file/config.properties"); // /file目录在工程的/WebContent目录下 Properties properties = new Properties(); properties.load(is);
-
也可以使用
ClassLoader
对象的getResourceAsStream()
方法获取工程下的文件.但这种方式的默认路径是以运行时的class文件的路径\WebContext\WEB-INF\classes
为默认路径.要从此路径返回到工程根路径,需要向上返回两层目录.InputStream is = this.getClass().getClassLoader() .getResourceAsStream("../../file/config.properties"); properties.load(is);
使用ServletContext存取域数据
域数据在Servlet间共享,可以用ServletContext
对象的getAttribute("数据名")
和setAttribute("数据名")
方法进行存取.若未存数据就取的话,会返回null对象.
下面是一个用Servlet记录登录次数的例子:
使用Servlet模拟网站登录并记录登录次数
工程的体系如下所示,登录http://localhost:8080/LoginTest/login.html
进行登录.
若登陆成功,页面跳转到登陆成功页面http://localhost:8080/LoginTest/login_succeed.html
;
点击登陆成功页面上的链接返回登录成功次数.
若登录失败,页面返回失败信息.
登录页/LoginTest/login.html
的html代码如下所示,是一个表单:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>请输入以下内容,完成登录</h2>
<!-- action指示表单提交的url,
表单提交的url为 /项目名称/LoginServlet,与该html文件位于同一虚拟目录下
这里的LoginServlet不是类名,是在/WEB-INF/web.xml中注册的url-pattern -->
<form action="LoginServlet" method="get">
账号:<input type="text" name="username" /><br>
密码:<input type="password" name="password" /><br>
<input type="submit" value="登录" />
</form>
</body>
</html>
其中/LoginServlet
是类cn.maoritian.login.LoginServlet
注册时所对应的url-pattern
.类LoginServlet
的代码如下:
package cn.maoritian.login;
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 LoginServlet extends HttpServlet {
public void init() throws ServletException {
super.init();
getServletContext().setAttribute("loginTimes", 0);
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// 得到Http请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username=" + username + ",password=" + password);
// 通过PrintWriter对象向response写入数据
PrintWriter pw = response.getWriter();
// 验证密码,这里为了简单起见,不访问数据库了
boolean loginSuccess = (username.equals("admin") && password
.equals("admin"));
if (loginSuccess) {
// 取出登陆成功次数,并将其刷新后存入域数据
int loginTimes = (Integer) getServletContext().getAttribute(
"loginTimes");
System.out.println("login succeed " + loginTimes + " times");
getServletContext().setAttribute("loginTimes", loginTimes + 1);
// 若成功,重定向到url /项目名称/login_succeed.html
// http状态码302为重定向,向响应头写入状态码和重定向url
response.setStatus(302);
response.setHeader("Location", "login_succeed.html");
} else {
// 若失败,直接在返回的响应体中写入信息
System.out.println("Login failed");
pw.write("Login failed");
}
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
在LoginServlet
的初始化方法init()
中,我们向ServletContext
写入域数据loginTimes
为0.
之后在登陆成功时对loginTimes
进行更新.
登陆成功时页面重定向到http://localhost:8080/LoginTest/login_succeed.html
,其代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>登录成功了</h2>
<!-- 链接的url为 /项目名称/CountLoginServlet,与该html文件位于同一虚拟目录下
这里的CountLoginServlet不是类名,是在/WEB-INF/web.xml中注册的url-pattern -->
<a href="CountLoginServlet">获取网站登录成功总数 </a>
</body>
</html>
其中/CountLoginServlet
是类cn.maoritian.login.CountLoginServlet
注册时所对应的url-pattern
.
类CountLoginServlet
代码如下:
package cn.maoritian.login;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CountLoginServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
int loginTimes = (Integer) getServletContext().getAttribute("loginTimes");
response.getWriter().write("logintimes=" + loginTimes);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
在该类中,我们从ServletContext
读取域数据loginTimes
,这证明域数据在Servlet之间是共享的.
ServletContext的作用范围和生命周期
- ServletContext的生命周期:
- 服务器启动时,会为托管的每一个web应用程序创建一个
ServletContext
对象. ServletContext
随着Servlet
的销毁而销毁.
- 服务器启动时,会为托管的每一个web应用程序创建一个
- ServletContext 的作用范围
ServletContext
在一个项目内是共享的,在项目间不共享.
HttpServletRequest
HttpServletRequest
对象封装了客户端提交过来的一切数据,包含请求头和请求体.
- 可以使用其
getHeaderNames()
取到所有传入的请求头信息,getHeader("请求头")
得到请求头的值. - 可以使用其
getParameter("参数名")
可以得到请求参数.
HttpServletResponse
HttpServletResponse
对象负责返回数据给客户端.
使用其getWriter()
返回一个PrintWriter
对象,可以以字符流的方式写数据.
使用其getOutputStream()
返回一个ServletOutputStream
对象,可以以字符流的方式写数据.
//以字符流的方式写数据
response.getWriter().write("<h1>hello response...</h1>");
//以字节流的方式写数据
response.getOutputStream().write("<h1>hello response...</h1>".getBytes());
请求转发和重定向
重定向
重定向需要向客户端返回Http状态码302,并在响应头的Location
写入要重定向URL.
response.setStatus(302);
response.setHeader("Location", "login_success.html");
也可以使用HttpResponse
的sendRedirect("目标URL")
来完成重定向,效果与上表两句完全相同.
response.sendRedirect("login_success.html");
请求转发
可以使用HttpRequest
的getRequestDispatcher("目标URL")
来完成重定向.
request.getRequestDispatcher("login_succeed.html").forward(request, response);
请求转发与重定向的区别
- 请求转发并不会在浏览器上产生URL跳转,对其他url上数据的请求是由服务器发出的.因此对于客户端来说,请求转发只发出了一次请求,且返回的Http状态码是200,效率较高.
- 重定向会在浏览器上产生URL跳转.对于客户端来说,重定向过程中发送了两次请求,且第一次请求返回的Http状态码是302,效率较低.
- 请求转发访问新页面的时候可以使用客户端访问第一个页面时传过来的response数据.
- 但是请求转发只能跳转到本项目目录下的url,不能跨域.