web服务器(Tomcat)

news2024/11/21 0:27:53

一、web服务器

1. 常见web服务器

Tomcat:由Apache组织提供的一种Web服务器,提供对jsp和Servlet的支持。它是一种轻量级的javaWeb容器(服务器),也是当前应用最广的JavaWeb服务器(免费)。

Jboss:是一个遵从JavaEE规范的、开放源代码的、纯Java的EJB服务器,它支持所有的JavaEE规范(免费)。

GlassFish: 由Oracle公司开发的一款JavaWeb服务器,是一款强健的商业服务器,达到产品级质量(应用很少,收费)。

Resin:是CAUCHO公司的产品,是一个非常流行的应用服务器 的支持,性能也比较优良,resin自身采用JAVA语言开发(收费,应用比较多)。

WebLogic:是Oracle公司的产品,是目前应用最广泛的Web服务器,支持JavaEE规范,而且不断的完善以适应新的开发要求,适合大型项目(收费,用的不多,适合大公司)。

2. web服务器简介

(1)web服务器 底层是 基于tcp协议封装 http协议实现、springboot框架 底层内嵌入我们的 Tomcat服

(2)web服务器是一个应用程序(软件),对http协议的进行封装,让web开发更加便捷。

手写http服务器框架,底层是基于socket tcp实现。

二、 Apache Tomcat服务器

1. Tomcat服务器简介

(1)tomcat下载地址:Apache Tomcat® - Apache Tomcat 10 Software Downloads

(2)Apache Tomcat最早是由Sun Microsystems开发的一个Servlet容器,在1999年被捐献给ASF(Apache Software Foundation),隶属于Jakarta项目,现在已经独立为一个顶级项目。Tomcat主要实现了Java EE中的Servlet、JSP规范,同时也提供HTTP服务,是市场上非常流行的Java Web容器。

2. Tomcat服务器基本使用

(1)bin(文件夹)例如启动tomcat 或者停止tomcat --------可执行文件

*.bat---运行在windows批处理文件

*.sh-----linux环境中运行文件

startup.bat ----启动tomcat

shutdown.bat---停止tomcat

如果tomcat启动成功之后 tomcat控制台界面 是不会停止的。

如果tomcat启动失败的话,则tomcat控制台界面会闪退。

tomcat 启动之后默认端口号码:8080

tomcat欢迎界面。

(2)conf 存放全局配置文件 修改tomcat启动端口号码

logging.properties

(3)webapps 存放运行程序 部署war包、jar包、静态资源。

http://127.0.0.1:8080/mayikt/ 默认就是查找tomcat webapps 目录中

mayikt文件夹中 index.html

3. Tomcat文件目录介绍

(1)bin:主要存放tomcat的操作命令,根据操作系统可以分为两大类:一是以.bat结尾(Windows);二是以.sh结尾(Linux)。比如可以通过startup启动,shutdown关闭Tomcat。

(2)conf:全局配置文件

一个策略文件:catalina.policy 定义了安全策略。

两个属性文件:catalina.properties 和 logging.properties 。

四个XML配置文件:

server.xml:Tomcat的主要配置文件,配置整个服务器信息,如修改连接器端口号(默认为8080)。不能动态重加载,文件修改之后必须重启服务器才能生效。

web.xml:全局的web应用程序部署描述文件,如可以设置tomcat支持的文件类型。

context.xml:Tomcat的一些特定配置项,针对所有应用程序生效。

tomcat-users.xml:配置Tomcat的用户名、密码,管理身份验证以及访问控制权限。

(3)lib:Tomcat运行依赖的一些Jar文件,比如常见的servlet-api.jar、jsp-api.jar。所有的应用程序可用,可以放置一些公用的Jar文件,如MySQL JDBC驱动(mysql-connector-java-5.1.{xx}-bin.jar)。

(4)logs:运行中产生的日志文件。包含引擎(engine)日志文件 Catalina.{yyyy-mm-dd}.log,主机日志文件localhost.{yyyy-mm-dd}.log,以及一些其他应用日志文件如manager、host-manager。访问日志也保存在此目录下。

(5)temp:临时文件目录,清空不会影响Tomcat运行

(6)webapps:默认的应用程序根目录,Tomcat启动时会自动加载该目录下的应用程序,可以以文件夹、war包、jar包的形式发布(启动时会自动解压成相应的文件夹)。也可以把应用程序放置在其他路径下,需要在文件中配置路径映射。

(7)work:用来存放tomcat在运行时的编译后文件,如JSP编译后的文件。清空work目录,然后重启tomcat,可以达到清除存的作用。

bin:可以执行文件。

conf:tomcat服务器的配置文件

lib:tomcat启动后需要依赖的jar包

logs:tomcat工作之后的日志文件

webapps:是tomcat部署工程的目录。

work:jsp文件在被翻译之后,保存在当前这个目录下,session对象被序列化之后保存的位置

tomcat下载:

📎apache-tomcat-10.0.20-windows-x64.zip

双击启动:startup.bat

访问:http://127.0.0.1:8080/

注意 :tomcat解压安装位置 不要带中文、不要带任何空格路径。纯英文路径下运行tomcat。

4. 启动tomcat常见问题

(1)启动tomcat控制台乱码

双击启动:startup.bat

D:\path\Tomcat\tomcat10\apache-tomcat-10.0.20-windows-x64\apache-tomcat-10.0.20\conf

