目录
一、Servlet介绍
什么是Servlet?
- Servlet是Java提供的一门动态Web资源开发技术
- Servlet是JavaEE规范之一,其实就是一个接口,我们需要定义Servlet类实现Servlet接口,并且Web服务器运行Servlet。
二、Servlet原生方式开发步骤
1、创建web项目,导入Servlet依赖坐标
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
2、创建:定义一个类实现Servlet接口,并重写接口中所有方法。
public class ServletDemo implements Servlet {
/**
* 初始化方法
* 1.调用时机:默认情况下,Servlet被第一次访问时调用(更改loadOnStartup) ·
* 2.调用次数:1次 */
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
/**
* 提供服务
* 1、调用时机:每一次Servlet被访问时调用
* 2、调用次数:多次 */
@Override
public void service(ServletRequest servletRequest,
ServletResponse servletResponse) throws ServletException, IOException {
// 根据请求方式不同,进行分别的处理
HttpServletRequest request = (HttpServletRequest) servletRequest;
// 1. 获取请求方式
String method = request.getMethod();
// 2. 判断请求方式
if("GET".equals(method)){
// GET 请求方式处理逻辑
}
else if("POST".equals(method)){
// POST 请求方式处理逻辑
}
}
/**
* 销毁服务
* 1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁调用
* 2.调用次数:1次*/
@Override
public void destroy() {
}
/**
* 获取 ServletConfig 对象*/
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 获取 Servlet信息*/
@Override
public String getServletInfo() {
return null;
}
}
3、配置:在类上使用@WebServlet注解,配置该Servlet的访问路径
@WebServlet("/资源访问路径")
public class ServletDemo implements Servlet
三、Servlet执行流程&生命周期
1. Servlet由谁创建?Servlet方法有谁调用?
- Servlet由Web服务器创建,Servlet方法由Web服务器调用
2. 服务器怎么知道Servlet 中一定有service方法?
- 自定义的Servlet必须实现Servlet接口并复写方法,而Servlet中有service方法
Servlet生命周期
- 对象的生命周期指一个对象从被创建到被销毁的整个过程
- Servlet运行在Servlet容器(Web服务器)中,其生命周期由容器来管理,分为4个阶段:
①加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建
Servlet对象
。
可在@WebServlet
注解上设置loadOnStartup
的值来设定什么时候创建Servlet对象
②初始化:在Servlet实例化之后,容器将调用Servlet的init()
方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只会调用一次。
③请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()
方法对请求做出处理。
④服务终止:当需要释放内存或容器关闭时,容器就会调用Servlet实例的destroy()
方法完成资源的释放。在destroy()
方法调用之后,容器将会释放这个Servlet实例,该实例随后会被Java的垃圾收集器回收。
四、Servlet体系结构
我们在开发
B/S架构
的Web项目时,都是针对于HTTP协议
,所以我们自定义Servlet,会继承HttpServlet
。
1. 继承HttpServle书写Servlet开发步骤
①继承HttpServlet
②重写doGet和doPost方法(若有其他请求方式,也直接重写即可,例如doPut()
、doDelete()
)
@WebServlet("/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
2. Servlet urlPattern配置
- Servlet要被访问,必须配置其访问路径(urlPattern)
1. 一个Servlet,可以配置多个urlPattern
@WebServlet(urlPattern = {"/demo1", "/demo2" ...})
2. urlPattern配置规则
① 精确匹配
- 配置路径:
- 访问路径:
== ② 目录匹配==
- 配置路径:
- 访问路径:
③ 扩展名匹配
- 配置路径:
- 访问路径:
④ 任意匹配
- 配置路径:
- 访问路径:
五、Servlet重要对象
1. Request
Request对象常用方法
Requesst用于获取请求数据。例如:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 使用 Request 对象获取请求数据
String name = req.getParameter("name");
}
- Request对象获取请求行数据的方法:
// 获取请求方式
String getMethod();
// 获取虚拟目录(项目访问路径):/servlet_study
String getContextPath();
// 获取URL(统一资源定位符):http://localhost:8080/servlet_study/demo
StringBuffer getRequestURL();
// 获取URI(统一资源标识符):/servlet_study/demo
String getRequestURI();
// 获取请求参数(GET方式):username=zs&age=18
String getQueryString();
- Request对象获取请求头数据的方法:
// 根据请求头名称,获取值
String getHeader(String name);
- Request对象获取请求体数据的方法:
// 获取字节输入流
ServletInputStream getInputStream();
// 获取字符输入流
BufferedReader getReader();
测试代码:
@WebServlet("/demo")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
String contextPath = req.getContextPath();
StringBuffer requestURL = req.getRequestURL();
String requestURI = req.getRequestURI();
String queryString = req.getQueryString();
String userAgent = req.getHeader("user-agent");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 获取字符输入流
BufferedReader reader = req.getReader();
// 2. 读取数据
String line = reader.readLine();
}
}
Request通用方式获取请求参数
Map<String,String[]> getParameterMap
:获取所有参数的Map集合String[] getParameterValues(String name)
:根据名称获取参数值(数组)String getParameter(String name)
:根据名称获取参数值(单值)
// 获取所有参数的Map集合
Map<String, String[]> map = req.getParameterMap();
// 根据名称获取参数值(数组)
String[] values = req.getParameterValues("user");
// 根据名称获取参数值(单值)
String value = req.getParameter("user");
请求参数中文乱码问题
- 请求参数如果存在中文数据,则会乱码。
- 解决方案:
POST请求:设置输入流的编码
req.setCharacterEncoding("utf-8");
GET请求:转换字符编码形式(先以
ISO-8859-1
形式编码,再以utf-8
形式解码)
// 两种方式
// 1.
String value = req.getParameter("user");
String encode = URLEncoder.encode(value, "ISO-8859-1");
value = URLDecoder.decode(encode, "utf-8");
// 2.
value = new String(value.getBytes(StandardCharsets.ISO_8859_1),
StandardCharsets.UTF_8);
// value 就是最终形式正常的数据
请求转发
- 请求转发(forward):一种在服务器内部的资源跳转方式
- 实现方式:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.getRequestDispatcher("资源B路径").forward(req, resp);
}
- 请求转发资源间共享数据:使用Request对象
// 存储数据到 request 域中
void setAttribute(String name, Object o);
// 根据 key,获取值
Object getAttribute(String name);
// 根据 key,删除该键值对
void removeAttribute(String name);
- 请求转发特点
- 浏览器地址栏路径不变化
- 只能转发到当前服务器的内部资源
一次请求
,可以在转发的资源间使用request
共享数据
2. Response
Response对象常用方法
Response用于设置响应数据。例如:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 使用 Response对象设置响应数据
resp.setHeader("content-type", "text/html;charset=utf-8");
resp.getWriter().write("<h1>hello, world</h1>");
}
- Response对象设置响应行数据的方法
// 设置响应状态码
void setStatus(int sc);
- Response对象设置响应头数据的方法
// 设置响应头键值对
void setHeader(String name, String value);
- Response对象设置响应体数据的方法
// 获取字符输出流
PrintWriter getWriter();
// 获取字节输出流
ServletOutPutStream getOutPutStream();
Response 响应字符数据
- 使用
①通过Response对象获取字符输出流
PrintWriter writer = resp.getWriter();
②写数据
writer.write("xxxxxxxxxxxx");
注意:
- 该流
不需要关闭
,随着响应结束,response对象销毁,由服务器关闭。
测试代码
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("<h1>Hello</h1>");
}
Response 响应字节数据
- 使用
①通过Response对象获取字节输出流
ServletOutputStream outputStream = response.getOutputStream();
②写数据
outputStream.write(字节数据);
测试代码
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 读取文件
FileInputStream fis = new FileInputStream("jpg路径");
// 获取字节输出流
ServletOutputStream outputStream = response.getOutputStream();
// 完成流的copy
byte[] buff = new byte[1024];
int len = 0;
while((len = fis.read(buff)) != -1){
outputStream.write(buff, 0, len);
}
fis.close();
}
- IOUtils工具类使用
①导入坐标
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
②使用
IOUtils.copy(输入流,输出流);
测试代码
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 读取文件
FileInputStream fis = new FileInputStream("jpg路径");
// 获取字节输出流
ServletOutputStream outputStream = response.getOutputStream();
IOUtils.copy(fis, outputStream);
fis.close();
}
响应数据中文乱码问题
响应数据如果存在中文数据,则会乱码。是因为通过response获取的字符输出流默认编码为ISO-8859-1
。
- 解决方案:
// 两种方式:
// 1.
resp.setContentType("text/html;charset=utf-8");
// 2.
resp.setHeader("contentType", "text/html;charset=utf-8");
重定向
- 重定向(redirect):一种资源跳转方式
- 实现方式:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 设置响应状态码
resp.setStatus(302);
// 2. 设置响应头
resp.setHeader("location", "资源B的路径");
// 或者直接使用下方一句代码
resp.sendRedirect("资源B的路径");
}
- 重定向特点
- 浏览器地址栏路径发生变化
- 可以重定向到任意位置的资源(服务器内外部均可)
两次请求
,不能在多个资源间使用request
共享数据
3. 请求转发与重定向路径问题
- 明确路径使用对象
- 浏览器使用:需要加虚拟目录(项目访问路径)
- 服务端使用:不需要加虚拟目录
在请求转发中,是服务器内部完成资源路径的跳转的行为。因此在请求转发中不需要加虚拟目录。
// 不需要加虚拟目录
req.getRequestDispatcher("路径");
重定向是一种服务器指导客户端进行资源路径跳转访问的行为,还是浏览器自身在使用。因此在重定向中需要加虚拟目录。
// 需要加虚拟目录
resp.sendRedirect("路径");
六、Servlet代码优化
- 在通过继承HttpServlet书写Servlet的类的过程中,通常是一个类只能完成一个需求,这会导致web层的Servlet数量太多,不利于管理与编写。
- 解决方法:将Servlet进行归类,对于同一个实体的操作方法,写到一个Servlet中。比如:UserServlet、BrandServlet,并将其
urlPattern
配置为目录匹配
。之后自定义一个BaseServlet
类,使用请求路径进行方法分发
,替换HttpServlet的根据请求方式
进行方法分发
- 在通过继承HttpServlet书写Servlet的类时,如何根据
请求方式
执行doGet()
或doPost()
是通过HttpServlet中的service()
方法确定的。因此我们要完成通过请求路径
进行方法分发
的需求,需要重写service()
方法。
原
service()
方法如图:
优化步骤
① 自定义
BaseServlet
类,替换HttpServlet,使用请求路径进行方法分发
/**
* 替换 HttpServlet, 根据请求路径进行方法分发
*/
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取请求路径
String uri = req.getRequestURI();
// 2. 获取方法名 -- 最后一段路径
String methodName = uri.substring(uri.lastIndexOf('/') + 1);
// 3. 执行方法
// 3.1 获取相应实体类Servlet字节码对象
// 谁调用我(this 所在的方法),this 就代表谁
Class<? extends BaseServlet> cls = this.getClass();
// 3.2 获取方法 Method 对象
try {
Method method = cls.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
// 3.3 执行方法
method.invoke(this, req, resp);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
② 定义实体类Servlet并继承
BaseServlet
,并将其urlPattern
配置为目录匹配
@WebServlet("/brand/*")
public class BrandServlet extends BaseServlet{
public void selectAll(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
public void add(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {;
}
public void update(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
public void delete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
// ............................
}
之后在每次的请求中,服务器都会先执行重写的service()
方法,根据请求路径进行方法分发。
七、 XML配置方式
- Servlet从3.0版本开始支持使用注解配置,3.0版本前只支持XML配置方式。
- 步骤
① 编写Servlet类
② 在web.xml中配置该Servlet
<servlet>
<servletr-name>定义的Servlet应用名字</servletr-name>
<servletr-class>对应的具体Servlet类文件</servletr-class>
</servlet>
//地址映射
<servlet-mapping>
<servletr-name>定义的Servlet应用名字</servletr-name>
<url-pattern>访问路径</url-pattern>
</servlet-mapping>