JavaWeb--Servlet
JavaWeb--Servlet
为了实现动态和前端用户交流的服务器程序,定制了servlet服务器程序规范(类似JDBC接口)
Tomcat等java程序遵守servlet接口规范,它可以直接调用servlet实现类,我们可以编写实现servlet子类,嫁接到不同的服务器容器使用(指Tomcat类的web服务器程序)
浏览器请求Servlet流程UML
浏览器请求静态资源的流程分析
浏览器调用Servlet流程分析
生命周期
-
init() 初始化阶段
-
Servlet 容器(比如: Tomcat)加载 Servlet,加载完成后,Servlet 容器会创建一个 Servlet 实例 并调用 init()方法,init()方法只会调用一次, Servlet 容器在下面的情况装载 Servlet:
-
Servlet 容器(Tomcat)启动时自动装载某些servlet,实现这个需要在 web.xml 文件中添加
<load-on-startup>1</load-on-startup> 1 表示装载的顺序
-
在Servlet 容器启动后,浏览器首次向 Servlet 发送请求
-
Servlet 重新装载时(比如 tomcat 进行 redeploy【redeploy 会销毁所有的 Servlet 实例】), 浏览器再向 Servlet 发送请求的第 1 次
-
-
-
service()处理浏览器请求阶段
- 每收到一个 http 请求,服务器就会产生一个新的线程去处理[线程]
- 创建一个用于封装 HTTP 请求消息的 ServletRequest 对象和一个代表HTTP响应消息的ServletResponse对象
- 然后调用 Servlet 的 service()方法并将请求和响应对象作为参数传递进去
-
destroy()终止阶段
- 当 web 应用被终止,或者 Servlet 容器终止运行,或者 Servlet 类重新装载时,会调用destroy()方法,比如重启 tomcat ,或者 redeploy web 应用
HttpServlet
在实际项目中,都是使用继HttpServlet类开发Servlet程序
它主要实现了对Http使用Servlet的流程代码基本封装,使用时只要重写doGet、doPost等方法即可快速实现服务
注意事项与细节
-
Servlet 是一个供其他 Java 程序(Servlet 引擎)调用的 Java 类,不能独立运行
-
针对浏览器的多次 Servlet 请求,通常情况下,服务器只会创建一个 Servlet 实例对象, 也就是说 Servlet 实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web 容器退出/或者 redeploy 该 web 应用,servlet 实例对象才会销毁 【示意图】
-
在 Servlet 的整个生命周期内,init 方法只被调用一次。而对每次请求都导致 Servlet 引
擎调用一次 servlet 的 service 方法。
-
对于每次访问请求,Servlet 引擎都会创建一个新的 HttpServletRequest 请求对象和一个
新的 HttpServletResponse 响应对象,然后将这两个对象作为参数传递给它调用的 Servlet
的 service()方法,service 方法再根据请求方式分别调用 doXXX 方法
-
如果在<servlet>元素中配置了一个<load-on-startup>元素,那么 WEB 应用程序在启动时, 就会装载并创建 Servlet 的实例对象、以及调用 Servlet 实例对象的 init()方法, (定时发送邮件的服务/自动启动->完成任务)
注解方式
通过使用注解可以不需要使用xml来配置servlet相关参数,即可让servlet类被web服务容器实例化使用
@WebServlet(urlPatterns = {"/ok1", "/ok2"})
-
urlPatterns
-
匹配
- 精确匹配urlPatterns = "/ok/zs"
- 目录匹配urlPatterns = "/ok/*"
- 扩展名匹配urlPatterns = "*.action" | urlPatterns = "*.do"
- 任意匹配urlPatterns = "/" | urlPatterns = "/*"
-
注意
-
当 Servlet 配置了 "/", 会覆盖 tomcat 的 DefaultServlet, 当其他的 utl-pattern 都匹配不上时 ,都 会 走 这 个 Servlet, 这 样 可 以 拦 截 到 其 它 静 态 资 源 , 比 如 D:\hspedu_javaweb_temp\hspedu_servlet\web\hi.html
The default servlet for all web applications, that serves static resources. (这个DefaultServlet是处理静态资源的,一旦拦截,静态资源不能处理)
-
当 Servelt 配置了 "/*", 表示可以匹配任意访问路径
-
建议不要使用 / 和 /*, 建议尽量使用精确匹配
-
-
优先级遵守: 精确路径 > 目录路径 > 扩展名路径 > /* > /
-
ServletConfig
servletConfig类是为了Servlet程序配置信息的类
servlet程序和servletConfig对下都是由Tomcat负责创建
Servlet程序默认是第一次访问的时候创建(实例化)
servletConfig在Servlet程序创建时,就创建一个对应的ServletConfig对象
作用
- 获取Servlet程序的servlet-name的值
- 获取初始化参数init-param
- 获取ServletContext对象
应用
public class DBServlet extends HttpServlet {
/**
* ServletConfig config 使用流程
* 1. 当DBServlet对象初始化时, tomcat会同时创建一个 ServletConfig对象
* 2. 这时如果DBServlet init() 方法中你调用 super.init(config);
* 3. 调用 父类 GenericServlet
* public void init(ServletConfig config) throws ServletException {
* this.config = config;
* this.init();
* }
* 这时就会把 Tomcat创建的 ServletConfig对象赋给 GenericServlet的属性 config
* 4. 因此如果你重写init()方法,记住如果你想在其它方法通过 getServletConfig() 方法获取ServletConfig
* , 则一定要记住 调用 super.init(config);
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
//ConcurrentHashMap, 是一个线程安全的容器.
System.out.println("init" + config);
super.init(config);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//在DBServlet 执行 doGet()/doPost() 时,可以获取到web.xml配置的用户名和密码
//你是一个OOP程序员->现有的方法或对象来搞定
//DBServlet的父类GenericServlet有getServletConfig()
/**
* 1. getServletConfig() 方法是 GenericServlet
* 2. 返回的 servletConfig对象是 GenericServlet private transient ServletConfig config;
* 3. 当一个属性被 transient 修饰,表示该属性不会被串行化(有些重要信息,不希望保存到文件)
*/
ServletConfig servletConfig = getServletConfig();
System.out.println("doPost=" + servletConfig);
String username = servletConfig.getInitParameter("username");
String pwd = servletConfig.getInitParameter("pwd");
System.out.println("初始化参数username= " + username);
System.out.println("初始化参数pwd= " + pwd);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
-------
Web.xml
<servlet>
<servlet-name>DBServlet</servlet-name>
<servlet-class>com.hspedu.servlet.DBServlet</servlet-class>
<!--配置信息,而不是硬编码到程序-->
<init-param>
<param-name>username</param-name>
<param-value>hsp</param-value>
</init-param>
<init-param>
<param-name>pwd</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>
ServletContext
ServletContext是一个接口,它表示Servlet上下文对象
一个 web工程,只有一个ServletContext对象实例
ServletContext 对象 是在 web 工程启动的时候创建,在 web 工程停止的时销毁,
ServletContext 对象可以通过 ServletConfig.getServletContext 方法获得对 ServletContext 对象的引用,也可以通过 this.getServletContext()来获得其对象的引用
由于一个 WEB 应用中的所有 Servlet 共享同一个 ServletContext 对象,因此 Servlet 对象之间可以通过 ServletContext 对象来实现多个 Servlet 间通讯。ServletContext 对象通常也被称之为域对象。
作用
- 获取web.xml中配置的上下文参数 context-param[此信息和整个web应用相关,而不是属于某个Servlet]
- 获取当前的工程路径,格式 /工程路径 如 /servlet
- 获取工程部署后在服务器硬盘上的绝对路径(如:D:\xxx\sss....\servlet_war_exploded)
- 可以像Map一样存取数据,多个Servlet共享数据
应用
public class ServletContext_ extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取web.xml的context-parameter
//1.获取到ServletContext对象
ServletContext servletContext = getServletContext();
//2. 获取website
String website = servletContext.getInitParameter("website");
String company = servletContext.getInitParameter("company");
//3. 获取项目的工程路径
String contextPath = servletContext.getContextPath();
//4. 获取项目发布会,正在的工作路径
// /表示我们的项目(发布后)的 根路径 D:\hspedu_javaweb\servlet\out\artifacts\servlet_war_exploded
String realPath = servletContext.getRealPath("/");
System.out.println("项目路径= " + contextPath);// /servlet
System.out.println("website= " + website);
System.out.println("company= " + company);
System.out.println("项目发布后的绝对路径= " + realPath);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
HttpServletRequest
HttpServletRequest对象代表客户端的请求
当客户端/浏览器通过HTTP 协议访问服务器时,HTTP 请求头中的所有信息都封装在这个对象中,通过这个对象的方法,可以获得客户端这些信息
常用方法
- getRequestURI() 获取请求的资源路径 http://localhost:8080/servlet/loginServlet
- getRequestURL() 获 取 请 求 的 统 一 资 源 定 位 符 ( 绝 对 路 径 ) http://localhost:8080/servlet/loginServlet
- getRemoteHost() 获取客户端的 主机, getRemoteAddr()
- getHeader() 获取请求头韩顺平 Java 工程师
- getParameter() 获取请求的参数
- getParameterValues() 获取请求的参数(多个值的时候使用) , 比如 checkbox, 返回的数组
- getMethod() 获取请求的方式 GET 或 POST
- setAttribute(key, value); 设置域数据
- getAttribute(key); 获取域数据
- getRequestDispatcher() 获取请求转发对象, 请求转发的核心对象
应用
public class HttpServletRequestMethods extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//这里我们使用request对象,获取表单提交的各种数据
System.out.println("HttpServletRequestMethods doPost() 被调用..");
/***********************************
* 获取和http请求头相关信息
***********************************/
System.out.println("请求的资源路径URI= " + request.getRequestURI());
//http://主机/uri
System.out.println("请求的统一资源定位符(绝对路径)URL= " + request.getRequestURL());
System.out.println("请求的客户端ip 地址= " + request.getRemoteAddr());//本地就是127.0.01
//思考题:如发现某个ip 在10s中,访问的次数超过 100次,就封ip
//实现思路: 1用一个集合concurrentHashmap[ip:访问次数] 2[线程/定时扫描] 3 做成处理
// 获取http请求头的信息,可以指定其他,比如 User-Agent , Host等待 老师就举一个例子
System.out.println("http请求头HOST= " + request.getHeader("Host"));
// 说明,如果我们希望得到请求的头的相关信息,可以使用request.getHeader("请求头字段")
System.out.println("该请求的发起地址是= " + request.getHeader("Referer"));
// 请获取访问网站的浏览器是什么?
String userAgent = request.getHeader("User-Agent");
System.out.println("User-Agent= " + userAgent);
// 取出FireFox, 取出最后
String[] s = userAgent.split(" ");
System.out.println("浏览器=" + s[s.length - 1].split("\\/")[0]);
//获取 Cookie
// JSESSIONID=8CBBD23BDE01BAE6705E03C5C8916BD1
String cookie = request.getHeader("Cookie");
String JSESSIONID = cookie.split("=")[1];
System.out.println("取出JSESSIONID= " + JSESSIONID);
//课堂练习: 要求同学们取出 Windows NT 10.0 和 Win64
// 主要是Get / Post
System.out.println("http请求方式~= " + request.getMethod());
/***********************************
* 获取和请求参数相关信息, 注意要求在返回数据前,获取参数
***********************************/
//解决接收参数的中文乱码问题, 老师提示,写在 getParameter前.
request.setCharacterEncoding("utf-8");
//1. 获取表单的数据[单个数据]
//username=tom&pwd=&hobby=hsp&hobby=spls
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
//2. 获取表单一组数据
String[] hobbies = request.getParameterValues("hobby");
System.out.println("username= " + username);
System.out.println("pwd= " + pwd);
//增强for循环的快捷键 iter->回车即可 , 能使用快捷键,就使用快捷键
for (String hobby : hobbies) {
System.out.println("hobby=" + hobby);
}
//推而广之, 如果是 单选 , 下拉框 等等. => 作业布置
//返回接收到的信息, 给浏览器回显
//本质就是在http响应头,加上 Content-Type: text/html;charset=utf-8
//说 text/html 表示返回的数据类型,浏览器会根据这个类型来解析数据
// text/plain 表示返回的数据,请浏览器使用文本方式解析
// application/x-tar 表示返回的是文件,浏览器就会以下载文件的方式处理
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("<h1>提交的用户名= " + username + "</h1>");
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
注意:
-
解决得到数据乱码问题,setCharacterEncoding("utf-8") 要 写 在 request.getParameter()前
-
如果通过PrintWriter writer, 有返回数据给浏览器,建议将获取参数代码写在writer.print() 之前,否则可能获取不到参数值(doPost)
-
解决返回数据乱码问题,response.setContentType("text/html;charset=utf-8")要放在response.getWrite()前
-
Http协议响应头中Content-Type的含义是让告诉浏览器按此格式(包括扩展名,编码)来展示返回的数据,比如 text/plain(文本) application/x-tar(x.tar文件下载)
-
response.setCharacterEncoding("UTF-8")不生效的原因及解决方法
原因:
1. request.getRequestDispatcher方式跳转到SecondServlet后,FirstServlet与SecondServlet共有同一个response。
2. response.setCharacterEncoding(“UTF-8”)语句执行之前执行了response.getWriter()语句。
基于以上两点原因,即使SecondServlet中response.setCharacterEncoding(“UTF-8”)语句执行之前没有执行了response.getWriter()语句,但是FirstServlet中执行了response.getWriter()语句,同样会使response.setCharacterEncoding(“UTF-8”)不生效。
请求的开始就设置编码,避免转发或者反射后设置就无效的情况
req.setCharacterEncoding("utf-8") ;
请求转发
在实际开发中,往往业务比较复杂,需要在一次请求中,使用到多个Servlet配合完成
使用
- 实现请求转发:请求转发指一个web 资源收到客户端请求后,通知服务器去调用另外一个 web 资源进行处理
- HttpServletRequest对象(也叫 Request 对象)提供了一个 getRequestDispatcher 方法,该方法返回一个 RequestDispatcher 对象,调用这个对象的 forward 方法可以实现请求转发
- request对象同时也是一个域对象,开发人员通过 request 对象在实现转发时,把数据通过 request 对象带给其它 web 资源处理
关联方法
- setAttribute方法
- getAttribute方法
- removeAttribute方法
- getAttributeNames方法
应用
public class CheckServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("CheckServlet 被调用..");
//根据用户名来确定该用户是什么身份
String username = request.getParameter("username");
//注意:如果是同一个request对象(请求转发),那么可以在不同的servlet中,是getParameter
if ("tom".equals(username)) {
//分配
request.setAttribute("role", "管理员");
} else {
request.setAttribute("role", "普通用户");
}
//获取分发器
// 1. /manageServlet写的是 要转发的servlet的url
// 2. / 会被解析成 /servlet
// 3. forward(request, response) 表示把当前servlet的request对象和response对象,传递给下一个servlet使用
RequestDispatcher requestDispatcher =
request.getRequestDispatcher("/manageServlet");
requestDispatcher.forward(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
public class ManageServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("ManageServlet 被调用..");
String username = request.getParameter("username");
String role = (String) request.getAttribute("role");
//输出信息
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("用户名: " + username + "<br/>");
writer.print("角色 : " + role);
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
注意:
- 浏览器地址不会变化(地址会保留在第 1 个servlet 的 url)
- 在同一次HTTP 请求中,进行多次转发,仍然是一次 HTTP 请求
- 在同一次HTTP 请求中,进行多次转发,多个 Servlet 可以共享 request 域/对象的数据(因为始终是同一个 request 对象)
- 可以转发到WEB-INF 目录下(后面做项目使用)
- 不能访问当前WEB 工程外的资源
- 因为浏览器地址栏会停止在第一个servlet ,如果你刷新页面,会再次发出请求(并且会带数据),所以在支付页面情况下,不要使用请求转发,否则会造成重复支付
HttpServletResponse
每次 HTTP 请求,Tomcat 会创建一个 HttpServletResponse 对象传递给 Servlet 程序去使用
HttpServletRequest 表示请求过来的信息,HttpServletResponse 表示所有响应的信息,如果需要设置返回给客户端的信息,通过 HttpServletResponse 对象来进行设置即可
返回数据方法
-
getOutputStream() 字节流,常用于下载(处理二进制数据)
-
getWriter() 字符流,常用于回传字符串
-
注意:两个流同时只能选择一个使用,否则会报错
注意事项
处理中文乱码问题
-
方案A
//设置服务器字符集为UTF-8 resp.setCharacterEncoding("UTF-8"); //通过响应头,设置浏览器也使用UTF-8字符集 resp.setHeader("Content-Type","text/html;charset=UTF-8");
-
方案B
//setContentType会设置服务器和客户端都用utf-8字符集,还设置了响应头 //setContentType要在获取流对象(getWriter)之前调用才有效 response.setContentType("text/html;charset=UTF-8")
请求重定向
一个web资源收到客户端请求后,通知客户端去访问另外一个web资源,这称之为请求重定向
应用
public class DownServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//完成了自己业务
//发出请求重定向-> DownServletNew
//1. sendRedirect 本质就会 返回 302 状态码 Location: /servlet/downServletNew
//2. 因此 302和 /servlet/downServletNew 是浏览器解析,而不是服务器
//3. 浏览器在解析 /servlet/downServletNew => http://localhost:8080/servlet/downServletNew
//4. 动态获取到application context
String contextPath = getServletContext().getContextPath();
System.out.println("contextPath= " + contextPath);
//response.sendRedirect("/servlet/downServletNew");
response.sendRedirect(contextPath + "/downServletNew");
//response.sendRedirect("http://www.baidu.com");
//第二种重定向的写法
// System.out.println("第二种方式重定向...");
// response.setStatus(302); //设置http响应的状态码
// //设置http响应的 Location: /servlet/downServletNew
// response.setHeader("Location", "/servlet/downServletNew");
}
public class DownServletNew extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("DownServletNew 被调用");
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("ok");
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
注意事项
-
最佳应用场景:网站迁移,比如原域名是 www.hsp.com 迁移到 www.hsp.cn ,但百度抓取的还是原来网址.
-
浏览器地址会发生变化,本质是两次 http 请求.
-
不能共享 Request 域中的数据,本质是两次 http 请求,会生成两个 HttpServletRequest 对象
-
不能重定向到 /WEB-INF 下的资源
-
可以重定向到 Web 工程以外的资源, 比如 到 www.baidu.com 【在前面的案例演示】
-
重定向有两种方式, 推荐使用第 1 种【在前面的案例演示】
-
动态获取到 application context【在前面的案例演示】
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?