当前位置:   article > 正文

web服务器(Tomcat & Servlet)

web服务器(Tomcat & Servlet)

目录

一、web服务器

1. 常见web服务器

2. web服务器简介

二、 Apache Tomcat服务器

1. Tomcat服务器简介

2. Tomcat服务器基本使用

3. 启动tomcat常见问题

(1)启动tomcat控制台乱码

(2)启动tomcat闪退问题

(3)启动报错

4.  如何关闭Tomcat服务器

5. Tomcat文件目录介绍

6. Tomcat服务器部署项目

7. Tomcat web开发项目结构

8.  idea创建web项目

9. web项目目录结构说明

三、servlet

1. 什么是servlet

2. servlet 环境搭建

3. 执行原理:

4. Servlet3.0

5. servlet debug调试

6. servlet 执行流程

7. servlet 生命周期(重点)

(1)创建servlet

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

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

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

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

9. servlet 方法介绍

10. servlet 体系结构

11. Servlet相关配置

四、 request与response对象

1. request与response继承模型

2. request获取请求数据

(1) 获取请求行数据

(2)获取请求头数据

(3)获取请求体数据 (Post 才有)

3. request获取请求参数

4. 中文乱码问题

5. request请求转发

6. response响应数据

7. response重定向

(1)重定向原理

(2)重定向与转发区别

8. ServletContext对象

五、文件下载

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

jdbc+servlet用户注册

jdbc+servlet用户登录

常见问题


一、web服务器

* 服务器:安装了服务器软件的计算机
* 服务器软件:接收用户的请求,处理请求,做出响应
* web服务器软件:接收用户的请求,处理请求,做出响应。
       * 在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目
       * 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)下载:http://tomcat.apache.org/

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

  (3) 卸载:删除目录就行了

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

        * bin/startup.bat ,双击运行该文件即可

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

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

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

  (5)访问:浏览器输入:http://localhost:8080 回车访问自己

                                            http://别人的ip:8080 访问别人

3. 启动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闪退问题

          黑窗口一闪而过:
                * 原因: 没有正确配置JAVA_HOME环境变量
                * 解决方案:正确配置JAVA_HOME环境变量

(3)启动报错

                a. 暴力:找到占用的端口号,并且找到对应的进程,杀死该进程
                    * netstat -ano

                b. 温柔:修改自身的端口号

                                找到tomcat目录/conf/server.xml

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


                    * 一般会将tomcat的默认端口号修改为80。80端口号是http协议的默认端口号。
                     * 好处:在访问时,就不用输入端口号

4.  如何关闭Tomcat服务器

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

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

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

5. Tomcat文件目录介绍

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

(2)conf:全局配置文件(logging.properties 修改tomcat启动端口号码)

一个策略文件: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:存放运行程序 部署war包、jar包、静态资源。

默认就是查找tomcat webapps 目录中项目文件夹中 index.html

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

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

  1. bin:可以执行文件。
  2. conf:tomcat服务器的配置文件
  3. libtomcat启动后需要依赖的jar
  4. logs:tomcat工作之后的日志文件
  5. webapps:是tomcat部署工程的目录。
  6. work:jsp文件在被翻译之后,保存在当前这个目录下,session对象被序列化之后保存的位置

6. Tomcat服务器部署项目

方式1: 直接将项目放到webapps目录下即可。
                * /hello:项目的访问路径-->虚拟目录
                * 简化部署:将项目打成一个war包,再将war包放置到webapps目录下。
                    * war包会自动解压缩

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

在<Host>标签体中配置
                <Context docBase="D:\hello" path="/hehe" />
                * docBase:项目存放的路径
                * path:虚拟目录

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

方式3:在conf\Catalina\localhost创建任意名称的xml文件。在文件中编写
                <Context docBase="D:\hello" />
                * 虚拟目录:xml文件的名称

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

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

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

7. Tomcat web开发项目结构

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

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