logging.properties

删除掉,在启动就好了。

(2)启动tomcat闪退问题

启动tomcat直接闪退,注意检查下jdk安装的环境变量

5.  如何关闭Tomcat服务器

第一种:Ctrl+C键 关闭Tomcat服务器

第二种:点击Tomcat窗口的右上角关闭按钮 (暴力停止服务器)

第三种:找到tomcat目录/shutdown.bat文件,双击执行关闭Tomcat。

发生启动tomcat服务器直接闪退----说明jdk环境没有好

6. Tomcat服务器配置

修改端口号码

1.找到tomcat目录/conf/server.xml

2.修改port的值,将port端口的值修改为80

7. Tomcat服务器部署项目

方式1:直接在tomcat webapps 目录创建一个文件夹

方式2:在tomcat目录/conf/server.xml 配置

127.0.0.1:8080/mayikt----D:\mayikt目录中查找info.html

host标签中:

<Context path="/mayikt" docBase="D:\mayikt"/>

<Context path=”浏览器要访问的目录---虚拟目录” docBase=网站所在磁盘目录”/>

方式3:将项目打成war包 放入到tomcat webapps目录中 自动解压

方式4:webapps目录下/ROOT工程的访问

当我们在浏览器中直接输入http://ip地址:端口号 那么 默认访问的是Tomcat目录/webapps/ROOT目录如果webapps下面有一个ROOT的项目。那么在访问的时候,直接可以省略项目的名字/ 表示找到root目录

----tomcat欢迎页面部署 ----webapps root 目录中

8. Tomcat web开发项目结构

(1)idea 先创建一个普通java项目

(2)在将该java项目 变成web项目

(3)整合tomcat

idea创建web项目

(1)选择创建项目

(2)创建java项目

(3)填写项目名称

(4)新增 add framework support

(5)选择web application

(6)多了web-inf文件夹

(7)新增tomcat

(8)点击新增tomcat

(9)选择tomcat server

(10)添加tomcat 路径

(11)添加当前java项目

(12)点击运行项目

(13)自动弹出界面

9. web项目目录结构说明

web项目结构

src------java代码 核心的配置文件(例如 spring配置文件等) servlet

web-----静态资源 或者jsp等

html--html、js、css、images等 静态资源 外部都可以直接访问的。

web-inf ------外界是无法访问的。

web.xml------servlet相关配置

index.jsp

三、servlet

1. 什么是servlet

Servlet定义:Servlet是基于Java技术的Web组件,由容器管理并产生动态的内容。Servlet与客户端通过Servlet容器实现的请求/响应模型进行交互。

springmvc----底层基于Servlet

演示代码:

http://localhost:8081/mayikt_tomcat04_war_exploded/mayikt?userName=mayikt

package com.mayikt.servlet;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;

import java.io.IOException;
import java.io.PrintWriter;


@WebServlet("/mayikt")
public class IndexServlet 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 {
        String userName = servletRequest.getParameter("userName");
        servletResponse.setContentType("text/html;charset=utf-8");
        PrintWriter writer = servletResponse.getWriter();
        if ("mayikt".equals(userName)) {
            writer.println("可以访问");
        } else {
            writer.println("无法访问");
        }
        writer.close();
    }
    
    @Override
    public String getServletInfo() {
        return null;
    }
    
    @Override
    public void destroy() {
        
    }
}

2. servlet 环境搭建

(1)在我们的项目中创建libs目录存放第三方的jar包

(2)项目中导入servlet-api.jar libs目录中

就在我们tomcat安装的目录 中 lib 目录中

servlet-api.jar 讲完课之后上传到 文档中可以直接下载

(3)创建servlet包 专门存放就是我们的servlet

(4)创建IndexServlet 实现Servlet 重写方法

(5)IndexServlet 类上加上@WebServlet("/mayikt")注解定义 URL访问的路径

(6)重写Servlet 类中service 在service中编写 动态资源

package com.mayikt.servlet;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;

import java.io.IOException;
import java.io.PrintWriter;


@WebServlet("/mayikt")
public class IndexServlet implements Servlet {
    
    /**
    * @param servletConfig
    * @throws ServletException
    */
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        
    }
    
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }
    
    /**
    * tomcat启动完成
    * 127.0.0.1:8080/项目名称/mayikt 执行 service 通过service方法获取servletRequest、servletResponse
    *
    * @param servletRequest
    * @param servletResponse
    * @throws ServletException
    * @throws IOException
    */
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("mayikt644");
        // 需要通过servletRequest对象获取到客户端传递参数到服务器端
        String userName = servletRequest.getParameter("userName");
        PrintWriter writer = servletResponse.getWriter();
        if ("mayikt".equals(userName)) {
            // 返回数据 ok
            writer.println("ok");
        } else {
            // fail
            writer.println("fail");
        }
        writer.close();// 关闭资源
        
    }
    
    @Override
    public String getServletInfo() {
        return null;
    }
    
    @Override
    public void destroy() {
        
    }
}

3. servlet debug调试

养成习惯 学会通过 debug模式 运行项目---非常重要

f8---下一步

f9---跳到下一个断点

4. servlet 执行流程

注意:servleturl 映射路径不要重复!

(1)servlet是由我们的 web服务器(tomcat)创建、该方法是由我们的 web服务器(tomcat)调用

断点分析

(2)tomcat服务器执行到servlet中的service方法,是因为我们创建的servlet实现httpservlet接口 重写了service方法

