文章目录
Servlet
Servlet是Java规范(可以理解成接口),定义了Java类被浏览器访问到(也就是被tomcat识别到)的规则
例子
1.建一个JavaEE WEB项目
2.
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
3.web.xml配置
<!--配置servlet-->
<servlet>
<servlet-name>demo1</servlet-name>
<servlet-class>cn.example.servlet.ServletDemo1</servlet-class>
</servlet>
<!--资源路径-->
<servlet-mapping>
<servlet-name>demo1</servlet-name>
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
4
public class ServletDemo1 implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init-demo1");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
5 配置tomcat
6 浏览器方法
浏览器访问http://localhost:8888/demo1就能自动执行到ServletDemo1中的service方法的原理是:
请求先通过这个xml,找到url-pattern为demo1的资源名称,即demo1,然后去找哪个servlet的name是demo1,然后取对应的class,即cn.example.servlet.ServletDemo1。tomcat将这个全类名对应的字节码文件加载进内存(class.forName()),创建对象(cls.newInstance()),调用service方法,因为我们实现了Servlet接口,所以tomcat就能知道这个类中肯定有service方法(这里就能理解为什么说Servlet是规则)。
所以Servlet的运行需要依赖web容器,也就是tomcat,去完成对象加载、创建、方法调用过程。
Servlet执行原理:
- 当服务器接收到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
- 查找web.xml文件,找对应的
<url-pattern>
标签 - 然后找
<servlet-class>
中配置的全限定类名 - tomcat对这个类进行加载、创建对象、调用方法
Servlet生命周期
public class ServletDemo2 implements Servlet {
/**
* 初始化方法,Servlet创建的时候执行
*/
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("init-demo2");
}
/**
* 获取Servlet配置对象
*/
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 提供服务的方法
*/
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("hello demo2");
}
/**
* 获取Servlet信息,版本、作者等
*/
@Override
public String getServletInfo() {
return null;
}
/**
* 在服务器正常关闭时,执行一次
*/
@Override
public void destroy() {
System.out.println("destroy");
}
}
Servlet生命周期:
- 创建:执行init方法,只执行一次
- 提供服务:执行service方法,执行多次
- 销毁:执行destroy方法,只执行一次
Servlet被创建时机:
- 第一次被访问的时候
- 自定义配置:
<servlet>
<servlet-name>demo2</servlet-name>
<servlet-class>cn.example.servlet.ServletDemo2</servlet-class>
<!--负数:表示第一次访问时Servlet被创建;正数或0:服务器启动的时候Servlet被创建-->
<load-on-startup>1</load-on-startup>
</servlet>
Servlet是单例的,所以最好不用在Servlet类中定义成员变量,否则导致多线程安全问题
Servlet注解配置
Servlet3.0+支持注解配置,直接用@WebServlet
注解
//@WebServlet(urlPatterns="/demo3")
//@WebServlet("/demo3")
//@WebServlet({"/d4","/dd4","/ddd4"})
//@WebServlet("/user/demo4")
//@WebServlet("/user/*")
//@WebServlet("/*") 通配符
@WebServlet("*.do")
public class ServletDemo3 implements Servlet {
//...
}
Servlet继承体系
public interface Servlet {}
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
// 只对service()方法声明为抽象的,对其他Servlet中的方法做了默认实现
}
HttpServlet 是对HTTP协议的封装,在service方法中判断请求方式,然后分派到不同的方法中,所以我们使用的时候只需要重写相关的doGet()、doPost()等方法即可
public abstract class HttpServlet extends GenericServlet {
//...
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
}
servletRequest和servletResponse
servletRequest和servletResponse是service()中的两个参数,加上这两个参数,在细说下Servlet执行原理:
- 当服务器接收到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
- 查找web.xml文件,找对应的
<url-pattern>
标签 - 然后找
<servlet-class>
中配置的全限定类名 - tomcat对这个类进行加载、创建对象
- tomcat会创建request和response对象,request对象中封装请求消息数据,response用来设置响应消息
- tomcat将request和response两个对象传递给service方法,并且调用service方法
- 我们在可以通过request对象获取请求消息数据,然后通过response对象设置响应消息数据
- 服务器在给浏览器做出响应之前,会从response对象中拿我们设置的响应消息数据
servletRequest对象的方法
获取请求行数据:
GET /day14/demo1?name=zhangsan HTTP/1.1
- 获取请求方式 :GET
String getMethod() - 获取虚拟目录:/day14
String getContextPath() - 获取Servlet路径: /demo1
String getServletPath() - 获取get方式请求参数:name=zhangsan
String getQueryString() - 获取请求URI:/day14/demo1
String getRequestURI(): /day14/demo1
StringBuffer getRequestURL() :http://localhost/day14/demo1 - 获取协议及版本:HTTP/1.1
String getProtocol() - 获取客户机的IP地址:
String getRemoteAddr()
获取请求头数据:
- String getHeader(String name):通过请求头的名称获取请求头的值
- Enumeration< String > getHeaderNames():获取所有的请求头名称
获取请求体数据,先要获取流对象,再从流对象中拿数据。获取流对象的方法如下两种(用在post请求方式中):
- BufferedReader getReader():获取字符输入流,只能操作字符数据
- ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据(比如上传的文件)
其他功能:
(1)获取请求参数的通用方法(get和post方式都能使用)
- String getParameter(String name):根据参数key获取value,形如username=z&password=1
- String[] getParameterValues(String name):根据key获取参数数组 ,形如复选框hobby=a&hobby=b
- Enumeration< String > getParameterNames():获取所有参数key-value
- Map<String, String[]> getParameterMap():获取所有参数的map集合
Enumeration< String > getParameterNames() 用法举例
<form action="/day14/requestDemo7" method="post">
<input type="text" placeholder="请输入用户名" name="username"><br>
<input type="text" placeholder="请输入密码" name="password"><br>
<input type="checkbox" name="hobby" value="game">游戏
<input type="checkbox" name="hobby" value="study">学习
<br>
<input type="submit" value="注册">
</form>
Enumeration<String> parameterNames = request.getParameterNames();
while(parameterNames.hasMoreElements()){
String name = parameterNames.nextElement();
System.out.println(name);
String value = request.getParameter(name);
System.out.println(value);
System.out.println("----------------");
}
注意:对于复选框,用getParameter()方法只能获取到一个值
Map<String, String[]> getParameterMap()用法举例
Map<String, String[]> parameterMap = request.getParameterMap();
//遍历
Set<String> keyset = parameterMap.keySet();
for (String name : keyset) {
//获取键获取值
String[] values = parameterMap.get(name);
System.out.println(name);
for (String value : values) {
System.out.println(value);
}
System.out.println("-----------------");
}
可以拿到复选框的所有值
(2)解决post方式中文请求参数乱码问题
对于get方法的请求,tomcat8已经解决了中文请求参数乱码问题
post方式的解决办法是通过request对象设置流的编码:request.setCharacterEncoding("utf-8");
(3)请求转发 forward
请求转发是一种在服务器内部的资源跳转方式,步骤是通过request对象获取请求转发器对象,然后执行forward()方法转发:
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/requestDemo9");
requestDispatcher.forward(request,response);
//合成一个链式调用
request.getRequestDispatcher("/requestDemo9").forward(request,response);
特点:
- 浏览器显示地址栏地址不变
- 转发只能访问当前服务器下的资源
- 转发是一次请求,所以可以使用request对象来共享数据
//通过request对象设置域对象,域的作用范围就是同一次请求中,用于请求转发共享数据
setAttribute(String name, Object obj) // 存储数据到域中
getAttribute(String name) // 获取共享数据
removeAttribute(String name) // 移除数据
servletResponse对象的方法
- 设置状态码:setStatus(int sc)
- 设置响应头:setHeader(String name, String value)
- 设置响应体,先要获取输出流:
字符输出流:PrintWriter getWriter()
字节输出流:ServletOutputStream getOutputStream()
设置响应体的例子
public class ResponseDemo4 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决乱码方式一:获取流对象之前,设置流的默认编码:ISO-8859-1 设置为:GBK
//缺点:限制了浏览器只能用GBK编码
// response.setCharacterEncoding("utf-8");
//解决乱码方式二:告诉浏览器,服务器发送的消息体数据的编码,建议浏览器使用该编码解码
//通过设置响应头content-type,告诉浏览器此次编码方式,并且这种方式还指定了获取流的编码方式
//response.setHeader("content-type","text/html;charset=utf-8");
//解决乱码方式三:下面这种写法是方式二的简化写法
response.setContentType("text/html;charset=utf-8");
//1.获取字符输出流
PrintWriter pw = response.getWriter();
//2.输出数据
pw.write("<h1>hello response</h1>"); //英文可以正常显示
pw.write("你好"); //中文会乱码,response是tomcat返回过来的,流对象也是从tomcat中获取到的,它里边设置的字符集和浏览器默认字符集编码不一样导致了乱码
//1.获取字节输出流
ServletOutputStream sos = response.getOutputStream();
//2.输出数据
sos.write("字节流一般用来输出图片等类型的数据".getBytes("utf-8"));
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
4. 重定向 redirect
特点:
- 地址栏地址发生变化
- 重定向可以定向到其他站点(跨域)
- 重定向是两次请求,不能使用request对象来共享数据
重定向是将新的URL发送给浏览器,浏览器再去请求这个URL:
方式一:
//1.设置状态码为302
response.setStatus(302);
//2.设置响应头location,day15是虚拟目录
response.setHeader("location","/day15/responseDemo2");
方式二:
response.sendRedirect("/day15/responseDemo2");
相对路径和绝对路径
相对路径:
找到当前资源和目标资源之间的相对位置关系,比如当前资源http://localhost/day15/html/responseDemo2
,目标资源http://localhost/day15/responseDemo1
,则用相对路径href="../responseDemo1"
。day15是虚拟目录。
绝对路径:
判断定义的路径是给谁用的,给客户端浏览器使用,如重定向,则需要加虚拟目录,建议虚拟目录动态获取:request.getContextPath()
;给服务器使用,不需要加虚拟目录,如在服务器端转发:request.getRequestDispatcher("/XX[不需要虚拟目录]").forward(request, response);
ServletContext
ServletContext代表整个web应用,可以和程序的容器(服务器)来通信
获取方式:
- 通过request对象获取:request.getServletContext();
- 通过HttpServlet获取:this.getServletContext();
功能:
- 获取MIME类型
MIME类型是在互联网通信过程中定义的一种文件数据类型,格式:大类型/小类型 text/html 、image/jpeg
方法:String getMimeType(String file) - 获取文件的真实(服务器)路径
方法:String getRealPath(String path)
String b = context.getRealPath("/b.txt");//web目录下资源访问
String c = context.getRealPath("/WEB-INF/c.txt");//WEB-INF目录下的资源访问
文件下载案例
@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取请求参数,文件名称
String filename = request.getParameter("filename");
//2.使用字节输入流加载文件进内存
//2.1找到文件服务器路径
ServletContext servletContext = this.getServletContext();
String realPath = servletContext.getRealPath("/img/" + filename);
//2.2用字节流关联
FileInputStream fis = new FileInputStream(realPath);
//3.设置response的响应头
//3.1设置响应头类型:content-type
String mimeType = servletContext.getMimeType(filename);//获取文件的mime类型
response.setHeader("content-type",mimeType);
//3.2设置响应头打开方式:content-disposition
//解决中文文件名问题
//1.获取user-agent请求头、
String agent = request.getHeader("user-agent");
//2.使用工具类方法编码文件名即可
filename = DownLoadUtils.getFileName(agent, filename);
response.setHeader("content-disposition","attachment;filename="+filename);
//4.将输入流的数据写出到输出流中
ServletOutputStream sos = response.getOutputStream();
byte[] buff = new byte[1024 * 8];
int len = 0;
while((len = fis.read(buff)) != -1){
sos.write(buff,0,len);
}
fis.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
}
public class DownLoadUtils {
public static String getFileName(String agent, String filename) throws UnsupportedEncodingException {
if (agent.contains("MSIE")) {
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
} else if (agent.contains("Firefox")) {
// 火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
} else {
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
}
Session和Cookie
Cookie
设置Cookie:
//1.创建Cookie对象
Cookie c = new Cookie("msg","hello");
//2.发送Cookie
response.addCookie(c);
获取Cookie:
//3. 获取Cookie
Cookie[] cs = request.getCookies();
//获取数据,遍历Cookies
if(cs != null){
for (Cookie c : cs) {
String name = c.getName();
String value = c.getValue();
System.out.println(name+":"+value);
}
}
一次可以发送多个Cookie:
Cookie c1 = new Cookie("msg","hello");
Cookie c2 = new Cookie("name","zhangsan");
//2.发送Cookie
response.addCookie(c1);
response.addCookie(c2);
Cookie在浏览器中保存的时间:
- 默认是,当浏览器关了,Cookie就没了
- 设置Cookie的生存时间
//1.创建Cookie对象
Cookie c1 = new Cookie("msg","hello");
//2.设置cookie的存活时间
c1.setMaxAge(30);//正数:将cookie持久化到硬盘,30秒后会自动删除cookie文件
//c1.setMaxAge(-1);//负数:默认值
//c1.setMaxAge(0);//删除cookie信息
Cookie存储中文:
- tomcat8之前不能存中文,一般采用URL编码对中文进行转码
- tomcat之后可以
Cookie共享问题:
- 在一个tomcat服务器中部署了多个web项目,cookie在这些项目下默认是不同享的
- 设置成
cookie.setPath("/");
表示同一tomcat下的项目共享cookie cookie.setDomain(String path)
如果设置一级域名相同,那么多个服务器之间cookie可以共享,如setDomain(".baidu.com"),那么tieba.baidu.com和zhidao.baidu.com共享cookie
Session
Session只能在一次会话的多次请求间共享数据,在一次会话范围内,多次获取Session是同一个对象。
设置Session:
//1.获取session
HttpSession session = request.getSession();
//2.存储数据
session.setAttribute("msg","hello session");
获取Session:
//1.获取session
HttpSession session = request.getSession();
//2.获取数据
Object msg = session.getAttribute("msg");