(3)整合tomcat

8.  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容器实现的请求/响应模型进行交互。

概念:运行在服务器端的小程序
    * Servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别)的规则。
    * 将来我们自定义一个类,实现Servlet接口,复写方法。

springmvc----底层基于Servlet

演示代码:

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

  1. @WebServlet("/mayikt")
  2. public class IndexServlet implements Servlet {
  3. @Override
  4. public void init(ServletConfig servletConfig) throws ServletException {
  5. }
  6. @Override
  7. public ServletConfig getServletConfig() {
  8. return null;
  9. }
  10. @Override
  11. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  12. String userName = servletRequest.getParameter("userName");
  13. servletResponse.setContentType("text/html;charset=utf-8");
  14. PrintWriter writer = servletResponse.getWriter();
  15. if ("mayikt".equals(userName)) {
  16. writer.println("可以访问");
  17. } else {
  18. writer.println("无法访问");
  19. }
  20. writer.close();
  21. }
  22. @Override
  23. public String getServletInfo() {
  24. return null;
  25. }
  26. @Override
  27. public void destroy() {
  28. }
  29. }

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中编写 动态资源

  1. @WebServlet("/mayikt")
  2. public class IndexServlet implements Servlet {
  3. /**
  4. * @param servletConfig
  5. * @throws ServletException
  6. */
  7. @Override
  8. public void init(ServletConfig servletConfig) throws ServletException {
  9. }
  10. @Override
  11. public ServletConfig getServletConfig() {
  12. return null;
  13. }
  14. /**
  15. * tomcat启动完成
  16. * 127.0.0.1:8080/项目名称/mayikt 执行 service 通过service方法获取servletRequest、servletResponse
  17. *
  18. * @param servletRequest
  19. * @param servletResponse
  20. * @throws ServletException
  21. * @throws IOException
  22. */
  23. @Override
  24. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  25. System.out.println("mayikt644");
  26. // 需要通过servletRequest对象获取到客户端传递参数到服务器端
  27. String userName = servletRequest.getParameter("userName");
  28. PrintWriter writer = servletResponse.getWriter();
  29. if ("mayikt".equals(userName)) {
  30. // 返回数据 ok
  31. writer.println("ok");
  32. } else {
  33. // fail
  34. writer.println("fail");
  35. }
  36. writer.close();// 关闭资源
  37. }
  38. @Override
  39. public String getServletInfo() {
  40. return null;
  41. }
  42. @Override
  43. public void destroy() {
  44. }
  45. }

3. 执行原理:

    (1) 当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
    (2)查找web.xml文件,是否有对应的<url-pattern>标签体内容。
    (3)如果有,则在找到对应的<servlet-class>全类名
    (4)tomcat会将字节码文件加载进内存,并且创建其对象
    (5) 调用其方法

4. Servlet3.0

  1. 传统xml方式配置Servlet:
  2. 在web.xml中配置:
  3. <!--配置Servlet -->
  4. <servlet>
  5. <servlet-name>demo1</servlet-name>
  6. <servlet-class>cn.itcast.web.servlet.ServletDemo1</servlet-class>
  7. </servlet>
  8. <servlet-mapping>
  9. <servlet-name>demo1</servlet-name>
  10. <url-pattern>/demo1</url-pattern>
  11. </servlet-mapping>

    * 好处:
        * 支持注解配置。可以不需要web.xml了。

    * 步骤:
        1. 创建JavaEE项目,选择Servlet的版本3.0以上,可以不创建web.xml
        2. 定义一个类,实现Servlet接口
        3. 复写方法
        4. 在类上使用@WebServlet注解,进行配置
            * @WebServlet("资源路径")

5. servlet debug调试

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

f8---下一步

f9---跳到下一个断点

6. servlet 执行流程

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

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

断点分析

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

7. servlet 生命周期(重点)

(1)创建servlet

选择创建servlet :

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

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

先创建servlet