5. servlet 生命周期(重点)

(1)创建servlet

选择创建servlet :

如果是第一次访问servlet 才会创建servlet ---优点

第一次访问到servlet (单例模式) 线程安全问题

先创建servlet

在执行service方法

该servlet 创建好了以后 在jvm内存中只会存在一份。

如果是第二次访问servlet

在执行service方法

提前创建servlet

或者当你第一次访问servlet 创建servlet 对象

提前创建servlet ----优点可以 第一次访问的时候就不需要创建

servlet 对象可以提高效率、但是 项目启动时提前创建servlet

这样就会导致tomcat启动变得比较慢了。 浪费内存---

创建Servlet实例

web容器负责加载Servlet,当web容器启动时或者是在第一次使用这个Servlet时,容器会负责创建Servlet实例,但是用户必须通过部署描述符(web.xml)指定Servlet的位置,或者在类上加上@WebServlet,成功加载后,web容器会通过反射的方式对Servlet进行实例化。

@WebServlet(urlPatterns = "/mayiktmeite",loadOnStartup = 1)

负数---第一次被访问时创建Servlet对象 @WebServlet(urlPatterns = "/mayiktmeite",loadOnStartup = -1)

0或者正数:服务器启动时创建Servlet对象 数字越小优先级越高

MeiteServlet loadOnStartup = 1

YushengjunServlet loadOnStartup = 2

底层会根据loadOnStartup (从0开始)值排序 越小越优先加载创建

(2)执行servlet类中init---初始化方法

当我们的servlet类被创建时,执行servlet类初始化方法init 代码初始化

该方法只会执行一次。

WEB容器调用Servlet的init()方法,对Servlet进行初始化

在Servlet实例化之后,Servlet容器会调用init()方法,来初始化该对象,
主要是为了让Servlet对象在处理客户请求前可以完成一些初始化的工作,
例如,建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只能被调用一次。
init()方法有一个类型为ServletConfig的参数,Servlet容器通过这个参数向Servlet传递配置信息。
Servlet使用ServletConfig对象从Web应用程序的配置信息中获取以名-值对形式提供的初始化参数。
另外,在Servlet中,还可以通过ServletConfig对象获取描述Servlet运行环境的ServletContext对象,
使用该对象,Servlet可以和它的Servlet容器进行通信。无论有多少客户机访问Servlet,
都不会重复执行init()。

(3)servlet类---service-----执行每次请求

每次客户端发送请求达到服务器端 都会执行到 servlet类service方法

 Servlet初始化之后,将一直存在于容器中,service()响应客户端请求

    a. 如果客户端发送GET请求,容器调用Servlet的doGet方法处理并响应请求

    b. 如果客户端发送POST请求,容器调用Servlet的doPost方法处理并响应请求

    c. 或者统一用service()方法处理来响应用户请求



   service()是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,
该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和
一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。
默认的服务功能是调用与HTTP请求的方法相应的do功能。要注意的是,在service()方法被容器调用之前,
必须确保init()方法正确完成。容器会构造一个表示客户端请求信息的请求对象(类型为ServletRequest)
和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()方法。
在service()方法中,Servlet对象通过ServletRequest对象得到客户端的相关信息和请求信息,
在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。

(4)servlet类---destroy---当我们tomcat容器停止时卸载servlet

存放销毁相关代码

   WEB容器决定销毁Servlet时,先调用Servlet的destroy()方法,
通常在关闭web应用之前销毁Servlet

   destroy()仅执行一次,在服务器端停止且卸载Servlet时执行该方法。
当容器检测到一个Servlet对象应该从服务中被移除的时候,容器会调用该对象的destroy()方法,
以便让Servlet对象可以释放它所使用的资源,保存数据到持久存储设备中,
例如,将内存中的数据保存到数据库中,关闭数据库的连接等。当需要释放内存或者容器关闭时,
容器就会调用Servlet对象的destroy()方法。在Servlet容器调用destroy()方法前,
如果还有其他的线程正在service()方法中执行,容器会等待这些线程执行完毕或等待服务器
设定的超时值到达。
一旦Servlet对象的destroy()方法被调用,容器不会再把其他的请求发送给该对象。
如果需要该Servlet再次为客户端服务,容器将会重新产生一个Servlet对象来处理客户端的请求。
在destroy()方法调用之后,容器会释放这个Servlet对象,在随后的时间内,
该对象会被Java的垃圾收集器所回收。

6. servlet 多线程存在安全问题

servlet 对象默认是单例 在jvm内存中只会存在一份

当多个线程如果共享到同一个全局变量可能会存在线程安全性问题

需求:需要统计 你是第一个人访问网站?

package com.mayikt.servlet;


import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;

import java.io.IOException;
import java.io.PrintWriter;


@WebServlet("/count")
public class CountServlet implements Servlet {
    private Integer count = 1;
    
    public CountServlet() {
        System.out.println("CountServlet 对象被创建");
    }
    
    @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.setContentType("text/html;charset=utf-8");
        PrintWriter writer = servletResponse.getWriter();
        writer.println("您是第" + count + "个人访问网站!");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        writer.close();
        synchronized (this) {
            count++;
        }
        
    }
    
    @Override
    public String getServletInfo() {
        return null;
    }
    
    @Override
    public void destroy() {
        
    }
}

7. servlet 方法介绍

