1. Servlet简介
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。
2. Servlet基本使用*
2.1 添加依赖
- 方式1:将TOMCAT_HOME\lib\servlet-api.jar导入到模块中的web\lib
- 方式2:在项目结构中给该模块的Dependencies添加Library,然后选择相应Tomcat配置
2.2 创建javax.servlet.Servlet实现类
- 一般可以继承HttpServlet
package com.liu2m.servlet;
import javax.servlet.*;
import java.io.IOException;
public class ServletImpl implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
servletResponse.getWriter().write("hello, servlet" + System.currentTimeMillis());
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
2.3 配置Servlet实现类的映射路径
-
方式1:在web.xml的web-app标签里配置Servlet实现类的映射路径
ServletImpl com.liu2m.servlet.ServletImpl ServletImpl /impl -
方式2:Servlet3.0以上支持注解配置,可以不用web.xml了;注解配置*:在Servlet实现类上使用@WebServlet("资源路径")注解
@WebServlet(urlPatterns = {"/uri1", "/uri2", "/uri3"})
- 启动Tomcat后Tomcat引擎执行的操作:
- 创建ServerSocket对象,开启指定端口
- Tomcat引擎扫描webapps目录,根据浏览器的URL读取指定项目中的web.xml
- Tomcat引擎扫描所有Servlet接口实现类,获取类上的注解和注解的属性值
- 当Tomcat引擎接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
- 创建Request对象和Response对象
- 依次找servlet-mapping下的url-pattern、servlet-name;servlet下的servlet-name、servlet-class
- 根据反射创建servlet-class指定的对象,调用service方法,传递Request对象和Response对象
- 运行service方法,将数据写在Response对象的缓冲区中
- service方法结束后,Tomcat引擎从Response对象的缓冲区中取出数据,组装HTTP响应信息回传给浏览器
3. Servlet对象生命周期*
-
void init(ServletConfig servletConfig)方法:Servlet对象初始化方法,在对象被创建后调用
只执行一次(说明一个Servlet在内存中只存在一个对象)
-
默认情况下,第一次被访问时Servlet被创建,但也可以在web.xml的
标签下配置Servlet的创建时机 负数 :第一次被访问时创建0或正整数 :在服务器启动时创建 整数越小,启动优先级越高 Servlet是单例的,多个用户同时访问时,可能存在线程安全问题;因此尽量避免在Servlet中使用共享变量
ServletConfig对象可以获取Servlet的初始化参数
service(servletRequest, servletResponse):客户端访问一次就执行一次
-
void destroy():Servlet对象销毁之前调用
- 只执行一次
- 当Servlet从服务器中移除或者服务器正常关闭的时候Servlet对象被销毁,在销毁前会执行destroy方法
- 一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成
4. ServletContext*
- 在Tomcat中的Web项目也叫Web应用程序;Web应用程序对象ServletContext可以描述整个Web应用程序
- 每个Web应用程序只有一个ServletContext对象,在Tomcat启动时就已经被创建好了
- Tomcat启动时会扫描webapps,找所有Web程序并为其创建ServletContext
- ServletContext是接口,实现类由Tomcat引擎提供
- 在ServletContext对象中存储的任何数据,在整个Web应用程序中全有效
-
获取ServletContext实现类对象:
- GenericServlet抽象类的方法:getServletContext()
- ServletConfig接口方法:getServletContext()
-
ServletContext对象作用:
-
作为域对象使多个Servlet可以共享数据*:
// 给ServletContext对象的Map中添加键值对 void setAttribute(String name, Object object) // 从ServletContext对象的Map中根据键取值 Object getAttribute(String name) // 从ServletContext对象的Map中根据键移除键值对 void removeAttribute(String name)
-
获取web项目中文件的真实路径,以及将web项目中的文件转成流*:
// 根据资源名称获取资源的绝对路径,传递相对于webapps下项目的路径 String getRealPath(String path) // 返回指定文件的流 InputStream getResourceAsStream(String path)
-
获取全局的初始化参数:
// 获取初始化参数值,传递参数名;如果参数不存在,则返回null String getInitParameter(String name) 初始化参数配置在web.xml,如:
key value -
获取文件的MIME类型:
// 返回指定文件的MIME类型,如果MIME类型未知,则返回null String getMimeType(String file)
-
5. HttpServlet
- GenericServlet抽象类继承了Servlet接口;HttpServlet抽象类继承了GenericServlet
GenericServlet:将Servlet接口中其他的方法做了默认空实现,只将service()方法作为抽象
-
HttpServlet:对http协议的一种封装,简化操作
定义类继承HttpServlet
重写doGet/doPost方法
-
HttpServlet有两个service方法,先调用重写的父类方法,将其参数向下转型为自己的方法参数类型,然后传递给自己的方法
父类方法:void service(ServletRequest req, ServletResponse res) 自己方法:void service(HttpServletRequest req, HttpServletResponse resp)
- 通常可以使用同一段代码来处理同一个请求的不同请求方式,如在doGet中调用doPost或在doPost中调用doGet
6. ServletRequest
6.1 获取请求行
-
ServletRequest方法:
// 获取客户端的IP地址* String getRemoteAddr() // 获取服务端的端口号 int getServerPort()
-
HttpServletRequest方法:
// 获取请求方式* String getMethod() // 获取站点的根路径(Application context)* String getContextPath() // 获取请求的URI* // 如:/servlet/request String getRequestURI() // 获取请求的URL // 如:http://localhost:8080/servlet/request StringBuffer getRequestURL() // 获取全部请求参数(get请求的URL中,?后面的内容) String getQueryString()
6.2 获取请求头*
-
HttpServletRequest方法:
// User-Agent:浏览器的信息 // Referer:该请求来自哪里;可以防盗链、防止恶意请求 // Content-Type:指示资源的MIME类型 // Authorization:HTTP授权的授权证书 String getHeader(String name)
6.3 获取请求体(获取请求参数)*
-
ServletRequest方法:
// 获得指定参数名对应的值;如果没有则返回null,如果有多个则获得第一个 String getParameter(String name) // 获得指定参数名对应的所有的值;此方法专门为复选框提供 String[] getParameterValues(String name) // 获得所有的请求参数;key为参数名;value为key对应的所有值 Map
getParameterMap() // 设置请求体的编码 // Tomcat8之前,get和post请求都会发生中文乱码 // Tomcat8+,get不会发生中文乱码,post还会发生中文乱码 // 解决中文乱码:在获取请求参数之前,设置请求体的编码为"UTF-8" void setCharacterEncoding(String env) -
使用BeanUtils将请求参数封装到一个JavaBean中:
创建一个POJO类,类中的属性名和请求参数名相对应 使用BeanUtils需要导入jar包,如: commons-beanutils-1.8.3.jar commons-beanutils-bean-collections-1.8.3.jar commons-logging-1.2.jar BeanUtils类静态方法: // 参数1为一个JavaBean对象 // 参数2为getParameterMap获取的Map
static void populate(Object bean, Map properties)
6.4 作为域对象共享数据*
- ServletRequest只能在同一次请求中作为域对象共享数据,可以配合请求转发一起使用
-
ServletRequest方法:
Object getAttribute(String name) void setAttribute(String name, Object o) void removeAttribute(String name)
6.5 请求转发*
- 请求转发特点:
- 请求转发的跳转是由服务器发起的,因此跳转操作不会发起新的请求,浏览器地址栏不会发生变化
- 请求转发只能跳转到本项目中的任意资源,包括WEB-INF中的资源,不能跳转到其他地方
- 请求转发可以和ServletRequest域对象一起使用
-
ServletRequest方法:
// 获取一个指定路径资源的RequestDispatcher对象 RequestDispatcher getRequestDispatcher(String path) RequestDispatcher的方法: // 将请求从一个servlet转发到服务器上的另一个资源 void forward(ServletRequest request, ServletResponse response) 请求转发使用示例: request.getRequestDispatcher("/本项目资源路径").forward(request, response);
7. ServletResponse
7.1 设置状态行
-
HttpServletResponse方法:
// 设置此响应的状态码 void setStatus(int sc)
7.2 设置响应头
-
HttpServletResponse方法:
// 用给定名称和值设置响应头。如果已经设置了头,则新值将重写以前的值 // Refresh:指定秒之后重定向,格式:秒数; url=网址 // Location:服务器告诉浏览器重定向到Location指定的URL // Content-Disposition:指示客户端下载文件,格式:attachment;filename=文件名 // Content-Type:设置响应内容的MIME类型 void setHeader(String name, String value) // 设置将要发送到客户端的响应的内容类型 void setContentType(String type)
7.3 设置响应体
-
ServletResponse方法:
// 返回适用于在响应中编写二进制数据的输出流 ServletOutputStream getOutputStream() // 返回可将字符文本发送到客户端的PrintWriter对象 PrintWriter getWriter() 以上两个流是互斥的,页面输出只能使用其中的一个流 解决字符流输出的中文乱码: 在响应之前设置: response.setContentType("text/html;charset=UTF-8");
-
浏览器下载文件思路:
获取文件名
-
设置响应报文的Content-Disposition,并将文件名编码;不同浏览器请求头内容的编码、解码方式不同
java.net.URLEncoder类静态方法: // 使用特定的编码方案将字符串转换为application/x-www-form-urlencoded格式 static String encode(String s, String enc)
使用ServletContext对象获取文件的MIME类型,并设置为响应报文的Content-Type
使用ServletContext对象获取指定文件名的输入流is
通过response获取输出流os
将is文件复制到os
关闭os和is
7.4 请求重定向
- 请求重定向特点:
- 重定向的跳转是由浏览器发起的,因此跳转操作会发起新的请求,浏览器地址栏会变成跳转到的地址
- 重定向跳转可以跳转到任意地方(访问另一个项目中的资源时要使用完整的URL),但不能跳转到WEB-INF中的资源
- 重定向跳转不能和ServletRequest域对象一起使用
-
HttpServletResponse方法:
// 使用指定重定向URL将临时重定向响应发送到客户端 void sendRedirect(String location)
8. 会话
- 从打开浏览器访问某个站点,到关闭浏览器的整个过程,称为一次会话
- 会话技术就是记录一次会话中客户端的状态与数据;会话技术分为Cookie和Session:
- Cookie:将一次会话的数据存储在浏览器端;大小和个数都有限制;只能存储字符串且不支持中文;不是很安全
- Session:将一次会话的数据存储在服务器端;原则上大小是没有限制;可以存储基本数据类型和对象;相对较安全
8.1 Cookie
Cookie是一种客户端的会话技术,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去
Cookie本质是响应头的Set-Cookie和请求头的Cookie
-
服务器往浏览器存储Cookie:
- 创建一个Cookie对象:new Cookie(String name, String value)
- 设置Cookie对象的有效期:void setMaxAge(int expiry)
- -1:代表将Cookie数据保存到浏览器文件中直到浏览器关闭;默认
- 正整数:代表将数据以秒为单位保存到保存到磁盘中直到有效期到期
- 0:代表删除浏览器端同name、同path的Cookie
- 设置Cookie对象的有效路径:void setPath(String uri)
- 将Cookie添加到HttpServletResponse中
-
服务器从浏览器的请求中获取Cookie:
- 从HttpServletRequest中获取浏览器带过来的所有Cookie
- 遍历出每一个Cookie对象
- 根据getName()获取key;根据getValue()获取值
name和path相同的Cookie会被覆盖
当Cookie被浏览器禁用时,可以将Cookie的信息设置在URL中响应给浏览器
-
Cookie类:
常用构造: // 创建一个Cookie对象(Cookie只能保存字符串,且不能保存中文) Cookie(String name, String value) 常用方法: // 获取Cookie中设置的key String getName() // 获取Cookie中设置的value String getValue() // 设置Cookie对象的有效期:expiry是秒 void setMaxAge(int expiry) // 设置Cookie对象的有效路径:uri使用绝对路径 // 一般可以设置为当前项目路径:cookie.setPath(request.getContextPath()) void setPath(String uri)
-
将Cookie添加到HttpServletResponse中的方法:
void addCookie(Cookie cookie)
-
从HttpServletRequest中获取浏览器带过来的所有Cookie的方法:
Cookie[] getCookies()
-
自定义Cookie工具类:
import javax.servlet.http.Cookie; /** * 自定义Cookie工具类 */ public class CookieUtils { public static Cookie createAndSetCookie(String name, String value, int expiry, String uri) { // 1. 创建一个Cookie对象 Cookie cookie = new Cookie(name, value); // 2. 设置Cookie对象的有效期 cookie.setMaxAge(expiry); // 3. 设置Cookie对象的有效路径 cookie.setPath(uri); return cookie; } public static String getCookieValue(Cookie[] cookies, String name) { String value = null; if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals(name)) { value = cookie.getValue(); break; } } } return value; } }
-
示例:使用Cookie记录上一次访问时间
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 解决响应的中文乱码 response.setContentType("text/html;charset=UTF-8"); // 使用自定义Cookie工具类获取name对应的值 String name = "lastTime"; Cookie[] cookies = request.getCookies(); String value = CookieUtils.getCookieValue(cookies, name); // 判断是否为第一次访问 if (value != null) { response.getWriter().println("上次访问时间:" + value); } else { response.getWriter().println("第一次访问"); } // 设置Cookie的新值为当前时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd/HH:mm:ss"); String currentTime = sdf.format(new Date()); Cookie timeCookie = CookieUtils.createAndSetCookie(name, currentTime, 60 * 60, request.getContextPath()); // 将新创建的Cookie添加到response中 response.addCookie(timeCookie); }
8.2 Session
Session是一种服务器端的技术。服务器为每一个浏览器开辟一块内存空间,即Session对象,每一个Session对象都对应一个JSESSIONID,服务器把JSESSIONID存储到Cookie中,再次访问的时候,浏览器把JSESSIONID带过来,找到对应的Session对象。
由于Session就是一个基于Cookie的域对象,因此其作用范围默认为一次会话;当本次会话结束后,浏览器端的Cookie没有了,但Session对象还在服务器中;可以手动设置JSESSIONID这个Cookie的有效时长
-
Session对象销毁时机:
- 默认闲置30分钟后销毁,也可以调用HttpSession的setMaxInactiveInterval(int interval)方法修改
- 调用HttpSession的invalidate()方法销毁
- 服务器异常关闭销毁
-
Session的执行过程:
- 当HttpServletRequest调用getSession()时,会判断本次请求中是否有一个名为JSESSIONID的Cookie
- 如果没有,则创建Session对象,并将该Session对象的JSESSIONID以Cookie形式响应给浏览器
- 如果有,则根据JSESSIONID寻找指定的Session对象
- 如果指定的Session对象存在,则直接使用
- 如果指定的Session对象销毁了,则执行第2步
-
HttpServletRequest获取Session的方法:第一次调用时创建session,之后调用通过sessionId找到Session进行使用
HttpSession getSession()
-
HttpSession在一次会话作为域对象存取数据的方法:
Object getAttribute(String name) void setAttribute(String name, Object value) void removeAttribute(String name)
-
防止表单重复提交思路:
- 方式1:点击表单页面时,给session中存储一个标志如subToken,并设置给表单页面,表单提交时携带subToken,服务器判断session中的subToken是否与表单携带来的一致,若一致则提交成功并清除session中的subToken,这样重复提交时session中的subToken与表单携带来就不一致了
- 方式2:给数据库增加唯一键约束,确保数据库只可以添加一条数据
- 方式3:提交成功后重定向
- 方式4:使用JavaScript设置标志位,提交后屏蔽提交按钮
- 方式5:使用AOP自定义切入
-
使用Session校验验证码思路:
-
创建一个获取验证码并将验证码的内容存储到session域对象中的Servlet
1. 导入验证码jar包,如:ValidateCode.jar 2. 在Servlet中创建验证码并返回给浏览器 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 创建验证码图片 ValidateCode validateCode = new ValidateCode(200, 50, 4, 10); // 2. 将验证码的内容存储到session域对象中 String code = validateCode.getCode(); request.getSession().setAttribute("code", code); // 3. 将验证码图片通过response的字节流输出到浏览器 validateCode.write(response.getOutputStream()); // 4. 在img的src属性中写访问这个Servlet的路径 }
创建html页面,在img的src属性中写访问步骤1中Servlet的路径
在校验Servlet中,获取html页面输入的验证码内容并和Session中的验证码内容进行忽略大小写的比较
切换图片即重新设置img的src属性,可以使用js的点击事件完成;为了防止缓存,可以在src的url后面加上请求参数,让每次的url不同
-
9. Servlet抽取思想
Servlet抽取思想:在一个Servlet实现类中自定义不同方法来处理不同请求
- 在每个请求中添加参数来标识要调用的方法
- 获取客户端要调用的方法名
- 使用反射,根据方法名获取方法
- 执行获取到的方法
try {
// 获取客户端要调用的方法名
String methodName = request.getParameter("method");
// 使用反射,根据方法名获取方法
Method method = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
// 执行获取到的方法;执行的是this的方法
method.invoke(this, request, response);
} catch (Exception e) {
e.printStackTrace();
}