在执行service方法

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

如果是第二次访问servlet

在执行service方法

提前创建servlet

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

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

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

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

  1. 创建Servlet实例
  2. web容器负责加载Servlet,当web容器启动时或者是在第一次使用这个Servlet时,容器会负责创建Servlet实例,但是用户必须通过部署描述符(web.xml)指定Servlet的位置,或者在类上加上@WebServlet,成功加载后,web容器会通过反射的方式对Servlet进行实例化。
  3. @WebServlet(urlPatterns = "/mayiktmeite",loadOnStartup = 1)
  4. 负数---第一次被访问时创建Servlet对象 @WebServlet(urlPatterns = "/mayiktmeite",loadOnStartup = -1)
  5. 0或者正数:服务器启动时创建Servlet对象 数字越小优先级越高
  6. MeiteServlet loadOnStartup = 1
  7. YushengjunServlet loadOnStartup = 2
  8. 底层会根据loadOnStartup (从0开始)值排序 越小越优先加载创建

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

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

该方法只会执行一次。

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

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

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

  1.  Servlet初始化之后,将一直存在于容器中,service()响应客户端请求
  2.     a. 如果客户端发送GET请求,容器调用Servlet的doGet方法处理并响应请求
  3.     b. 如果客户端发送POST请求,容器调用Servlet的doPost方法处理并响应请求
  4.     c. 或者统一用service()方法处理来响应用户请求
  5. service()是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,
  6. 该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和
  7. 一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。
  8. 默认的服务功能是调用与HTTP请求的方法相应的do功能。要注意的是,在service()方法被容器调用之前,
  9. 必须确保init()方法正确完成。容器会构造一个表示客户端请求信息的请求对象(类型为ServletRequest)
  10. 和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()方法。
  11. 在service()方法中,Servlet对象通过ServletRequest对象得到客户端的相关信息和请求信息,
  12. 在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。

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

存放销毁相关代码

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

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

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

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

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

  1. @WebServlet("/count")
  2. public class CountServlet implements Servlet {
  3. private Integer count = 1;
  4. public CountServlet() {
  5. System.out.println("CountServlet 对象被创建");
  6. }
  7. @Override
  8. public void init(ServletConfig servletConfig) throws ServletException {
  9. }
  10. @Override
  11. public ServletConfig getServletConfig() {
  12. return null;
  13. }
  14. @Override
  15. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  16. servletResponse.setContentType("text/html;charset=utf-8");
  17. PrintWriter writer = servletResponse.getWriter();
  18. writer.println("您是第" + count + "个人访问网站!");
  19. try {
  20. Thread.sleep(5000);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. writer.close();
  25. synchronized (this) {
  26. count++;
  27. }
  28. }
  29. @Override
  30. public String getServletInfo() {
  31. return null;
  32. }
  33. @Override
  34. public void destroy() {
  35. }
  36. }

9. 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的初始化参数。

  1. @WebServlet(urlPatterns = "/servletConfig", initParams = {@WebInitParam(name = "p1", value = "mayikt")})
  2. public class ServletConfigServlet implements Servlet {
  3. private ServletConfig servletConfig;
  4. @Override
  5. public void init(ServletConfig servletConfig) throws ServletException {
  6. this.servletConfig = servletConfig;
  7. }
  8. @Override
  9. public ServletConfig getServletConfig() {
  10. return servletConfig;
  11. }
  12. @Override
  13. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  14. String value = getServletConfig().getInitParameter("p1");
  15. PrintWriter writer = servletResponse.getWriter();
  16. writer.println(value);
  17. writer.close();
  18. }
  19. @Override
  20. public String getServletInfo() {
  21. return null;
  22. }
  23. @Override
  24. public void destroy() {
  25. }
  26. }