Servlet接口定义了5种方法:

init()

service()

destroy()

getServletConfig()

getServletInfo()

(1)init()

在Servlet实例化后,Servlet容器会调用init()方法来初始化该对象,主要是为了让Servlet对象在处理客户请求前可以完成一些初始化工作,例如:建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只能被调用一次。init()方法有一个类型为ServletConfig的参数,Servlet容器通过这个参数向Servlet传递配置信息。Servlet使用ServletConfig对象从Web应用程序的配置信息中获取以名-值对形式提供的初始化参数。另外,在Servlet中,还可以通过ServletConfig对象获取描述Servlet运行环境的ServletContext对象,使用该对象,Servlet可以和它的Servlet容器进行通信。

(2)service()

容器调用service()方法来处理客户端的请求。要注意的是,在service()方法被容器调用之前,必须确保init()方法正确完成。容器会构造一个表示客户端请求信息的请求对象(类型为ServletRequest)和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()。在service()方法中,Servlet对象通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。

(3)destroy

当容器检测到一个Servlet对象应该从服务中被移除的时候,容器会调用该对象的destroy()方法,以便让Servlet对象可以释放它所使用的资源,保存数据到持久存储设备中,例如将内存中的数据保存到数据库中,关闭数据库的连接等。当需要释放内存或者容器关闭时,容器就会调用Servlet对象的destroy()方法,在Servlet容器调用destroy()方法前,如果还有其他的线程正在service()方法中执行容器会等待这些线程执行完毕或者等待服务器设定的超时值到达。一旦Servlet对象的destroy()方法被调用,容器不回再把请求发送给该对象。如果需要改Servlet再次为客户端服务,容器将会重新产生一个Servlet对象来处理客户端的请求。在destroy()方法调用之后,容器会释放这个Servlet对象,在随后的时间内,该对象会被java的垃圾收集器所回收。

(4)getServletInfo()

返回一个String类型的字符串,其中包括了关于Servlet的信息,例如,作者、版本和版权。该方法返回的应该是纯文本字符串,而不是任何类型的标记。

(5)getServletConfig()

该方法返回容器调用init()方法时传递给Servlet对象的ServletConfig对象,ServletConfig对象包含了Servlet的初始化参数。

package com.mayikt.servlet;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;

import java.io.IOException;
import java.io.PrintWriter;


@WebServlet(urlPatterns = "/servletConfig", initParams = {@WebInitParam(name = "p1", value = "mayikt")})
public class ServletConfigServlet implements Servlet {
    private ServletConfig servletConfig;

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        this.servletConfig = servletConfig;
    }

    @Override
    public ServletConfig getServletConfig() {
        return servletConfig;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        String value = getServletConfig().getInitParameter("p1");
        PrintWriter writer = servletResponse.getWriter();
        writer.println(value);
        writer.close();
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

8. servlet 体系结构

HttpServlet 对的servlet接口包装 提供 get/post等方法

9. request与response对象

request: 获取客户端发送数据给服务器端

response:返回对应的数据给客户端(浏览器)

http协议基于 请求(request)与响应的模型(response)

package com.mayikt.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;


@WebServlet("/servletDemo05")
public class ServletDemo05 extends HttpServlet {
    /**
    * 获取到HttpServletRequest、HttpServletResponse对象
    *
    * @param req
    * @param resp
    * @throws ServletException
    * @throws IOException
    */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // req 获取到客户端发送数据给服务器()
        // http://localhost:8081/mayikt_tomcat04_war_exploded/servletDemo05?userName=mayikt&userPwd=123
        String userName = req.getParameter("userName");//userName=mayikt
        String userPwd = req.getParameter("userPwd");//userPwd=123456
        PrintWriter writer = resp.getWriter();
        // 判断用户传递的  用户名称和密码 如果是为zhangsan  644064 则 登录成功
        if ("zhangsan".equals(userName) && "644064".equals(userPwd)) {
            // 服务器端处理完数据之后 返回对应的数据给客户端 告诉给客户端说 响应的是一个 html或者是文本
            resp.setHeader("Content-Type", "text/html;charset=UTF-8");
            writer.write("<html> <meta charset=\"utf-8\"/><body><h1>恭喜您登录成功,用户名称是:" + userName + "</h1></body></html>");
        } else {
            writer.write("<html><meta charset=\"utf-8\"/><body><h1>很遗憾密码错误</h1></body></html>");
        }
        // 关闭资源
        writer.close();
    }
}

(1)request与response继承模型

ServletRequest ------ 接口 java提供的请求对象根接口

HttpServletRequest ------ 接口(继承ServletRequest) java提供的对http协议封装请求对象接口

org.apache.catalina.connnector.RequestFacade —— 类(Tomcat编写的,实现HttpServletRequest )

(2)request获取请求数据

http://localhost:8080/mayikt-tomcat04/servletDemo06

a. 请求行部分

String getMethod() // 获取请求方式

String getContextPath() // 获取项目访问路径 /mayikt-tomcat04

StringBuffer getRequestURL() // 获取 URL 统一资源定位符 http://localhost:8080/mayikt-tomcat04/servletDemo06

String getRequestURI() // 获取 URI 统一资源标识符 /mayikt-tomcat04/servletDemo06

String getQueryString() // 获取请求参数(Get 方式) username=mayikt&password=123

b.请求头部分

String getHeader(String name) // 根据请求头名称, 获取值

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36

