Tomcat + Servlet
浏览器请求资源:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bdU6yNl3-1688545785576)(C:\Users\cxz\Desktop\笔记图片\web-UML1.jpg)]
web机制:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MFOnPTfg-1688545785577)(C:\Users\cxz\Desktop\笔记图片\web机制.jpg)]
Servlet基本使用(补充)
service方法:
/**
* Tomcat把http请求的数据封装成实现ServletRequest接口的request对象
* servletResponse 用于返回数据给tomcat -> 浏览器
*/
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
}
Tomcat维护一个大HashMap<id,Servlet>
Servlet 流程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KR8IaIw-1688545785577)(C:\Users\cxz\Desktop\笔记图片\Servlet流程.jpg)]
Servlet注解方式开发
@WebServlet(urlPatterns = {"/ok1", "/ok2"})
public class OkServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("doPost ----");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("doGet -----");
}
}
匹配优先级:精确路径 > 目录路径 > 扩展名路径 > / > /*
Http协议
问题:一个html中有一个图片,问访问该html页面时,浏览器一共发出几次http请求?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>图片</h1>
<img src="images/640.png" width="300px">
</body>
</html>
答案:
三次!
1.第一次请求html
2.浏览器解析该html页面,发现其中有<img src="images/640.png" width="300px">
3.浏览器会继续向服务器发请求,要照片
Get请求返回数据:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.给回送数据设置编码
//2.text/html告诉浏览器返回的数据是text下的html格式MIME
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("<h1>登录成功</h1>");
}
URL编码
url编码:Content-Type: application/x-www-form-urlencoded
%E9%99%88%E5%85%88%E6%B3%BD ----》 “陈先泽”
什么时候出现304状态码?
1.浏览器在访问资源时如果使用的是之前缓存的静态资源,就会报304状态码
2.浏览器如果访问的资源修改过了,就会访问新的更新的资源,并返回200状态码,并有Last-Modified:记录上次
修改资源时间
Last-Modified: Wed, 28 Jun 2023 02:43:10 GMT
当再次访问该静态资源时,就会使用缓存,就会返回304状态码
什么时候出现302状态码?
302状态码表示:临时性的重定向,即要访问url时,被重定向到另一个URL
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//重定向到hi.html
response.sendRedirect("/hi.html");
}
访问的url的相应头中会指定location,即重定向的URL:Location: /hi.html
HTTP/1.1 302
Location: /hi.html
Content-Length: 0
Date: Wed, 28 Jun 2023 02:52:21 GMT
Keep-Alive: timeout=20
Connection: keep-alive
Servlet核心
ServletConfig
配置config的初始化信息:
<servlet>
<servlet-name>DBServlet</servlet-name>
<servlet-class>com.cxz.servlet.DBServlet</servlet-class>
<!--配置信息-->
<init-param>
<param-name>username</param-name>
<param-value>cxz</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>1234567</param-value>
</init-param>
得到初始化信息:
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//得到ServletConfig getServletConfig:
ServletConfig servletConfig = getServletConfig();
//获取init-parameter中的信息:
String username = servletConfig.getInitParameter("username");
String password = servletConfig.getInitParameter("password");
System.out.println("username:"+username+" ,password:"+password);
}
注意:
如果要重写init()方法,如果在其他方法中想通过getServletConfig()获取ServletConfig,
一定要调用super.init(config)
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("init:config = "+config);
super.init(config);
}
ServletContext
如何获取ServletContext对象?
ServletContext servletContext = getServletContext();
实际是通过ServletConfig对象拿到的
ServletContext可以实现什么?
1 可以被多个Servlet共享(在一个web应用中只有一个ServletContext)
2 数据存储格式 k-v 类似map
3 可以实现多个Servlet的通信
ServletContext可以做什么?
1.获取web.xml中配置的上下文参数 context-param (是属于整个web应用的)
<!--配置整个网站的信息-->
<context-param>
<param-name>website</param-name>
<param-value>http://www.cxz.com</param-value>
</context-param>
<context-param>
<param-name>company</param-name>
<param-value>cxz</param-value>
</context-param>
String website = servletContext.getInitParameter("website");
String company = servletContext.getInitParameter("company");
2.获取工程路径 : /工程路径
String contextPath = servletContext.getContextPath();
3.获取工程部署在硬盘上的绝对路径:
String realPath = servletContext.getRealPath("/");
ServletContext实现网站计数器
public class WebUtils {
public static Integer visitCount(ServletContext servletContext){
Object visit_count = servletContext.getAttribute("visit_count");
if (visit_count == null){
// 说明是第一访问
servletContext.setAttribute("visit_count",1);
visit_count=1;
}else {
//第二次及以后访问
visit_count = Integer.parseInt(visit_count + "") + 1;
//放回到ServletContext
servletContext.setAttribute("visit_count",visit_count);
}
return Integer.parseInt(visit_count+"");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext servletContext = getServletContext();
Integer count = WebUtils.visitCount(servletContext);
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("<h1>网站被访问的次数是:"+count+"</h1>");
}
HttpServletRequest
是什么?
HttpServletRequest对象代表客户端的请求
常用方法:
1.获取URI
System.out.println("URI =="+request.getRequestURI());
2.获取URL
System.out.println("URL =="+request.getRequestURL());
3.获取客户端主机IP
System.out.println("ip =="+request.getRemoteAddr());
4.获取请求头
System.out.println("HOST =="+request.getHeader("HOST"));
5.获取请求参数
//解决中文乱码问题
request.setCharacterEncoding("utf-8");
System.out.println("username = " + request.getParameter("username"));
System.out.println("pwd = " + request.getParameter("pwd"));
6.获取请求参数(多个值时)
//一组数据
String[] hobbies = request.getParameterValues("hobby");
for (String hobby : hobbies) {
System.out.println("hobby = "+hobby);
}
7.设置域数据
request.setAttribute("name",request.getParameter("username"));
8.获取域对象
request.getAttribute("name")
9.获取请求转发对象
10.获取请求方式
System.out.println("method =="+request.getMethod());
请求转发
请求转发示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-72PqxKVI-1688545785578)(C:\Users\cxz\Desktop\笔记图片\请求转发.jpg)]
需求:login ------> CheckServlet(检查提交的用户名并判断,并转发到下一个Servlet) ----------> ManagerServlet(根据转发来的request取出数据并显示)
前端请求
<form action="/cxz/check" method="post">
u : <input type="text" name="username"><br/>
<input type="submit" value="login">
</form>
第一个Servlet:
@WebServlet(urlPatterns = {"/check"})
public class CheckServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("CheckServlet~~~");
// 注意:如果在同一个request对象,那么可以在不同的Servlet中,用getParameter取出参数
String username = request.getParameter("username");
if (username.equals("Tom")){
request.setAttribute("role","管理员");
}else{
request.setAttribute("role","普通用户");
}
// 获取分发器:解析为 localhost:8080/cxz/manager
RequestDispatcher requestDispatcher =
request.getRequestDispatcher("/manager");
// forward表示把当前request对象和response对象传递给下一个servlet使用
requestDispatcher.forward(request,response);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req,resp);
}
}
第二个Servlet:
@WebServlet(urlPatterns = {"/manager"})
public class ManagerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("ManagerServlet~~");
// 取出用户名:
String username = request.getParameter("username");
String role = (String) request.getAttribute("role");
System.out.println("role -- "+request);
//输出信息:
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("username = "+username+"<br/>");
writer.print("role = "+(String)role);
}
}
说明:为什么第二个Servlet能取出第一个Servlet里的参数(Parameter)和域对象(getAttribute) ?
解答:因为如果参数和域值在同一个request中,那么可以在不同的Servlet中取出,
而第一个Servlet通过forward(),把自己的request和response转发到了第二个Servlet中。
所以始终是同一个request对象!!
ServletResponse
什么是ServletResponse?
答:每次Http请求,Tomcat会创建一个HttpServletResponse对象传递给Servlet程序去使用
表示所有响应信息。
乱码:
将服务端和客户端都设置为UTF-8
response.setContentType("text/html;charset=utf-8");
将服务端设置为UTF-8
request.setCharacterEncoding("utf-8");
请求重定向
原理示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RGjJjM0p-1688545785578)(C:\Users\cxz\Desktop\笔记图片\请求重定向.jpg)]
示例:
前端请求:
<body>
<h1>下载文件</h1>
<a href="http://localhost:8080/cxz/down">下载文件!</a>
</body>
第一个Servlet,请求重定向至:/cxz/new:
注意:重定向地址要加项目名:/cxz/new
重定向也可以直接写资源名: response.sendRedirect(“new”);注意前面不要带斜杠!
public class DownServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 请求重定向
// 要带上项目路径 ==> /cxz
// 返回302 和 Location:/cxz/new 由浏览器解析,所以它不知道项目路径:/cxz
// 浏览器解析成 :http://localhost:8080/cxz/new
response.sendRedirect("/cxz/new");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request,response);
}
}
第二个Servlet:/cxz/new
public class DownServletNew extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(("text/html;charset=utf-8"));
PrintWriter writer = response.getWriter();
writer.print("ok");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request,response);
}
}
注意事项:
1.重定向后最后的URL地址是重定向的最后的Servlet的URL
2.重定向过程中Request对象是不同的,因为每一次请求都是一次线程
3.无法重定向至WEB-INF目录内的文件
4.可以重定向至web项目以外的网站,如百度
5.可以重定向到web目录内的页面不过要加上 /项目名
response.sendRedirect("/cxz/Redirect.html");
Tomcat底层机制
Tomcat整体架构
整体架构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xKL18yQ0-1688545785578)(C:\Users\cxz\Desktop\笔记图片\tomcat.jpg)]
Tomcat底层实现:
第一版本的Tomcat:
Tomcat V1
/**
* 第一版Tomcat
* 完成客户端的请求并返回信息
*/
public class HspTomcatV1 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("======MyTomcat在8080监听=======");
while (!serverSocket.isClosed()){
//等待浏览器的连接
//如果有连接过来 就创建一个socket
//即服务器和客户端的连接
Socket socket = serverSocket.accept();
//接受浏览器发送的数据
//inputStream ---> BuffedReader
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String mes = null;
System.out.println("=======接收到浏览器的发送数据==========");
while ((mes=bufferedReader.readLine()) != null){
// 判断mes
if (mes.length()==0){
break;
}
System.out.println(mes);
}
//Tomcat回送-http
OutputStream outputStream = socket.getOutputStream();
//构建一个http响应消息头
String respHeader = "HTTP/1.1 200\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
//响应体resp:
String resp = respHeader + "hi,CXZ";
System.out.println("==========我们的Tomcat给浏览器回送的数据==========");
System.out.println(resp);
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
}
}
}
第二版本的Tomcat
目标:实现BIO线程模型,支持多线程:
多线程类 HspRequestHandler.java:
/**
* HspRequestHandler对象是一个线程对象
* 处理http请求的
*/
public class HspRequestHandler implements Runnable{
//定义Socket
private Socket socket = null;
public HspRequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
// 不同线程在和浏览器交互
System.out.println("当前线程 = "+Thread.currentThread().getName());
System.out.println("========hspTomcatv2接收的数据如下==========");
String msg = null;
while ((msg = bufferedReader.readLine()) != null){
if (msg.length()==0){
break;
}
System.out.println(msg);
}
//构建一个http响应消息头
String respHeader = "HTTP/1.1 200\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
String resp = respHeader + "<h1>hi CXZ v2</h1>";
System.out.println("==========myTomcat V2 返回的数据是============");
System.out.println(resp);
//返回数据给浏览器 封装成http响应
OutputStream outputStream = socket.getOutputStream();
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
**Tomcat V2 **
/**
* V2版本Tomcat
*/
public class HspTomcatV2 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("========MyTomcat V2 正在监听========");
// 等待客户端的连接
while (!serverSocket.isClosed()){
// 接受到一个浏览器的连接,成功后就会得到socket
// socket是客户端和服务器之间的通道
Socket socket = serverSocket.accept();
// 创建一个线程对象,并把socket给该线程
HspRequestHandler hspRequestHandler =
new HspRequestHandler(socket);
new Thread(hspRequestHandler).start();
}
}
}
处理Servlet
1.自定义Request
作用:封装http请求的数据
/**
* 作用:封装http请求的数据:
* get /hspCalServlet?num1=1&num2=3
* 比如 method get/post uri 参数列表
* HspRequest 等价于原生的Servlet中的HttpServletRequest
*/
public class HspRequest {
private String method;
private String uri;
private HashMap<String,String> parameterMapping =
new HashMap<>();
private InputStream inputStream = null;
//构造器 对http请求进行封装
public HspRequest(InputStream inputStream){
this.inputStream = inputStream;
// 完成对http请求数据的封装
init();
}
private void init(){
System.out.println("init 被调用");
try {
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
// get /hspCalServlet?num1=1&num2=3
String requestLine = bufferedReader.readLine();
//分割
String[] requestLineArr = requestLine.split(" ");
method = requestLineArr[0];
//解析得到 /hspCalServlet 看看有没有参数列表
int index = requestLineArr[1].indexOf("?");
if (index == -1){
uri = requestLineArr[1];
}else{
uri = requestLineArr[1].substring(0,index);
//获取参数列表:
String parameters = requestLineArr[1].substring(index + 1);
//分割成:num1=1 、 num2=3 .....
String[] parametersPair = parameters.split("&");
if (parametersPair!=null && !"".equals(parametersPair)){
// 分割:
for (String parameterPair : parametersPair) {
// parameterVal: {"num1","1"}
String[] parameterVal = parameterPair.split("=");
if (parameterVal.length == 2){
//放入到HashMap
parameterMapping.put(parameterVal[0],parameterVal[1]);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//方法: getParameter(String name):
public String getParameter(String name){
if (parameterMapping.containsKey(name)){
String s = parameterMapping.get(name);
return s;
}else {
return null;
}
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
}
使用:
HspRequest hspRequest = new HspRequest(inputStream);
String num1 = hspRequest.getParameter("num1");
String num2 = hspRequest.getParameter("num2");
System.out.println("请求num1 = "+num1);
System.out.println("请求num2 = "+num2);
2. 自定义Response
/**
* 封装OutputStream 和socket关联
* 通过HspResponse对象 返回http响应给浏览器
*/
public class HspResponse {
private OutputStream outputStream = null;
//http响应头
public static final String respHeader = "HTTP/1.1 200\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
// 创建HspResponse时 outputStream是和Socket关联的
public HspResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}
//当需要给浏览器返回数据时,可以通过HspResponse的输出流完成
public OutputStream getOutputStream() {
return outputStream;
}
}
Servlet规范
HspServlet接口:
/**
* 简化版的Servlet接口
*/
public interface HspServlet {
void init(ServletConfig var1) throws Exception;
void service(HspRequest request, HspResponse response)
throws IOException;
void destroy();
}
HspHttpServlet
public abstract class HspHttpServlet implements HspServlet{
@Override
public void service(HspRequest request, HspResponse response) throws IOException {
if ("GET".equalsIgnoreCase(request.getMethod())){
this.doGet(request,response);
}else if("POST".equalsIgnoreCase(request.getMethod())){
this.doPost(request,response);
}
}
// 抽象模板设计模式
// 让HspHttpServlet子类 HspCalServlet实现!
public abstract void doGet(HspRequest request,HspResponse response);
public abstract void doPost(HspRequest request,HspResponse response);
}
HspCalServlet
public class HspCalServlet extends HspHttpServlet{
@Override
public void doGet(HspRequest request, HspResponse response) {
doPost(request,response);
}
@Override
public void doPost(HspRequest request, HspResponse response) {
int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);
int res = num1+num2;
// 返回数据
OutputStream outputStream = response.getOutputStream();
String respMes = HspResponse.respHeader+"<h1>"+num1+"+"+num2+"="+res+" HSP Tomcat3</h1>";
try {
outputStream.write(respMes.getBytes());
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void init(ServletConfig var1) throws Exception {}
@Override
public void destroy() {}
}
※ 最终版 ※:
整体思路总结:
一:实现Tomcat的Http请求和Http响应
自定义HttpRequest类:
说明:该类实现了InputStream 和 Socket的关联!
/**
* 作用:封装http请求的数据:
* get /hspCalServlet?num1=1&num2=3
* 比如 method get/post uri 参数列表
* HspRequest 等价于原生的Servlet中的HttpServletRequest
*/
public class HspRequest {
private String method;
private String uri;
private HashMap<String,String> parameterMapping =
new HashMap<>();
private InputStream inputStream = null;
//构造器 对http请求进行封装
public HspRequest(InputStream inputStream){
this.inputStream = inputStream;
// 完成对http请求数据的封装
init();
}
private void init(){
System.out.println("init 被调用");
try {
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
// get /hspCalServlet?num1=1&num2=3
String requestLine = bufferedReader.readLine();
//分割
String[] requestLineArr = requestLine.split(" ");
method = requestLineArr[0];
//解析得到 /hspCalServlet 看看有没有参数列表
int index = requestLineArr[1].indexOf("?");
if (index == -1){
uri = requestLineArr[1];
}else{
uri = requestLineArr[1].substring(0,index);
// 获取参数列表:
String parameters = requestLineArr[1].substring(index + 1);
// 分割成:num1=1 、 num2=3 .....
String[] parametersPair = parameters.split("&");
if (parametersPair!=null && !"".equals(parametersPair)){
// 分割:
for (String parameterPair : parametersPair) {
// parameterVal: {"num1","1"}
String[] parameterVal = parameterPair.split("=");
if (parameterVal.length == 2){
//放入到HashMap
parameterMapping.put(parameterVal[0],parameterVal[1]);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//方法: getParameter(String name):
public String getParameter(String name){
if (parameterMapping.containsKey(name)){
String s = parameterMapping.get(name);
return s;
}else {
return null;
}
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
}
自定义HttpResponse类:
说明:该类实现了将OutputStream和socket的关联!
/**
* 封装OutputStream 和socket关联
* 通过HspResponse对象 返回http响应给浏览器
*/
public class HspResponse {
private OutputStream outputStream = null;
//http响应头
public static final String respHeader = "HTTP/1.1 200\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
// 创建HspResponse时 outputStream是和Socket关联的
public HspResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}
//当需要给浏览器返回数据时,可以通过HspResponse的输出流完成
public OutputStream getOutputStream() {
return outputStream;
}
}
二:自定义Tomcat
说明:通过dom4j和反射, 解析web.xml 将servlet标签和servlet-mapping标签中的信息放入容器中
/**
* 第三版Tomcat
*/
public class HspTomcatV3 {
// 存放容器
public static final ConcurrentHashMap<String, HspHttpServlet>
servletMapping = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<String, String>
servletUrlMapping = new ConcurrentHashMap<>();
public static void main(String[] args) {
HspTomcatV3 hspTomcatV3 = new HspTomcatV3();
hspTomcatV3.init();
//启动
hspTomcatV3.run();
}
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("==========hspTomcatV3在8080监听========");
while (!serverSocket.isClosed()) {
Socket socket = serverSocket.accept();
HspRequestHandler hspRequestHandler = new HspRequestHandler(socket);
new Thread(hspRequestHandler).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void init() {
//读取web.xml
// 得到web.xml路径
String path = HspTomcatV3.class.getResource("/").getPath();
//dom4j读取
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new File(path + "web.xml"));
System.out.println(document);
//得到根元素
Element rootElement = document.getRootElement();
// 得到根元素下的所有元素
List<Element> elements = rootElement.elements();
//遍历并过滤
for (Element element : elements) {
if ("servlet".equalsIgnoreCase(element.getName())) {
//反射 将该Servlet实例放入ServletMapping
Element servletName = element.element("servlet-name");
Element servletClass = element.element("servlet-class");
servletMapping.put(servletName.getText(),
(HspHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
} else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
//反射 放入ServletUrlMapping
Element servletName = element.element("servlet-name");
Element urlPattern = element.element("url-pattern");
servletUrlMapping.put(urlPattern.getText(), servletName.getText());
}
}
} catch (Exception e) {
e.printStackTrace();
} /*catch (Exception e) {
e.printStackTrace();
}*/
System.out.println(servletMapping);
System.out.println(servletUrlMapping);
}
}
三:通过创建线程对象实现http的请求和http的响应
public class HspRequestHandler implements Runnable {
//定义Socket:作为线程处理类的属性
private Socket socket = null;
public HspRequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 通过HspRequest对象对浏览器发起请求
// 当创建HspRequest类时,便会调用init(),实现对提交数据的解析并将parameter
// 放入其parameterMapping
HspRequest hspRequest = new HspRequest(socket.getInputStream());
// 通过HSpResponse对象返回数据给浏览器
HspResponse hspResponse = new HspResponse(socket.getOutputStream());
//创建Servlet
String uri = hspRequest.getUri();
String servletName = HspTomcatV3.servletUrlMapping.get(uri);
if (servletName == null){
servletName = "";
}
// uri -> servletName -> servlet实例
// 编译类型是父类 HspHttpServlet 真正的运行类型是其子类:HspCalServlet
HspHttpServlet hspHttpServlet =
HspTomcatV3.servletMapping.get(servletName);
//调用service,通过动态绑定机制 调用运行类型的doGet/doPost
if (hspHttpServlet != null) {
hspHttpServlet.service(hspRequest, hspResponse);
} else {
//没有
String resp = HspResponse.respHeader + "<h1>404 Not Found</h1>";
OutputStream outputStream = hspResponse.getOutputStream();
// 通过response对象的outputStream将信息响应给浏览器
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close();
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
自定义Servlet接口:
/**
* 简化版的Servlet接口
*/
public interface HspServlet {
void init(ServletConfig var1) throws Exception;
void service(HspRequest request, HspResponse response)
throws IOException;
void destroy();
}
自定义HttpServlet抽象类:
说明:
方法service()被继承该类的子类调用时,会动态绑定选择doGet/doPost
public abstract class HspHttpServlet implements HspServlet{
@Override
public void service(HspRequest request, HspResponse response) throws IOException {
if ("GET".equalsIgnoreCase(request.getMethod())){
this.doGet(request,response);
}else if("POST".equalsIgnoreCase(request.getMethod())){
this.doPost(request,response);
}
}
// 抽象模板设计模式
// 让HspHttpServlet子类 HspCalServlet实现!
public abstract void doGet(HspRequest request,HspResponse response);
public abstract void doPost(HspRequest request,HspResponse response);
}
自定义Servlet类并实现自定义HttpServlet类
public class HspCalServlet extends HspHttpServlet{
@Override
public void doGet(HspRequest request, HspResponse response) {
doPost(request,response);
}
@Override
public void doPost(HspRequest request, HspResponse response) {
int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);
int res = num1+num2;
// 返回数据
OutputStream outputStream = response.getOutputStream();
String respMes = HspResponse.respHeader+"<h1>"+num1+"+"+num2+"="+res+" HSP Tomcat3 反射!</h1>";
try {
outputStream.write(respMes.getBytes());
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void init(ServletConfig var1) throws Exception {}
@Override
public void destroy() {}
}
Web路径
相对路径
<form action="ok" method="post">
u : <input type="text" name="username"/><br/>
<input type="submit" value="注册用户"/>
</form>
使用相对路径:“ok”
会默认为:http://localhost:8080/工程名/ + ok(资源)
注意:资源名不能带 “/” 否则会解析成:http://localhost:8080/d1/d2/b.html 失去项目名!!
<a href="/d1/d2/b.html">跳转到d1/d2/b.html</a>
相对路径存在的问题:
当一个资源在多级目录下时,如 /views/user/User.html :
相对路径便会默认是localhost:8080/cxz/views/user/
这时若请求的url不在该目录下,就会出错!
可以在该页内使用 标签解决!
使用base标签
位于标签中
<!--<base>表示;
当前页面访问的所有资源都是以"http://localhost:8080/cxz/"为参考路径
-->
<base href="http://localhost:8080/cxz/">
这样该页面的请求的所有资源路径都可以只带资源名
也可以简写为:
<!--浏览器解析 :
浏览器在解析第一个 / 的时候 都会解析成 http://localhost:8080/ -->
<base href="/cxz/">
转发的路径问题
//在服务器端 解析到第一个“/"时被解析为:http://localhost:8080/项目名/
System.out.println("Servlet 转发");
request.getRequestDispatcher("/d1/d2/b.html").forward(request,response);
总结
编写资源路径时需注意:
1.这个路径前面有没有 “ / ”
2.这个“/ ”在哪被解析?
如果有 / 并且是在浏览器端,就被解析成:http://localhost:8080/
(会丢失项目路径!)
如果有 / 并且在服务器端,就被解析成:/工程路径/
3.如果路径没有“ / ”:
如果在浏览器端被解析,则以浏览器当前的地址栏 去掉 资源部分,作为一个默认的相对路径
比如:http://localhost:8080/cxz/views/user/User.html 的默认相对路径是 :
http://localhost:8080/cxz/views/user/
4.重定向的资源路径算是被 浏览器 解析的!
注意!重定向路径前可以有两种形式:
方式一:/项目路径/资源名
如:
response.sendRedirect("/cxz/new");
方式二:资源名
如:前面不要带 “ / ”
response.sendRedirect("views/user/User.html");
会话技术 Cookie+Session
Cookie
Cookie介绍:
1.Cookie数据是保存在浏览器的
2.服务器在需要的时候可以从客户端/浏览器读取
Cookie的创建和读取
Cookie示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dM45wYVi-1688545785579)(C:\Users\cxz\Desktop\笔记图片\cookie.jpg)]
Cookie的创建:
//1.创建cookie对象
// username是惟一的。是该Cookie的名字
// CXZ是该cookie的值
// 此时 cookie在服务器端
Cookie cookie = new Cookie("username", "CXZ");
response.setContentType("text/html;charset=utf-8");
// 将该cookie发送给浏览器,保存起来
response.addCookie(cookie);
PrintWriter writer = response.getWriter();
writer.print("<h1>创建Cookie成功</h1>");
Cookie的读取
//通过request读取所有Cookie信息:
Cookie[] cookies = request.getCookies();
//遍历Cookie信息
if (cookies != null && cookies.length!=0){
for (Cookie cookie : cookies) {
System.out.println("cookie name = "+cookie.getName()+
" value = "+cookie.getValue());
}
}
读取指定Cookie
1.新建Cookie读取工具类
public class CookieUtils {
// 编写方法 返回指定名字的cookie
public static Cookie readCookieByName(String name, Cookie[] cookies) {
if (name == null || "".equals(name) || cookies == null || cookies.length == 0) {
return null;
}
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())) {
return cookie;
}
}
return null;
}
}
2.读取指定Cookie:
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 得到指定Cookie的值:
Cookie[] cookies = request.getCookies();
Cookie email = CookieUtils.readCookieByName("email", cookies);
if (email != null){
System.out.println("email = "+email.getValue());
}else {
System.out.println("没有找到!");
}
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("<h1>读取成功</h1>");
}
修改Cookie
String name = "email";
Cookie[] cookies = request.getCookies();
// 从Cookie数组中读取指定name的cookie
Cookie cookie = CookieUtils.readCookieByName(name, cookies);
if (cookie == null){
System.out.println("没有找到Cookie");
}else{
// 修改cookie的值
cookie.setValue("qwe@qq.com");
}
// 给浏览器返回修改后的cookie
if (cookie!=null){
response.addCookie(cookie);
}
另外:
如果创建一个同名Cookie,则会覆盖掉原先的同名Cookie!
Cookie的生命周期
介绍:
1、Cookie的生命周期指的是管理Cookie什么时候被销毁
2、setMaxAge():
正数:表示在指定的秒数后过期
负数:表示浏览器关闭,Cookie就会被删除
0 :表示马上删除Cookie
设置Cookie在指定时间后无效:
// 创建Cookie 声明周期为60S
Cookie cookie = new Cookie("job", "java");
// 浏览器根据创建的时间计时 60S后无效
// 当该Cookie无效,那么浏览器在发出http请求时 就不会携带该Cookie
cookie.setMaxAge(60);
// 保存到浏览器
response.addCookie(cookie);
删除Cookie
// 删除Cookie
// 先得到username
Cookie[] cookies = request.getCookies();
Cookie usernameCookie = CookieUtils.readCookieByName("username", cookies);
//设置声明周期为 0 等价于让浏览器删除该Cookie
usernameCookie.setMaxAge(0);
// 通知浏览器保存
response.addCookie(usernameCookie);
Cookie的有效路径
介绍:
path属性决定过滤哪些 Cookie 可以发送给浏览器
Cookie cookie1 = new Cookie("k1", "v1");
Cookie cookie2 = new Cookie("k2", "v2");
// 设置不同的有效路径
cookie1.setPath(request.getContextPath());
// /cxz/aaa
cookie2.setPath(request.getContextPath() + "/aaa");
//如果没有设置cookie的有效路径,默认是 /工程路径名
response.addCookie(cookie1);
response.addCookie(cookie2);
Session
介绍:
1、服务器在运行时为每一个用户的浏览器创建一个其独享的session对象/集合
2、session可以看做是HashMap : String - Object
Session底层机制
示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iu8hzjPB-1688545785579)(C:\Users\cxz\Desktop\笔记图片\session.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D3Um5I90-1688545785580)(C:\Users\cxz\Desktop\笔记图片\session2.jpg)]
Session生命周期
可以设置Session的生命周期:
session.setMaxInactiveInterval(60);
注意:
1、如果在Session没有过期的情况下,操作Session,则重新计时生命周期
2、Session的计时是在 服务器端 管理的
如何删除Session?(删除的是整个Session)
session.invalidate();
删除某个Session的具体属性:
session.removeAttribute( "xx" );
监听器Listener + 过滤器Filter
监听器 Listener
ServletContextListener:
作用监听 ServletContext,即生命周期的监听。
创建监听器: 创建类并实现 ServletContextListener 并重写它的方法
作用:可以监听ServletContext的创建和销毁。
示例:
/**
* web启动时,就会产生 ServletContextEvent事件
* 程序员通过ServletContextEvent事件对象来获取信息 进行业务处理
*
*/
public class HspServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// 启动web时调用
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 销毁web时调用
}
}
需要在web.xml中配置监听器,Tomcat就知道有监听器的存在了
过滤器 Filter
原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmAwKyKl-1688545785580)(C:\Users\cxz\Desktop\笔记图片\Filter.jpg)]
Filter的使用:
编写类实现Filter接口并重写方法:
public class ManageFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
}
并在web.xml中配置:
<filter>
<filter-name>ManageFilter</filter-name>
<filter-class>com.cxz.filter.ManageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ManageFilter</filter-name>
<url-pattern>/manage/*</url-pattern>
</filter-mapping>
FilterConfig:
介绍:
FilterConfig是Filter的配置类 Tomcat创建Filter时就会创建一个FilterConfig对象,包含了Filter配置文件的
配置信息。
使用:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("======= init 执行 =======");
// 通过filterConfig获取相关参数
String filterName = filterConfig.getFilterName();
String ip = filterConfig.getInitParameter("ip");
ServletContext servletContext = filterConfig.getServletContext();
// 获取所有配置信息参数名
Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
// 遍历枚举
while (initParameterNames.hasMoreElements()){
System.out.println("name = "+initParameterNames.nextElement());
}
}
FilterChain 过滤器链
执行顺序:
两个Filter AFilter 、 BFilter
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S2PfTDtt-1688545785580)(C:\Users\cxz\Desktop\笔记图片\image-20230702170912465.png)]
注意:
1、多个filter和目标资源在一次Http请求,在同一线程中
2、多个Filter对象是同一个 request 对象
3、chain.doFilter() 将执行下一个过滤器的doFilter(), 如果后面没有过滤器了,就执行目标资源。
JSON + Ajax
JSON
JSON快速入门:
格式:
var 变量名 = {
“k1":"v1",
"k2":123,
"k3":[1,"abc",3],
"k4":{"age":12,"name":"cxz"}
};
<script type="text/javascript">
window.onload = function(){
var cxz = {
"k1": "cxz",
"k2": 123,
"k3": [123, "abc"],
"k4": {"age": 12, "name": "cxz"}
};
// 获取json对象值
console.log("k1 = ",cxz.k2);
}
</script>
JSON和字符串之间的转化:
JSON.stringify( Json );
JSON.parse( String );
<script type="text/javascript">
window.onload = function () {
//将JSON对象转为String
var Jsonperson = {
"name":"jack",
"age":20
}
var strPerson = JSON.stringify(Jsonperson);
console.log("strPerson" , strPerson)
//将String转为Json
var strDog = "{\"name\":\"狗\",\"age\":2}";
var jsonDog = JSON.parse(strDog);
console.log(jsonDog)
}
</script>
Java和JSON之间的转换
JavaBean和JSON之间的转换
前提:引入gson.jar
public class JavaJson {
public static void main(String[] args) {
//创建gson作为工具
Gson gson = new Gson();
// Javabean和JSON字符的转换
Book book1 = new Book(100, "book1");
// javabean - > json字符串
String strBook = gson.toJson(book1);
// json字符串 - > javabean
// 第一个参数: json字符串
// 第二个参数: 转成某个类的对象的类
Book book2 = gson.fromJson(strBook, Book.class);
}
}
List对象和JSON字符串之间的转换
// List - 》json字符串
ArrayList<Book> bookList = new ArrayList<>();
bookList.add(new Book(123,"三国演义"));
bookList.add(new Book(222,"西游记"));
String strBookList = gson.toJson(bookList);
System.out.println("strBookList = "+strBookList);
//JSON字符串 -- 》 list
/**
* 需要使用TypeToken 然后通过实例指定需要转换成的类型
*/
Type type = new TypeToken<List<Book>>() {
}.getType();
List<Book> bookList2 = gson.fromJson(strBookList, type);
System.out.println("bookList2= "+bookList2);
Map对象和JSON之间的转化
// Map --》 JSON
HashMap<String, Book> bookMap = new HashMap<>();
bookMap.put("k1",new Book(300,"天龙八部"));
bookMap.put("k2",new Book(444,"红楼梦"));
String strBookMap = gson.toJson(bookMap);
System.out.println("strBookMap = "+strBookMap);
//json --> map
Map<String,Book> bookMap2= gson.fromJson(strBookMap,
new TypeToken<Map<String, Book>>() {}.getType());
System.out.println("bookMap2 = "+bookMap2);
Ajax
介绍:
Ajax是浏览器异步发起请求(指定哪些数据),局部更新 的技术
Ajax原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M590mrsY-1688545785580)(C:\Users\cxz\Desktop\笔记图片\xml.jpg)]
Ajax应用:
onreadystatechange事件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opPk1nqV-1688545785581)(C:\Users\cxz\Desktop\笔记图片\onreadstatechange.png)]
<script type="text/javascript">
window.onload = function () {
var checkButton = document.getElementById("checkButton");
checkButton.onclick = function () {
//创建XMLHttpRequest对象[ajax引擎]
var xhr = new XMLHttpRequest();
//获取提交数据
var uname = document.getElementById("uname").value;
//发送指定数据
// "/cxz/check?username=" + uname --> URL
// true : 异步处理
xhr.open("GET", "/cxz/check?uname=" + uname, true);
// send调用前 给xhr绑定一个事件
// onreadystatechange表示可以去指定一个函数,当数据变化时,会触发
xhr.onreadystatechange = function () {
// 如果请求已完成 且响应已就绪 并且状态码: 200
if (xhr.readyState == 4 && xhr.status == 200) {
document.getElementById("div1").innerHTML = xhr.responseText
var responseText = xhr.responseText;
// console.log("responseText = ",responseText);
if (responseText != "") {
document.getElementById("myres").value = "用户名不可用"
} else {
document.getElementById("myres").value = "用户名可用"
}
}
}
// 正式发送AJAX 请求:
// 如果是POST请求 在()内填写 请求数据
xhr.send();
}
}
</script>
ThreadLocal
介绍:
1、作用:可以实现在同一个线程数据共享,从而解决多线程数据安全问题。
2、使用ThreadLocal + Filter 实现事务安全
应用
创建:
public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();
使用
public static class Task implements Runnable{
@Override
public void run() {
Dog dog = new Dog();
Pig pig = new Pig();
System.out.println("放入的dog : "+dog);
// 放入ThreadLocal
threadLocal1.set(dog);
threadLocal2.set(pig);
new T1Service().update();
}
}
public static void main(String[] args) {
new Thread(new Task()).start();
}
取出当前线程的ThreadLocal:Object o = T1.threadLocal1.get();
注意:
threadLocal1.set(dog); 一个ThreadLocal中只能放一个数据,第二次set() 会使得ThreadLocal中
数据被替换。
局部更新* 的技术
Ajax原理
[外链图片转存中…(img-M590mrsY-1688545785580)]
Ajax应用:
onreadystatechange事件:
[外链图片转存中…(img-opPk1nqV-1688545785581)]
<script type="text/javascript">
window.onload = function () {
var checkButton = document.getElementById("checkButton");
checkButton.onclick = function () {
//创建XMLHttpRequest对象[ajax引擎]
var xhr = new XMLHttpRequest();
//获取提交数据
var uname = document.getElementById("uname").value;
//发送指定数据
// "/cxz/check?username=" + uname --> URL
// true : 异步处理
xhr.open("GET", "/cxz/check?uname=" + uname, true);
// send调用前 给xhr绑定一个事件
// onreadystatechange表示可以去指定一个函数,当数据变化时,会触发
xhr.onreadystatechange = function () {
// 如果请求已完成 且响应已就绪 并且状态码: 200
if (xhr.readyState == 4 && xhr.status == 200) {
document.getElementById("div1").innerHTML = xhr.responseText
var responseText = xhr.responseText;
// console.log("responseText = ",responseText);
if (responseText != "") {
document.getElementById("myres").value = "用户名不可用"
} else {
document.getElementById("myres").value = "用户名可用"
}
}
}
// 正式发送AJAX 请求:
// 如果是POST请求 在()内填写 请求数据
xhr.send();
}
}
</script>
ThreadLocal
介绍:
1、作用:可以实现在同一个线程数据共享,从而解决多线程数据安全问题。
2、使用ThreadLocal + Filter 实现事务安全
应用
创建:
public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();
使用
public static class Task implements Runnable{
@Override
public void run() {
Dog dog = new Dog();
Pig pig = new Pig();
System.out.println("放入的dog : "+dog);
// 放入ThreadLocal
threadLocal1.set(dog);
threadLocal2.set(pig);
new T1Service().update();
}
}
public static void main(String[] args) {
new Thread(new Task()).start();
}
取出当前线程的ThreadLocal:Object o = T1.threadLocal1.get();
注意:
threadLocal1.set(dog); 一个ThreadLocal中只能放一个数据,第二次set() 会使得ThreadLocal中
数据被替换。