10. servlet 体系结构

  1. * GenericServlet:将Servlet接口中其他的方法做了默认空实现,只将service()方法作为抽象
  2. * 将来定义Servlet类时,可以继承GenericServlet,实现service()方法即可
  3. * HttpServlet:对http协议的一种封装,简化操作
  4. 1. 定义类继承HttpServlet
  5. 2. 复写doGet/doPost方法

11. Servlet相关配置

  1. urlpartten:Servlet访问路径
  2.         1. 一个Servlet可以定义多个访问路径 : @WebServlet({"/d4","/dd4","/ddd4"})
  3.         2. 路径定义规则:
  4.             (1/xxx:路径匹配
  5.            (2/xxx/xxx:多层路径,目录结构
  6.            (3*.do:扩展名匹配

四、 request与response对象

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

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

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

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

1. request与response继承模型

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

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

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

2. request获取请求数据

(1) 获取请求行数据

                a. 获取请求方式 :GET
                    * String getMethod()  
                b. (*)获取虚拟目录:/day14
                    * String getContextPath()
                c. 获取Servlet路径: /demo1
                    * String getServletPath()
                d. 获取get方式请求参数:name=zhangsan
                    * String getQueryString()
                e. (*)获取请求URI:/day14/demo1
                    * String getRequestURI():        /day14/demo1
                    * StringBuffer getRequestURL()  :http://localhost/day14/demo1

                    * URL:统一资源定位符 : http://localhost/day14/demo1    中华人民共和国
                    * URI:统一资源标识符 : /day14/demo1                    共和国
                
                f. 获取协议及版本:HTTP/1.1
                    * String getProtocol()

                g. 获取客户机的IP地址:
                    * String getRemoteAddr()

(2)获取请求头数据

                * String getHeader(String name):通过请求头的名称获取请求头的值
                * Enumeration<String> getHeaderNames():获取所有的请求头名称

(3)获取请求体数据 (Post 才有)

         * 请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数
            * 步骤:
                a. 获取流对象
                    *  BufferedReader getReader():获取字符输入流,只能操作字符数据
                    *  ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据
                    

                b. 再从流对象中拿数据

3. request获取请求参数

不论get还是post请求方式都可以使用下列方法来获取请求参数
            a. String getParameter(String name):根据参数名称获取参数值    username=zs&password=123
            b. String[] getParameterValues(String name):根据参数名称获取参数值的数组  hobby=xx&hobby=game
            c. Enumeration<String> getParameterNames():获取所有请求的参数名称
            d. Map<String,String[]> getParameterMap():获取所有参数的map集合

?age=18&age=22

  1. Map<String, String[]> map = req.getParameterMap();
  2. for(String key : map.keySet()){
  3. System.out.print(key + ":");
  4. String[] values = map.get(key);
  5. for(String value : values){
  6. System.out.print(value + " ");
  7. }
  8. System.out.println();
  9. }
  1. @WebServlet("/httpServletDemo06")
  2. public class HttpServletDemo06 extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. this.doPost(req, resp);
  6. }
  7. @Override
  8. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  9. // 判断请求是get还是post请求
  10. String method = req.getMethod();
  11. String parameters = null;
  12. switch (method) {
  13. case "GET":
  14. parameters = req.getQueryString();
  15. break;
  16. case "POST":
  17. BufferedReader reader = req.getReader();
  18. parameters = reader.readLine();
  19. reader.close();
  20. break;
  21. }
  22. HashMap<String, String> parametersMap = new HashMap<>();
  23. String[] sp1 = parameters.split("&");
  24. for (int i = 0; i < sp1.length; i++) {
  25. String[] sp2 = sp1[i].split("=");
  26. String key = sp2[0];
  27. String value = sp2[1];
  28. parametersMap.put(key, value);
  29. }
  30. System.out.println(parametersMap);
  31. }
  32. }
  33. 1

4. 中文乱码问题

                * get方式:tomcat 8 已经将get方式乱码问题解决了
                * post方式:会乱码
                    * 解决浏览器请求的数据乱码:在获取参数前,设置request的编码

                                req.setCharacterEncoding("UTF-8");