c.请求体部分 (Post 才有)

通过 流读取 来获取 Post请求的参数 userName=mayikt&password=123

ServletInputStream getInputStream() // 字节输入流

BufferedReader getReader() // 字符输入流 readLine();

(3)request获取请求参数

Request对象里封装好了一个 Map集合,Map集合里放的就是所有的参数

Map<String,String[]> getParameterMap() // 返回这个 Map 集合

String[] getParameterValues(String name) // 根据 键名 获取值

String getParameter(String name) // 根据 键名 获取值

?age=18&age=22

Map<String, String[]> map = req.getParameterMap();
for(String key : map.keySet()){
    System.out.print(key + ":");
    
    String[] values = map.get(key);
    for(String value : values){
        System.out.print(value + " ");
    }
    
    System.out.println();
}
package com.mayikt.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;


@WebServlet("/httpServletDemo06")
public class HttpServletDemo06 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 判断请求是get还是post请求
        String method = req.getMethod();
        String parameters = null;
        switch (method) {
            case "GET":
                parameters = req.getQueryString();
                break;
            case "POST":
                BufferedReader reader = req.getReader();
                parameters = reader.readLine();
                reader.close();
                break;
        }
        HashMap<String, String> parametersMap = new HashMap<>();
        String[] sp1 = parameters.split("&");
        for (int i = 0; i < sp1.length; i++) {
            String[] sp2 = sp1[i].split("=");
            String key = sp2[0];
            String value = sp2[1];
            parametersMap.put(key, value);
        }
        System.out.println(parametersMap);
    }
}
1

(4)request请求转发

请求转发:一种在服务器内部的资源跳转方式

   a.通过request对象获取请求转发器对象 :

RequestDispatcher getRequestDispatcher(String path)

   b.使用RequestDispatcher对象来进行转发:

forward(ServletRequest request, ServletResponse response)

RequestDispatcher requestDispatcher = request.getRequestDispatcher("/requestDemo6");

requestDispatcher.forward(request,response);

浏览器地址栏路径不发生变化;

只能转发到当前服务器内部资源中;

转发是一次请求;

request.setAttribute("name",value); 数据共享

有效范围是一个请求范围,不发送请求的界面无法获取到value的值,jsp界面获取使用EL表达式${num};

只能在一个request内有效,如果重定向客户端,将取不到值。

request在当次的请求的URL之间有效,比如,你在请求某个servlet,那么你提交的信息,可以使用request.getAttribute()方式获得,而当你再次跳转之后,这些信息将不存在。

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;


@WebServlet("/httpServletDemo09")
public class HttpServletDemo09 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("name", "mayikt");
        req.getRequestDispatcher("/httpServletDemo10").forward(req, resp);
    }
}


package com.mayikt.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
 * @author 余胜军
 * @ClassName HttpServletDemo10
 * @qq 644064779
 * @addres www.mayikt.com
 * 微信:yushengjun644
 */
@WebServlet("/httpServletDemo10")
public class HttpServletDemo10 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = (String) req.getAttribute("name");
        System.out.println(name);
        System.out.println("httpServletDemo10");
    }
}

5. response响应数据

response是Servlet.service方法的一个参数,类型为javax.servlet.http.HttpServletResponse。在客户端发出每个请求时,服务器都会创建一个response对象,并传入给Servlet.service()方法。response对象是用来对客户端进行响应的,这说明在service()方法中使用response对象可以完成对客户端的响应工作。

//设置服务端的编码

resp.setCharacterEncoding("UTF-8");

//通过设置响应头设置客户端(浏览器的编码)

resp.setHeader("Content-type","text/html;utf-8");

//这个方法可以同时设置客户端和服务端,因为它会调用setCharacterEncoding方法

resp.setContentType("text/html;charset=utf-8");

resp.setContentType("text/html;charset=utf-8");

响应格式分为3个部分

(1)响应行:响应数据第一行 http协议版本1.1版本

200表示响应状态码 ok为 成功状态

(2)响应头:第二行开始 格式 key value

(3)响应体

response是响应对象,向客户端输出响应正文(响应体)可以使用response的响应流,repsonse一共提供了两个响应流对象:

PrintWriter out = response.getWriter():获取字符流;

ServletOutputStream out = response.getOutputStream():获取字节流;

如果响应正文内容为字符,那么使用response.getWriter(),如果响应内容是字节,
例如下载时,那么可以使用response.getOutputStream()。
//设置错误的响应码

resp.setError(404,"未找到请求的资源!");

//设置正确的响应码

resp.setStatus(200);

HttpServletResponse与ServletResponse

ServletResponse--- 接口 java提供的响应对象根接口

HttpServletResponse --- 接口(继承ServletResponse) java提供的对http协议封装响应对象接口

org.apache.catalina.connnector.ResponseFacade —— 类(Tomcat编写的,实现HttpServletResponse )

6. response重定向

(1)重定向原理

当我们的客户端发送请求达到服务器端,我们的服务器端响应状态码302 ,同时在响应头中

设置重定向地址(resp.setHeader("Location","www.mayikt.com");) ;

客户端(浏览器)收到结果之后,在浏览器解析Location www.mayikt.com 在直接重定向到

www.mayikt.com

首先客户浏览器发送http请求,当web服务器接受后发送302状态码响应及对应新的location给客 户浏览器,客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location 地址,服务器根据此请求寻找资源并发送给客户。

