第四章深入Servlet技术
1.Servlet概述:
是用Java编写的服务器程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容,是JavaWeb的核心程序,是Java Web的三大组件之一(Servlet,Filter,Listener)
作用:用来处理客户端发送来的请求,并对请求做出响应
1.获取客户端的请求数据
2.处理请求
3.将处理的结果通过响应发送给客户端
2.编写第一个Servlet:
1.创建一个类继承HTTPServlet
2.复写HttpServlet中的doGet()、doPost()
3.配置web.xml文件
4.部署项目通过浏览器访问servlet
5.servlet的执行流程
3.使用Servlet3.0创建一个Servlet:
Java EE version最好要在7以上版本
4.HttpServeltRequest获取请求行和请求头:
- 获取请求行相关信息的相关方法:
1.getMethod()方法:返回请求方法,请求方法通常是GET或POST,也有可能是HEAD、 PUT或DELETE
2.getRequestURI()方法:返回URI
3.getRemoteAddr()方法:获取请求客户端的IP地址
4.getRemoteport()方法:获取请求客户端的端口号
5.getLocalAddr():获取服务器当前接收的IP地址
6.getContextPath():用于获取URL中属于web应用程序的路径
7.getProtocol():用于获取请求行中的协议名和版本
//获取请求行
System.out.println("接收到get请求");
System.out.println("请求方式:"+request.getMethod());
System.out.println("URI:"+request.getRequestURI());
System.out.println("发出请求客户端IP地址:"+request.getRemoteAddr());
System.out.println("服务点接收请求的IP地址:"+request.getLocalAddr());
System.out.println("访问客户端的端口号:"+request.getRemotePort());
System.out.println("web应用路径:"+request.getContextPath());
System.out.println("http协议和版本:"+request.getProtocol());
- 获取请求头的相关方法:
1.getHeader(String name):获取指定字段的值,如果请求头不包含该字段则返回null,如果包含多个字段的值则获取第一个值
2.getIntHeader(String name):获取指定头字段的值,并将其转换为Int类型,不存在则返回-1,如果得到的值不能转换为Int,发生NumberFormatException异常
3.getDateHeaders(String name):获取指定头字段的值,并按照GMT时间转换成一个代表日期/时间的长整数
4.getHeaderNames():获取所有包含请求头字段Enumberation
//获取请求头
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()){
String element = headerNames.nextElement();
System.out.println(element+":"+request.getHeader(element));
}
- 获取请求参数:
1.getParameter(String name):获取某个指定名称的参数值,如果请求中没有包含指定名称的参数,则返回Null,如果有指定参数但没有确定的值,则返回空串“”,如果包含多个参数的值则返回第一个值
2.getParameterNames():返回一个包含请求消息中所有参数名的Enumernation
3.getParameterMap():将请求中的所有参数和值装入一个map对象然后返回
在页面中输入账号和密码,点击提交按钮,就会在控制台上打印出获取到的账号和密码参数
//获取请求参数
String name = request.getParameter("name");
String passWord = request.getParameter("passWord");
System.out.println("用户名:"+name);
System.out.println("密码:"+passWord);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
<center>
<h2>发起HTTP get请求</h2>
<form action="hi" method="get">
<p>账号: <input type="text" name="name" /></p>
<p>密码: <input type="text" name="passWord" /></p>
<input type="submit" value="提交" />
</form>
</center>
</html>
5.Response对象发送响应行和响应头和响应消息体:
- 发送响应行:
1.setStatus(int status):当Servlet向客户端回送响应消息时,需要设置一个状态码,该方法用于设置HTTP响应消息的状态码,并生成响应状态行,由于响应状态行中的描述直接和状态码相关,因此只需要设置该方法,就可以发送一个响应行,正常情况下,web服务器会默认发送一个200的状态码
2.sendError(int code):该方法用于发送表示错误信息的状态码,例如404找不到访问资源,还有一种重载形式sendError(int code,String errorMessage),errorMessage可以以文本形式显示在客户端浏览器 - 发送响应头:
1.addHeader(String name,String value):用于设置HTTP协议的响应头字段,其中Name时响应头字段,Value是响应字段的值
2.setHeader(String name,String value):同上,唯一区别在于可以重复添加同名的响应头字段,会覆盖之前添加的同名的响应头
3.setContentLength():设置HTTP响应消息的内容大小,单位是字节
4.setContentType()L设置Servlet输出内容的类型
5.setCharacterEncoding(String charset)
发送响应消息体:
1.getOutputStream()方法:获取字节流输出对象为ServletOutputStream类型,
2.getWrite()方法:获得的字符输出流对象是PrintWriter类型由于它可以直接输出文本类型,因此需要输出网页文档,需要使用这个方法
String result="恭喜您登录成功";
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(result.getBytes());
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(result);
request.setAttribute("name","123");
request.getRequestDispatcher("/index.jsp").forward(request,response);
案例:实现网站登录中验证码的切换功能:
创建一个Servelt:
package net.zixue.servlet;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
@WebServlet(name = "VerifyCodeServlet", urlPatterns = "/code")
public class VerifyCodeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int width = 100;
int height = 30;
String data = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Random random = new Random();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = image.getGraphics();
graphics.setColor(Color.gray);
graphics.fillRect(0, 0, width, height);
graphics.setColor(Color.black);
for (int i = 0; i < 4; i++) {
int position = random.nextInt(data.length());
String randomStr = data.substring(position, position + 1);
graphics.drawString(randomStr, width / 5 * (i + 1), 15);
}
ImageIO.write(image, "jpg", response.getOutputStream());
}
}
前端html页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
<center>
<h2>切换验证码</h2>
<script>
function changeImageCode() {
document.getElementById('btn').isDisabled=true;
document.getElementById('identity').src='code?ts='+new Date().getTime();
}
</script>
<image src="code" id="identity" onload="btn.disable=false;" style="cursor:pointer; vertical-align:middle">
</image>
<input type="button" value="看不清,请求更换验证码" onclick="changeImageCode()" id="btn" style="vertical-align:middle">
</center>
</html>
6.Servlet的生命周期
Servlet接口:
javax.servlet.Servlet接口:
init(ServletConfig)方法,初始化方法
service(ServletRequest,ServletResponse)方法,每次访问都会调用来处理请求
destory()方法:销毁servlet方法
HttpServlet方法接口:javax.servlet.http:
继承自Servlet接口,并重新实现了service方法,根据不同请求方式调用不同的处理方法。
service(HttpServletRequest,HttpServletResponse)方法,获取请求方式,分别调用deGet(),或者doPost()方法
- 当服务器关闭或者项目被移出服务器,destory方法会执行,生命周期结束
代码演示:
package net.zixue.servlet;
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(name = "LifeServlet",urlPatterns = "/life")
public class LifeServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("init 被执行了");
super.init();
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("service 被执行了");
super.service(req, resp);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("do get被执行了");
}
@Override
public void destroy() {
System.out.println("destroy被执行了");
super.destroy();
}
}
7.ServletConfig对象
ServletConfig对象是它所对应的Servlet对象的相关配置信息
特点:
- 每一个servlet对象都有一个ServletConfig对象和它相对应
- ServletConfig对象在多个Servlet对象之间是不能共享的
常见的ServletConfig对象的方法: - getInitParameter(String name):返回一个初始化变量的值
- getInitParameterNames():返回servlet初始化参数的所有名称
- getServletContext():获取ServletContext对象
- getServletNmae():获取Servlet的name配置值
web.xml页面展示
<context-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</context-param>
public class HelloServlet extends HttpServlet{
@Override
public void init() throws ServletException {
super.init();
ServletConfig servletConfig = this.getServletConfig();
String encoding = servletConfig.getInitParameter("encoding");
System.out.println("encoding="+encoding);
}
8.ServletContext-获取项目初始值
ServletContext定义
即Servlet上下文环境,表示当前web应用环境信息
获取ServletContext对象
- 通过ServletConfig的getServletContext()方法可以得到ServletContext对象
- HttpServelt中直接通过this.getServletConext()获取
域对象
ServletContext对象通常称为Context域对象,ServletContext是我们学习的第一个域对象
<context-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</context-param>
package net.zixue.servlet;
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(name = "ServletTest3",urlPatterns = "/test3")
public class ServletTest3 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String encoding = this.getServletContext().getInitParameter("encoding");
this.getServletContext().getInitParameterNames();
System.out.println(encoding);
}
}
9.ServletContext对象-在多个Servlet之间共享数据
这是ServletTest1中定义的name
package net.zixue.servlet;
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(name = "ServletTest1",urlPatterns = "/test1")
public class ServletTest1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String data="xiaozhang";
this.getServletContext().setAttribute("name",data);
}
}
在servletTest2中:
package net.zixue.servlet;
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(name = "ServletTest2",urlPatterns = "/test2")
public class ServletTest2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name =(String)this.getServletContext().getAttribute("name");
System.out.println(name);
}
}
10.ServletContext-读取项目的资源文件
配置文件有两种:
1.web.xml
2.propertise:数据之间没有具体关联
连接数据库:
package net.zixue.servlet;
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;
import java.io.InputStream;
import java.util.Properties;
@WebServlet(name = "ServletTest4",urlPatterns = "/test4")
public class ServletTest4 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
InputStream resourceAsStream = this.getServletContext().getResourceAsStream("/db.properties");//读取文件的路径,返回文件流
Properties properties = new Properties();
properties.load(resourceAsStream);
String name = properties.getProperty("name");
String pass = properties.getProperty("passWord");
String url = properties.getProperty("url");
System.out.println(url);
System.out.println(pass);
System.out.println(name);
}
}
11.请求转发
Servlet之间可以实现跳转,从一个Servlet跳转到另一个Servlet,利用Servlet的跳转技术可以很方便的把一块业务模块分开,比如使用一个Servlet接受用户提交数据,使用另一个Servlet读取数据库,最后跳转到另一个Servlet把结果处理展示出来,MVC模式
-
转发Forward简介:
在Servlet中如当前的web资源不想处理请求时,可以通过forward方法将当前的请求传递给其他的Web资源处理,这样的方式称为请求转发
请求转发的相关方法:
RequestDispatcher对象,可以通过request.getRequestDisptcher()方法获取调用这个对象的forward方法就可以实现请求转发
转发过程中携带数据:
request本身也是一个域对象,request可以携带数据传递给其他web资源
setAttribute方法:
getAttriubute 方法:
removeAttribute方法:
getAttributeNames方法: -
重定向:
重定向是根据服务器返回的状态码来实现的。客户端浏览器在请求服务器的时候,服务端会返回一个状态码,服务器通过HttpServletResponse的setStatus(int status)方法来设置状态码。如果服务器这个时候返回的状态码是301或者是302,则浏览器就会到新的网址重新请求该资源。服务的响应中会带着这个新资源的地址。 -
请求转发和重定向的区别:
1.重定向的地址栏会发生变化,转发不会
2.重定向是两次请求两次响应,转发是一次请求一次响应
3.重定向路径需要加工程名,转发路径不需要加工程名
4.重定向可以跳转到任何网站,转发只能在服务器内部进行
5.图示区别:
12.实验3-登录错误时显示错误界面
登录的页面代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
<center>
<h2>用户登录</h2>
<form action="login" method="get">
<p>账号: <input type="text" name="name" /></p>
<p>密码: <input type="text" name="passWord" /></p>
<input type="submit" value="登录" />
</form>
</center>
</html>
Login.Servlet
package net.zixue.servlet;
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(name = "LoginServlet",urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userName = "xiaoming";
String userPass = "123456";
String name = request.getParameter("name");
String passWord = request.getParameter("passWord");
if(!name.equals(userName))
{//账户不存在
request.setAttribute("errorMessage","账户不存在");
request.getRequestDispatcher("/loginError.jsp").forward(request,response);
}else if(!passWord.equals(userPass)){
//密码错误
request.setAttribute("errorMessage","密码错误");
request.getRequestDispatcher("/loginError.jsp").forward(request,response);
}
}
}
13.实验4-登录后跳转网站首页
package net.zixue.servlet;
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(name = "LoginServlet",urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userName = "xiaoming";
String userPass = "123456";
String name = request.getParameter("name");
String passWord = request.getParameter("passWord");
if(!name.equals(userName))
{//账户不存在
request.setAttribute("errorMessage","账户不存在");
request.getRequestDispatcher("/loginError.jsp").forward(request,response);
}else if(!passWord.equals(userPass)){
//密码错误
request.setAttribute("errorMessage","密码错误");
request.getRequestDispatcher("/loginError.jsp").forward(request,response);
}
else{
//实现请求重定向方法
// response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
// response.setHeader("Location","https://www.baidu.com/");//会跳转到百度的页面
response.sendRedirect("/hello/home.html");
}
}
}
14.网页自动刷新
在遇到页面乱码问题时的解决办法:
在方法体内插入这段话即可解决乱码问题
response.setContentType("text/html;charset=utf-8");
package net.zixue.servlet;
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(name = "RefreshServlet",urlPatterns = "/refresh")
public class RefreshServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String message="<meta http-equiv='refresh' content='3;url=/hello/home.html'>3秒后会自动跳回首页</meta>";
request.setAttribute("message",message);
request.getRequestDispatcher("/index.jsp").forward(request,response);
}
private void RefreshDemol(HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=utf-8");
response.setHeader("refresh","3;url='/hello/home.html");
response.getWriter().print("3秒钟自动刷新");
}
}
15.Servlet线程安全
当多个客户端并发访问一个Servlet时,web服务器会为每一个客户端的访问创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内部如果访问了同一个资源的话,就可能引发线程安全问题
package net.zixue.servlet;
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(name = "ThreadServlet",urlPatterns = "/thread")
public class ThreadServlet extends HttpServlet {
int i=0;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
synchronized (this)
{
i++;//第一次访问i已经是1,第二次访问i=2
try{
Thread.sleep(5*1000);
}catch(InterruptedException e)
{
e.printStackTrace();
}
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(i+"");
}
}
}
16.实验5-文件下载
1.通过文件输入输出流将文件返回给客户端浏览器
2.通过对应的http响应头通知浏览器下载该文件
- 告知浏览器文件的类型:response.setContentType(文件的MIME类型)
- 告知浏览器文件的打开方式是下载:
response.setHeader(“Content-Disposition”,“attachment;filename=文件名称”);
package net.zixue.servlet;
import jdk.management.resource.internal.inst.FileInputStreamRMHooks;
import sun.misc.BASE64Encoder;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
@WebServlet(name = "DownloadServlet",urlPatterns = "/download")
public class DownloadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//如何将服务器上的资源返回给浏览器
// response.getWriter();//输出字节码文件的
// response.getOutputStream();//输出二进制文件的
String filename=request.getParameter("filename");
String filenameEncoder = URLEncoder.encode(filename, "utf-8");
//如何通知浏览器不要预览,而是下载文件
String mimeType = this.getServletContext().getMimeType(filename);
response.setContentType(mimeType);
response.setHeader("Content-Disposition","attachment;filename="+filenameEncoder);
//通过ServletContext对象获取文件的绝对路径
String realPath = this.getServletContext().getRealPath("download/"+filename);
InputStream in=new FileInputStream(realPath);
ServletOutputStream outputStream = response.getOutputStream();//输出二进制文件的
//文件拷贝
int len=0;
byte[] buffer=new byte[1024];
while ((len=in.read(buffer))>0){
outputStream.write(buffer,0,len);
}
in.close();
}
}