5. request请求转发

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

(1) 步骤:

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

RequestDispatcher getRequestDispatcher(String path)

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

forward(ServletRequest request, ServletResponse response)

  1. RequestDispatcher requestDispatcher = request.getRequestDispatcher("/requestDemo6");
  2. requestDispatcher.forward(request,response);

(2)特点:

  1. 浏览器地址栏路径不发生变化;
  2. 只能转发到当前服务器内部资源中;
  3. 转发是一次请求;

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

  1. 共享数据:
  2. * 域对象:一个有作用范围的对象,可以在范围内共享数据
  3. * request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
  4. * 方法:
  5. 1. void setAttribute(String name,Object obj):存储数据
  6. 2. Object getAttitude(String name):通过键获取值
  7. 3. void removeAttribute(String name):通过键移除键值对
  8. * 注意:这个放于forward方法的上面,不然响应太快会导致无法获取信息!
  9. 获取ServletContext:
  10. * ServletContext getServletContext()

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

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

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

  1. @WebServlet("/httpServletDemo09")
  2. public class HttpServletDemo09 extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. doPost(req, resp);
  6. }
  7. @Override
  8. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  9. req.setAttribute("name", "mayikt");
  10. req.getRequestDispatcher("/httpServletDemo10").forward(req, resp);
  11. }
  12. }
  13. package com.mayikt.servlet;
  14. import jakarta.servlet.ServletException;
  15. import jakarta.servlet.annotation.WebServlet;
  16. import jakarta.servlet.http.HttpServlet;
  17. import jakarta.servlet.http.HttpServletRequest;
  18. import jakarta.servlet.http.HttpServletResponse;
  19. import java.io.IOException;
  20. @WebServlet("/httpServletDemo10")
  21. public class HttpServletDemo10 extends HttpServlet {
  22. @Override
  23. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  24. doPost(req, resp);
  25. }
  26. @Override
  27. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  28. String name = (String) req.getAttribute("name");
  29. System.out.println(name);
  30. System.out.println("httpServletDemo10");
  31. }
  32. }

6. response响应数据

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

  1. 1. 设置响应行
  2. 1. 格式:HTTP/1.1 200 ok
  3. 2. 设置状态码:setStatus(int sc)
  4. 2. 设置响应头:setHeader(String name, String value)
  5. 3. 设置响应体:
  6. * 使用步骤:
  7. 1. 获取输出流
  8. * 字符输出流:PrintWriter getWriter()
  9. * 字节输出流:ServletOutputStream getOutputStream()
  10. 2. 使用输出流,将数据输出到客户端浏览器

中文乱码问题解决:

  1. //设置服务端的编码
  2. resp.setCharacterEncoding("UTF-8");
  3. //通过设置响应头设置客户端(浏览器的编码)
  4. resp.setHeader("Content-type","text/html;utf-8");
  5. //这个方法可以同时设置客户端和服务端,因为它会调用setCharacterEncoding方法
  6. resp.setContentType("text/html;charset=utf-8");

响应格式分为3个部分

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

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

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

(3)响应体

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

  1. PrintWriter out = response.getWriter():获取字符流;
  2. ServletOutputStream out = response.getOutputStream():获取字节流;
  3. 如果响应正文内容为字符,那么使用response.getWriter(),如果响应内容是字节,
  4. 例如下载时,那么可以使用response.getOutputStream()。
  1. //设置错误的响应码
  2. resp.setError(404,"未找到请求的资源!");
  3. //设置正确的响应码
  4. resp.setStatus(200);

HttpServletResponse与ServletResponse

  1. ServletResponse--- 接口 java提供的响应对象根接口
  2. HttpServletResponse --- 接口(继承ServletResponse) java提供的对http协议封装响应对象接口
  3. org.apache.catalina.connnector.ResponseFacade —— 类(Tomcat编写的,实现HttpServletResponse )