void sendRedirect(String location) 使用指定的重定向位置URL,向客户端发送临时重定向响应

resp.setStatus(302);
resp.setHeader("Location","www.mayikt.com");
(1)两次请求,重定向之后,浏览器地址栏的URL会发生改变

(2)重定向过程中会将前面Request对象销毁,然后创建一个新的Request对象

(3)重定向的URL可以是其它项目工程

(2)重定向与转发区别

1.转发只能将请求转发给同一个web应用(项目工程)中的其他组件(servlet程序);
重定向可以重定向到任意的地址,网络地址或是文件地址(跨项目文件夹 www.mayikt.com)

2.重定向访问结束后,浏览器地址栏URL发生变化,变成了重定向后的URL;转发则不变

重定向对浏览器的请求直接做出响应,结果就是告诉浏览器去重新发出另一个新的URL访问请求;
请求转发在服务器端内部将请求转发给另一个资源,浏览器不知道服务器程序内部发生了转发过程

3.请求转发调用者与被调用者之间共享相同的请求对象,属于同一个请求和响应过程;
重定向则是不同的请求和响应过程

四、案例:jdbc+servlet登录和注册

初始化数据库表结构

CREATE TABLE `mayikt_users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `userName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `userPwd` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb3;

jdbc+servlet用户注册

1.添加jar包

commons-lang3-3.4----常用工具包

mysql-connector-java-8.0.13.jar ----mysql驱动包

servlet-api.jar ----servlet-api.jar

该jar上传 该位置--- 点击下载jar包

文档:javaweb开发相关资料下载.note

链接:有道云笔记

2. jdbc工具类

package com.mayikt.utils;

import java.io.InputStream;
import java.sql.*;
import java.util.Properties;


public class MayiktJdbcUtils {
    /**
    * 1.需要将我们的构造方法私有化 ---工具类 不需要 new出来 是通过类名称.方法名称访问
    */
    private MayiktJdbcUtils() {
        
    }
    
    /**
    * 2.定义工具类 需要 声明 变量
    */
    private static String driverClass;
    private static String url;
    private static String user;
    private static String password;
    