7. response重定向

(1)重定向原理

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

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

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

www.mayikt.com

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

  1. void sendRedirect(String location) 使用指定的重定向位置URL,向客户端发送临时重定向响应
  2. resp.setStatus(302);
  3. resp.setHeader("Location","www.mayikt.com");
  1. 1)重定向是两次请求,不能使用request对象来共享数据,重定向之后,浏览器地址栏的URL会发生改变
  2. 2)重定向过程中会将前面Request对象销毁,然后创建一个新的Request对象
  3. 3)重定向的URL可以是其它项目工程,即可以访问其他站点(服务器)的资源
(2)重定向与转发区别
  1. 1.转发只能将请求转发给同一个web应用(项目工程)中的其他组件(servlet程序);
  2. 重定向可以重定向到任意的地址,网络地址或是文件地址(跨项目文件夹 www.mayikt.com)
  3. 2.重定向访问结束后,浏览器地址栏URL发生变化,变成了重定向后的URL;转发则不变
  4. 重定向对浏览器的请求直接做出响应,结果就是告诉浏览器去重新发出另一个新的URL访问请求;
  5. 请求转发在服务器端内部将请求转发给另一个资源,浏览器不知道服务器程序内部发生了转发过程
  6. 3.请求转发调用者与被调用者之间共享相同的请求对象,属于同一个请求和响应过程;
  7. 重定向则是不同的请求和响应过程

8. ServletContext对象

(1) 概念:代表整个web应用,可以和程序的容器(服务器)来通信

(2)获取:
  1. 1. 通过request对象获取
  2. request.getServletContext();
  3. 2. 通过HttpServlet获取
  4. this.getServletContext();
  1. req.getServletContext() org.apache.catalina.core.ApplicationContextFacade@7922f5a8
  2. this.getServletContext() org.apache.catalina.core.ApplicationContextFacade@7922f5a8
(3)功能:
  1. 1. 获取MIME类型:
  2. * MIME类型:在互联网通信过程中定义的一种文件数据类型
  3. * 格式: 大类型/小类型   text/html image/jpeg
  4. * 获取:String getMimeType(String file)  
  5. 2. 域对象:共享数据
  6. 1. setAttribute(String name,Object value)
  7. 2. getAttribute(String name)
  8. 3. removeAttribute(String name)
  9. * ServletContext对象范围:所有用户所有请求的数据
  10. 3. 获取文件的真实(服务器)路径
  11. 1. 方法:String getRealPath(String path)  
  12. String b = context.getRealPath("/b.txt");//web目录下资源访问
  13.         System.out.println(b);
  14.       String c = context.getRealPath("/WEB-INF/c.txt");
  15. //WEB-INF目录下的资源访问
  16.       System.out.println(c);
  17.       String a = context.getRealPath("/WEB-INF/classes/a.txt");
  18. //src目录下的资源访问
  19.       System.out.println(a);
  1. System.out.println( req.getServletContext().getRealPath("/loginServlet"));
  2. //F:\扬州软件园\02_JavaWeb\07_Tomcat&Servlet\作业\login-tomcat\out\artifacts\login_tomcat_war_exploded\loginServlet

五、文件下载:

1. 步骤:

(1)定义页面,编辑超链接href属性,指向servlet,传递资源名称fileName

(2)定义servlet
       a. 获取文件名称
       b.  使用字节输入流加载文件进内存
       c.   指定response的响应头: content-disposition:attachment;filename=xXX

       d.将数据写出到response输出流

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

初始化数据库表结构

  1. CREATE TABLE `mayikt_users` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `userName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  4. `userPwd` varchar(255) DEFAULT NULL,
  5. PRIMARY KEY (`id`)
  6. ) 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工具类

  1. public class MayiktJdbcUtils {
  2. /**
  3. * 1.需要将我们的构造方法私有化 ---工具类 不需要 new出来 是通过类名称.方法名称访问
  4. */
  5. private MayiktJdbcUtils() {
  6. }
  7. /**
  8. * 2.定义工具类 需要 声明 变量
  9. */
  10. private static String driverClass;
  11. private static String url;
  12. private static String user;
  13. private static String password;
  14. /**
  15. *3.使用静态代码快 来给我们声明好 jdbc变量赋值(读取config.properties)
  16. */
  17. static {
  18. try {
  19. // 1.读取config.properties IO 路径 相对路径
  20. InputStream resourceAsStream = MayiktJdbcUtils.class.getClassLoader().
  21. getResourceAsStream("config.properties");
  22. // 2.赋值给我们声明好的变量
  23. Properties properties = new Properties();
  24. properties.load(resourceAsStream);
  25. driverClass = properties.getProperty("driverClass");
  26. url = properties.getProperty("url");
  27. user = properties.getProperty("user");
  28. password = properties.getProperty("password");
  29. // 3.注册驱动类
  30. Class.forName(driverClass);
  31. } catch (Exception e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. /**
  36. * 4.封装连接方法
  37. */
  38. public static Connection getConnection() throws SQLException {
  39. Connection connection = DriverManager.getConnection(url, user, password);
  40. return connection;
  41. }
  42. /**
  43. * 5.封装释放连接方法 (重载)
  44. */
  45. public static void closeConnection(ResultSet resultSet, Statement statement, Connection connection) {
  46. // 1.查询 释放连接 resultSet statement connection
  47. try {
  48. if (resultSet != null)
  49. resultSet.close();
  50. if (statement != null)
  51. statement.close();
  52. if (connection != null)
  53. connection.close();
  54. } catch (Exception e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. /**
  59. * 增删改---释放jdbc资源
  60. *
  61. * @param statement
  62. * @param connection
  63. */
  64. public static void closeConnection(Statement statement, Connection connection) {
  65. // 1.查询 释放连接 resultSet statement connection
  66. closeConnection(null, statement, connection);
  67. }
  68. /**
  69. * 开启事务
  70. *
  71. * @param connection
  72. * @throws SQLException
  73. */
  74. public static void beginTransaction(Connection connection) throws SQLException {
  75. connection.setAutoCommit(false);
  76. }
  77. /**
  78. * 提交事务
  79. *
  80. * @param connection
  81. * @throws SQLException
  82. */
  83. public static void commitTransaction(Connection connection) throws SQLException {
  84. connection.commit();
  85. }
  86. /**
  87. * 回滚事务
  88. *
  89. * @param connection
  90. */
  91. public static void rollBackTransaction(Connection connection) {
  92. if (connection != null) {
  93. try {
  94. connection.rollback();
  95. } catch (SQLException e) {
  96. e.printStackTrace();
  97. }
  98. }
  99. }
  100. /**
  101. * 关闭事务
  102. *
  103. * @param connection
  104. */
  105. public static void endTransaction(Connection connection) {
  106. if (connection != null) {
  107. try {
  108. connection.setAutoCommit(true);
  109. } catch (SQLException e) {
  110. e.printStackTrace();
  111. }
  112. }
  113. }
  114. }

拷贝 config.properties

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

3.编写注册 servlet

  1. @WebServlet("/register")
  2. public class RegisterServlet extends HttpServlet {
  3. @Override
  4. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. PrintWriter writer = resp.getWriter();
  6. resp.setContentType("text/html;charset=utf-8");
  7. //1.获取参数
  8. String userName = req.getParameter("userName");
  9. if (StringUtils.isEmpty(userName)) {
  10. writer.print("userName不能为空!");
  11. return;
  12. }
  13. String userPwd = req.getParameter("userPwd");
  14. if (StringUtils.isEmpty(userPwd)) {
  15. writer.print("userPwd不能为空!");
  16. return;
  17. }
  18. Connection connection = null;
  19. PreparedStatement preparedStatement = null;
  20. try {
  21. connection = MayiktJdbcUtils.getConnection();
  22. //2.验证用户名称和密码
  23. MayiktJdbcUtils.beginTransaction(connection);// 开启事务
  24. String insertSql = "INSERT INTO `mayikt`.`mayikt_users` (`id`, `userName`, `userPwd`) VALUES (null, ?,?); ";
  25. preparedStatement = connection.prepareStatement(insertSql);
  26. preparedStatement.setString(1, userName);
  27. preparedStatement.setString(2, userPwd);
  28. int insertResult = preparedStatement.executeUpdate();
  29. MayiktJdbcUtils.commitTransaction(connection);
  30. String result = insertResult > 0 ? "注册成功" : "注册失败";
  31. writer.println(result);
  32. } catch (Exception e) {
  33. e.printStackTrace();
  34. writer.println("error");
  35. // 回滚事务
  36. if (connection != null)
  37. MayiktJdbcUtils.rollBackTransaction(connection);
  38. } finally {
  39. if (writer != null) {
  40. writer.close();
  41. }
  42. MayiktJdbcUtils.closeConnection(null, preparedStatement, connection);
  43. }
  44. }
  45. }

4.编写注册html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>用户注册</title>
  6. </head>
  7. <body>
  8. <h1>用户注册</h1>
  9. <form action="/mayikt_web_login_war_exploded/register" method="post">
  10. <!--name=mayikt&age=12 -->
  11. <span>用户名称:</span> <input type="text" name="userName"> <br>
  12. <span>密&nbsp;&nbsp;&nbsp;&nbsp;码:</span> <input type="text" name="userPwd" value=""><br>
  13. <input type="submit">
  14. </form>
  15. </body>
  16. </html>

jdbc+servlet用户登录

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>用户登录</title>
  6. </head>
  7. <body>
  8. <h1>用户登录</h1>
  9. <form action="/mayikt_web_login_war_exploded/login" method="post">
  10. <!--name=mayikt&age=12 -->
  11. <span>名称:</span> <input type="text" name="userName"> <br>
  12. <span>密&nbsp;&nbsp;&nbsp;码:</span> <input type="text" name="userPwd" value=""><br>
  13. <input type="submit">
  14. </form>
  15. </body>
  16. </html>
  1. @WebServlet("/login")
  2. public class LoginServlet extends HttpServlet {
  3. @Override
  4. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. //1.获取参数
  6. String userName = req.getParameter("userName");
  7. String userPwd = req.getParameter("userPwd");
  8. PrintWriter writer = resp.getWriter();
  9. resp.setContentType("text/html;charset=utf-8");
  10. Connection connection = null;
  11. PreparedStatement preparedStatement = null;
  12. ResultSet resultSet = null;
  13. try {
  14. //2.验证用户名称和密码
  15. connection = MayiktJdbcUtils.getConnection();
  16. String loginSql = "SELECT * FROM mayikt_users where userName=? and userPwd=? ";
  17. preparedStatement = connection.prepareStatement(loginSql);
  18. preparedStatement.setString(1, userName);
  19. preparedStatement.setString(2, userPwd);
  20. resultSet = preparedStatement.executeQuery();
  21. if (resultSet.next()) {
  22. String dbUserName = resultSet.getString(1);
  23. writer.println("恭喜" + userName + "登录成功");
  24. } else {
  25. writer.println("登录失败");
  26. }
  27. } catch (Exception e) {
  28. e.printStackTrace();
  29. writer.println("error");
  30. // 回滚事务
  31. if (connection != null)
  32. MayiktJdbcUtils.rollBackTransaction(connection);
  33. } finally {
  34. if (writer != null) {
  35. writer.close();
  36. }
  37. MayiktJdbcUtils.closeConnection(null, preparedStatement, connection);
  38. }
  39. }
  40. }

常见问题

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

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

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/191504
推荐阅读
相关标签
  

闽ICP备14008679号