    /**
    *3.使用静态代码快 来给我们声明好 jdbc变量赋值(读取config.properties)
    */
    static {
        try {
            // 1.读取config.properties  IO 路径 相对路径
            InputStream resourceAsStream = MayiktJdbcUtils.class.getClassLoader().
                getResourceAsStream("config.properties");
            // 2.赋值给我们声明好的变量
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            driverClass = properties.getProperty("driverClass");
            url = properties.getProperty("url");
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            // 3.注册驱动类
            Class.forName(driverClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
    * 4.封装连接方法
    */
    public static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection(url, user, password);
        return connection;
    }
    
    /**
    * 5.封装释放连接方法 (重载)
    */
    public static void closeConnection(ResultSet resultSet, Statement statement, Connection connection) {
        // 1.查询 释放连接 resultSet  statement connection
        try {
            if (resultSet != null)
                resultSet.close();
            if (statement != null)
                statement.close();
            if (connection != null)
                connection.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
    
    /**
    * 增删改---释放jdbc资源
    *
    * @param statement
    * @param connection
    */
    public static void closeConnection(Statement statement, Connection connection) {
        // 1.查询 释放连接 resultSet  statement connection
        closeConnection(null, statement, connection);
    }
    
    /**
    * 开启事务
    *
    * @param connection
    * @throws SQLException
    */
    public static void beginTransaction(Connection connection) throws SQLException {
        connection.setAutoCommit(false);
    }
    
    /**
    * 提交事务
    *
    * @param connection
    * @throws SQLException
    */
    public static void commitTransaction(Connection connection) throws SQLException {
        connection.commit();
    }
    
    /**
    * 回滚事务
    *
    * @param connection
    */
    public static void rollBackTransaction(Connection connection) {
        if (connection != null) {
            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
    * 关闭事务
    *
    * @param connection
    */
    public static void endTransaction(Connection connection) {
        if (connection != null) {
            try {
                connection.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
}

拷贝 config.properties

driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mayikt?serverTimezone=UTC
user=root
password=root

3.编写注册 servlet

package com.mayikt.servlet;

import com.mayikt.utils.MayiktJdbcUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;

/
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        resp.setContentType("text/html;charset=utf-8");
        //1.获取参数
        String userName = req.getParameter("userName");
        if (StringUtils.isEmpty(userName)) {
            writer.print("userName不能为空!");
            return;
        }
        String userPwd = req.getParameter("userPwd");
        if (StringUtils.isEmpty(userPwd)) {
            writer.print("userPwd不能为空!");
            return;
        }
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {

            connection = MayiktJdbcUtils.getConnection();
            //2.验证用户名称和密码
            MayiktJdbcUtils.beginTransaction(connection);// 开启事务
            String insertSql = "INSERT INTO `mayikt`.`mayikt_users` (`id`, `userName`, `userPwd`) VALUES (null, ?,?); ";
            preparedStatement = connection.prepareStatement(insertSql);
            preparedStatement.setString(1, userName);
            preparedStatement.setString(2, userPwd);
            int insertResult = preparedStatement.executeUpdate();
            MayiktJdbcUtils.commitTransaction(connection);
            String result = insertResult > 0 ? "注册成功" : "注册失败";
            writer.println(result);
        } catch (Exception e) {
            e.printStackTrace();
            writer.println("error");
            // 回滚事务
            if (connection != null)
                MayiktJdbcUtils.rollBackTransaction(connection);
        } finally {
            if (writer != null) {
                writer.close();
            }
            MayiktJdbcUtils.closeConnection(null, preparedStatement, connection);
        }
    }
}

4.编写注册html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>

<h1>用户注册</h1>
<form action="/mayikt_web_login_war_exploded/register" method="post">
    <!--name=mayikt&age=12 -->
    <span>用户名称:</span> <input type="text" name="userName"> <br>
    <span>密&nbsp;&nbsp;&nbsp;&nbsp;码:</span> <input type="text" name="userPwd" value=""><br>
    <input type="submit">
</form>
</body>
</html>

jdbc+servlet用户登录

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
</head>
<body>

<h1>用户登录</h1>
<form action="/mayikt_web_login_war_exploded/login" method="post">
    <!--name=mayikt&age=12 -->
    <span>名称:</span> <input type="text" name="userName"> <br>
    <span>密&nbsp;&nbsp;&nbsp;码:</span> <input type="text" name="userPwd" value=""><br>
    <input type="submit">
</form>
</body>
</html>

package com.mayikt.servlet;

import com.mayikt.utils.MayiktJdbcUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;


@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.获取参数
        String userName = req.getParameter("userName");
        String userPwd = req.getParameter("userPwd");
        PrintWriter writer = resp.getWriter();
        resp.setContentType("text/html;charset=utf-8");
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            //2.验证用户名称和密码
            connection = MayiktJdbcUtils.getConnection();
            String loginSql = "SELECT * FROM mayikt_users where userName=? and userPwd=? ";
            preparedStatement = connection.prepareStatement(loginSql);
            preparedStatement.setString(1, userName);
            preparedStatement.setString(2, userPwd);
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                String dbUserName = resultSet.getString(1);
                writer.println("恭喜" + userName + "登录成功");
            } else {
                writer.println("登录失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            writer.println("error");
            // 回滚事务
            if (connection != null)
                MayiktJdbcUtils.rollBackTransaction(connection);
        } finally {
            if (writer != null) {
                writer.close();
            }
            MayiktJdbcUtils.closeConnection(null, preparedStatement, connection);
        }
    }
}

常见问题

我们在使用tomcat 运行 web项目时 启动项目报错

java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver

原因1:没有引入 mysql驱动jar包

原因2:tomcat 在运行过程中 会在lib目录中查找jar包 发现没有 就会报该错误。

但是我们在编译阶段是在项目中lib 查找jar包,编译阶段没有报错。

将该 jar包拷贝到 tomcat -lib 目录中即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/790446.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

看了2023年的一线互联网公司时薪排行榜!值得思考

前言 根据最近针对国内的一线互联网企业做的调研&#xff0c;汇总了他们的平均时薪水平&#xff0c;最终出了一个排行榜&#xff01; 首先我们来看下&#xff0c;排行榜分哪几个Level&#xff0c;分别为初级、中级、高级、资深、专家/架构这五个&#xff0c;主要根据工程师的…

【044】深入探索STL:解密set与multiset容器的神秘力量

解密set与multiset容器的神秘力量 引言一、set和 multiset容器概述二、set容器常用API三、multiset的常用API四、对组 pair4.1、概念4.2、创建对组 pair 的方式 五、使用示例5.1、重定义排序规则5.2、队组pair的使用 总结 引言 &#x1f4a1; 作者简介&#xff1a;一个热爱分享…

【GeoDa实用技巧100例】016:制作(三维)散点图

文章目录 一、散点图介绍二、加载实验数据三、散点图制作四、剔除回归样本五、3D散点图一、散点图介绍 散点图是一种以点的分布反映变量之间相关情况的统计图。根据散点图中各点的分布走向和密度,可以大致判断变量之间相互关系。根据反映变量的维度可分为二维(亦称2D)和三维(…

数据结构第六天(7.20)双向链表逆置

DoubleLink rev_double(DoubleLink L,int n) {if(LNULL||L->nextNULL)return L;DoubleLink pL;LL->next;p->nextNULL;DoubleLink t;printf("%d",n);for(int i0;i<n-1;i){tL;LL->next;t->nextp;p->prevt;t->prevNULL;pt;}return p; }

【kubernetes系列】flannel之vxlan模式原理

概述 在Kubernetes中要保证容器之间网络互通&#xff0c;网络至关重要。而Kubernetes本身并没有自己实现容器网络&#xff0c;而是而是借助CNI标准&#xff0c;通过插件化的方式自由接入进来。在容器网络接入进来需要满足如下基本原则&#xff1a; Pod无论运行在任何节点都可…

【Ubuntu18.04安装FileZilla】

Ubuntu18.04安装FileZilla 1 FileZilla简介2 安装方式3 使用方式3.1 连接FTP服务器3.1.1 快速连接3.1.2 通过站点管理器 1 FileZilla简介 FileZilla是自由开源、快速、可信赖的FTP客户端以及服务器端应用&#xff0c;具有多种特色、直观的接口。 特点&#xff1a;可控性、有条…

flutter android Webview 打开网页错误ERR_CLEARTEXT_NOT_PERMITTED 、 net:ERR_CACHE_MISS

当你在Flutter应用中尝试打开一个非安全连接的网页&#xff08;例如HTTP连接而不是HTTPS连接&#xff09;时&#xff0c;可能会遇到"ERR_CLEARTEXT_NOT_PERMITTED"错误。这是因为默认情况下&#xff0c;Android 9及更高版本禁止应用程序通过非安全的明文HTTP连接进行…

Lombok 使用教程

lombok 官网 Project Lombok 课程目标: 什么是lombok lombok的原理 lombok的常用注解 lombok的安装 引言 还记得刚刚接触java,学习面向对象时因为手写get,set,hashcode,equals等方法的痛苦吗? 还记得后来上手了Eclipse或Idea这样工具可以快速生成get,set等相关方法的兴奋吗? …

【如何训练一个中译英翻译器】LSTM机器翻译模型部署之ncnn(python)(五)

系列文章 【如何训练一个中译英翻译器】LSTM机器翻译seq2seq字符编码&#xff08;一&#xff09; 【如何训练一个中译英翻译器】LSTM机器翻译模型训练与保存&#xff08;二&#xff09; 【如何训练一个中译英翻译器】LSTM机器翻译模型部署&#xff08;三&#xff09; 【如何训…

数据结构和算法——桶排序和基数排序(图示、伪代码、多关键字排序,基数排序代码)

目录 桶排序 图示 伪代码 时间复杂度 基数排序 多关键字排序 代码&#xff08;C语言&#xff09; 次位优先 主位优先 桶排序 假设有N个学生&#xff0c;他们的成绩是0到100之间的整数&#xff08;于是有M101个不同的成绩值&#xff09;。如何在线性时间内将学生按成绩…

什么是在线帮助中心?

随着企业越来越注重客户体验和服务质量&#xff0c;建立一个完善的在线帮助中心已经成为企业不可或缺的一部分。在线帮助中心可以帮助客户解决各种问题&#xff0c;从而提升客户满意度和忠诚度。而Baklib作为一款优雅的云知识库构建平台&#xff0c;提供了一种简单高效的方式来…

网页聊天室项目性能测试报告

文章目录 一 概述二 测试环境三 测试内容及方法四 GUI测试步骤五 简单数据写入器 HTML报告DashBoard六 结果分析七 性能优化方案 一 概述 1.1 目的 本测试报告为网页聊天室的性能测试报告&#xff0c;目的在于总结性能测试阶段的学习以及分析测试结果&#xff0c;描述网站是否…

创建线程的两种方式

一、线程相关概念 程序&#xff1a;完成特定任务&#xff0c;用某种语言编写的一组指令的集合。进程&#xff1a;运行起来的程序就是进程。进程运行时&#xff0c;操作系统需要为该进程分配内存空间。进程是一个动态过程&#xff0c;有产生、存在和消亡的过程。线程&#xff1…

osg earth中加载标签并设置文字 以及使用注意事项

osgearth中加载标签并设置文字 //头文件 #include <osgEarthAnnotation/PlaceNode> #include <osgEarthSymbology/Style> #include <osgEarth/MapNode> osgEarth::GeoPoint position(m_mapnode->getMapSRS(), lon, lat, 0, osgEarth::AltitudeMode::ALTM…

数据驱动商业合作:企业联系方式查询API在市场中的角色与作用

摘要 在当今数字化商业环境中&#xff0c;企业间的商务合作和合作伙伴关系构建变得更加重要。为了有效推进商业拓展和建立持久合作&#xff0c;企业需要快速、准确地获取潜在合作伙伴的联系方式。本文将深入探讨企业联系方式查询 API 在市场中的角色与作用&#xff0c;以及它如…

JetBrains全家桶:如何自定义实现类TODO注释?

文章目录 效果图具体方法参考文献 效果图 TODO注释大家应该都用过&#xff0c;在注释开头打上TODO的话&#xff0c;软件下方的TODO选项卡里就可以自动筛选出你打了TODO的注释&#xff0c;你可以点击里面对应的注释来实现快速跳转。 jetbrains全家桶&#xff08;如Pycharm、Int…

LKT(LCS)系列IIC接口加密芯片

调试常见问题&#xff08;一&#xff09; 1.加密芯片的数据交互协议是什么格式&#xff1f; 发送时&#xff1a;地址两字节数据长度&#xff08;后续数据的长度&#xff09;后续数据内容。Eg.50 0005 0084000008。接收时&#xff1a;地址两字节数据长度&#xff08;后续数据的…

万字长文详解Webpack5高级优化

本文从 4 个角度对 webpack 和代码进行了优化&#xff1a; 1.提升开发体验 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。 2.提升打包构建速度 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码&#xff0c;不变的代码使用缓存&#xff…

Github Flow工作流简单介绍(以部署为中心的开发模式)

前言 这篇文章主要介绍Github Flow的理念&#xff0c;以下内容来源于《Github入门与实践》。 Github Flow是以部署为中心的开发模式&#xff0c;通过简单的规则&#xff0c;持续高速且安全地进行部署。而Gitflow则是以发布为中心的分支管理模型&#xff0c;它提供了一种更灵活…

【Docker】Docker容器编排

目录 一、Docker Compose1.2Docker Compose 环境安装1.3 YAML 文件格式及编写注意事项2.3 Docker Compose配置常用字段2.4 Docker Compose 常用命令 二、Docker Compose实验2.1编写Nginx的Dockerfile脚本2.2编写MySQL&#xff0c;Dockerfile脚本2.3编写PHP&#xff0c;Dockerfi…