Servlet 详解

文章目录
  • 一、Servlet 的定义
  • 二、Servlet 的生命周期
    • 1、Servlet 出生
    • 2、Servlet 服务
    • 3、Servlet 销毁
  • 三、Servlet 中的接口
    • 1、ServletRequest 和 ServletResponse
    • 2、ServletRequest
    • 3、ServletResponse
    • 4、ServletConfig
  • 四、Servlet 的实现
    • 1、实现Servlet有三种方式:
    • 2、GenericServlet
    • 3、HttpServlet
    • 4、启动创建 Servlet
    • 5、web.xml文件的继承
    • 6、ServletContext
      • 6.1、ServletContext概述
      • 6.2、获取ServletContext
      • 6.3、域对象的功能
      • 6.4、操作数据的方法
      • 6.5、初始化参数
  • 五、请求和响应
    • 1、Request
      • 1.1、概述
      • 1.2、常用方法
      • 1.3、实际应用
    • 2、Response
      • 2.1、概述
      • 2.2、响应正文
      • 2.3、设置响应头信息
      • 2.4、设置状态码及其他方法
      • 2.5、重定向
    • 3、GET 和 POST 的区别
    • 4、请求转发
    • 5、转发和重定向的区别

一、Servlet 的定义

Servlet 是 JavaWeb 的三大组件之一,它属于动态资源。Servlet 的作用是处理请求,服务器会把接收到的请求交给 Servlet 来处理,在 Servlet 中需要:

  • 接收请求数据;
  • 处理请求;
  • 完成响应;

二、Servlet 的生命周期

我们先创建一个实例来实现 Servlet 接口:

public class Servlet01 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 {

    }

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

    @Override
    public void destroy() {

    }
}

可以看到该实例默认实现了5个方法,我们主要讲解其中的3个。

方法:
  • void init(ServletConfig):出生之后(1次);
  • void service(ServletRequest request, ServletResponse response):每次处理请求时都会被调用;
  • void destroy():临死之前(1次);

特征:
  • 单例的,一个类只有一个对象;
  • 多线程的,不是线程安全的。

1、Servlet 出生

  • 默认情况下,服务器会在第一次访问 Servlet 时创建它,如果是在服务器启动时就创建 Servlet ,那么需要在 web.xml 中配置。
  • 对于一个 Servlet ,服务器只会创建一次,当访问 Servlet 时, 服务器会首先检查该 Servlet 是否创建过,如果创建过直接拿过来用,如果没有才会通过反射来创建该 Servlet 实例。
  • Servlet 出生后会立即调用 init() 方法,而且这个方法之后被调用一次,所以一些对 Servlet 的初始化操作可以放在该方法中。

2、Servlet 服务

当服务器每次接收到请求时,都会调用 service 方法,该方法是会被多次调用的。

3、Servlet 销毁

  • Servlet 是不会轻易消失的,通常在服务器关闭的时候才会销毁,在服务器关闭时,他会调用 destroy 方法,然后销毁 Servlet,我们可以将一些资源的释放放到该方法中。

三、Servlet 中的接口

在 Servlet 中有三个接口类型的参数:

  • ServletConfig:init()方法的参数,它表示Servlet配置对象,它对应Servlet的配置
  • ServletRequest:service() 方法的参数,它表示请求对象,它封装了所有与请求相关的数据,里面全是请求头之类get方法,它是由服务器创建的;
  • ServletResponse:service()方法的参数,它表示响应对象,里面很多响应的set方法,在 service() 方法中完成对客户端的响应需要使用这个对象;

1、ServletRequest 和 ServletResponse

ServletRequest 和 ServletResponse 是 Servlet 的service() 方法的两个参数:


一个是请求对象,可以从 ServletRequest 对象中获取请求数据; 一个是响应对象,可以使用 ServletResponse 对象完成响应。

  • ServletRequest 和 ServletResponse 的实例由服务器创建,然后传递给 service方法,如果要使用 HTTP 相关内容,那么需要将 ServletRequest 强转成 HttpServletRequest ,这很烦,因为我们经常要这么做,后面有一个类可以解决这个问题。

2、ServletRequest

  • String getHeader(String var1):获取指定请求头的值;
  • String getMethod():获取请求方法,例如GET或POST;
  • String getParameter(String paramName):获取指定请求参数的值;
  • void setCharacterEncoding(String encoding):设置请求体的编码!

request.setCharacterEncoding(“utf-8”)之后,再通过getParameter()方法获取参数值时,那么参数值都已经通过了转码,即转换成了UTF-8编码。所以,这个方法必须在调用 getParameter() 方法之前调用!

3、ServletResponse

  • PrintWriter getWriter():获取字符响应流,使用该流可以向客户端输出响应信息。
  • ServletOutputStream getOutputStream():获取字节响应流,当需要向客户端响应字节数据时,需要使用这个流;
  • void setCharacterEncoding(String encoding):用来设置字符响应流的编码;
  • void setHeader(String name, String value):向客户端添加响应头信息。 例如setHeader(“Refresh”, “5;url=http://www.baidu.cn”),表示 5 秒后自动刷新到http://www.baidu.cn;
  • void setContentType(String contentType):该方法是 setHeader(“content-type”, “xxx”) 的简便方法,即用来添加名为content-type响应头的方法。content-type响应头用来设置响应数据的MIME类型。 -例如要向客户端响应 jpg 的图片,那么可以 setContentType(“image/jepg”) ,如果响应数据为文本类型,那么还要同时设置编码 -例如setContentType(“text/html;chartset=utf-8”)表示响应数据类型为文本类型中的html类型,并且该方法会调用 setCharacterEncoding(“utf-8”) 方法;

4、ServletConfig

Servlet 的配置信息,即web.xml文件中的<servlet>元素。

  • 一个 ServletConfig 对象对应着一个 servlet 元素的配置信息(servlet-name,servlet-class)
  • getServletName():获取的是<servlet-name>
  • getServletContext():获取的是 Servlet 上下文对象

四、Servlet 的实现

1、实现Servlet有三种方式:

  • 实现 javax.servlet.Servlet 接口;
  • 继承 javax.servlet.GenericServlet 类;
  • 继承 javax.servlet.http.HttpServlet 类;

通常我们会去继承 HttpServlet 类来完成我们的 Servlet

2、GenericServlet

GenericServlet是Servlet接口的实现类,我们可以通过继承GenericServlet来编写自己的Servlet。

GenericServlet 还实现了ServletConfig 接口,所以可以直接调用getInitParameter()、getServletContext() 等 ServletConfig 的方法。

源代码:

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }

    public Enumeration<String> getInitParameterNames() {
        return this.getServletConfig().getInitParameterNames();
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

    public String getServletInfo() {
        return "";
    }

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }

    public void log(String msg) {
        this.getServletContext().log(this.getServletName() + ": " + msg);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        return this.config.getServletName();
    }
}

3、HttpServlet

HttpServlet 类是 GenericServlet 的子类,它提供了对 HTTP 请求的特殊支持,所以通常我们都会通过继承 HttpServlet 来完成自定义的 Servlet 。

  • HttpServlet 覆盖了 service() 方法 HttpServlet类中提供了 service(HttpServletRequest,HttpServletResponse) 方法,这个方法是 HttpServlet 自己的方法,不是从 Servlet 继承来的。在 HttpServlet的service(ServletRequest,ServletResponse) 方法中会把ServletRequest和ServletResponse 强转成 HttpServletRequest 和 HttpServletResponse ,然后调用,所以这样一来就不用我们自己强转了。
  • doGet() 和 doPost() 在 HttpServlet 的 service(HttpServletRequest,HttpServletResponse) 方法会去判断当前请求是 GET 还是 POST ,如果是 GET 请求,那么会去调用本类的doGet() 方法,如果是 POST 请求会去调用 doPost() 方法,这说明我们在子类中去覆盖 doGet() 或 doPost() 方法即可。

4、启动创建 Servlet

1、< load-on-startup >

<servlet>
	<servlet-name>hello</servlet-name>
	<servlet-class>cn.lsu.servlet.HelloServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet>元素中配置<load-on-startup>元素可以让服务器在启动时就创建该Servlet,其中<load-on-startup>元素的值必须是大于等于的整数,它的使用是服务器启动时创建 Servlet 的顺序。

2、< url-pattern >

<servlet-mapping>
	<servlet-name>hello</servlet-name>
	<url-pattern>/hello</url-pattern>
</servlet-mapping>

<url-pattern><servlet-mapping>的子元素,用来指定Servlet的访问路径,即URL。

可以在<servlet-mapping>中给出多个<url-pattern>,这样它所绑定的所有的 URL 都是指向这个 Servlet 的。

还可以使用通配符:

<url-pattern>.do</url-pattern>:/abc/def/ghi.do、/a.do,都匹配

<url-pattern>/*<url-pattern>:匹配所有URL;

5、web.xml文件的继承

在 Tomcat 的conf/web.xml路径下有一个所有的 web.xml 共同的父文件。

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0">
<servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
</servlet>

    <servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>
    
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    
    <!-- 这里省略了大概4000多行的MIME类型的定义,这里只给出两种MIME类型的定义 -->
    <mime-mapping>
        <extension>bmp</extension>
        <mime-type>image/bmp</mime-type>
    </mime-mapping>
    <mime-mapping>
        <extension>htm</extension>
        <mime-type>text/html</mime-type>
    </mime-mapping>
    
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

6、ServletContext

6.1、ServletContext概述

服务器会为每个应用创建一个 ServletContext 对象:

  • ServletContext对象的创建是在服务器启动时完成的;
  • ServletContext对象的销毁是在服务器关闭时完成的。

ServletContext 对象的作用 是在整个Web应用动态资源之间共享数据

6.2、获取ServletContext

  • ServletConfig 下的 getServletContext();
  • GenericServlet 下的 getServletContext();(包括子类 HttpServlet )
  • HttpSession 下的 getServletContext();
  • ServletContextEvent 下的 getServletContext();

在 Servlet 中获取 ServletContext 对象:

  • 在void init(ServletConfig config)中: ServletContext context = config.getServletContext(); ServletConfig类的getServletContext()方法可以用来获取ServletContext对象;
  • GenericServlet类有getServletContext()方法,所以可以直接使用this.getServletContext()来获取;
public class MyServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) {
    	ServletContext context = this.getServletContext();
	}
}

6.3、域对象的功能

ServletContext是JavaWeb四大域对象之一:

  • PageContext;
  • ServletRequest;
  • HttpSession;
  • ServletContext;

所有域对象都有存取数据的功能,因为域对象内部有一个`Map`,用来存储数据

6.4、操作数据的方法

  • void setAttribute(String name, Object value):用来存储一个对象;
  • Object getAttribute(String name):用来获取ServletContext中的数据;
  • void removeAttribute(String name):用来移除ServletContext中的域属性;
  • Enumeration getAttributeNames():获取所有域属性的名称;

6.5、初始化参数

1、应用初始化参数

该参数是每个 Servlet 独有的,是局部参数。

2、公共初始化参数

该参数为所有 Servlet 都可用。

可以在 web.xml 中配置:

<web-app ...>
  ...
  <context-param>(为ServletContext设置的公共初始化参数)
	<param-name>paramName1</param-name>
	<param-value>paramValue1</param-value>  	
  </context-param>
  <context-param>
	<param-name>paramName2</param-name>
	<param-value>paramValue2</param-value>  	
  </context-param>
</web-app>

使用域对象获取:

ServletContext context = this.getServletContext();
String value1 = context.getInitParameter("paramName1");
String value2 = context.getInitParameter("paramName2");

五、请求和响应

服务器每次收到请求时,都会为这个请求开辟一个新的线程。

1、Request

1.1、概述

request 是 Servlet.service() 方法的一个参数,类型为javax.servlet.http.HttpServletRequest。在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用 Servlet.service() 方法时传递给 service() 方法,这说明在 service() 方法中可以通过 request 对象来获取请求数据。


request的功能可以分为以下几种:

  • 封装了请求头数据;
  • 封装了请求正文数据,如果是GET请求,那么就没有正文;
  • request是一个域对象,可以把它当成Map来添加获取数据;
  • request提供了请求转发和请求包含功能。

一个请求会创建一个request对象,如果在一个请求中经历了多个 Servlet ,那么多个 Servlet 就可以使用 request 来共享数据

request 的域方法同 ServletContext 的域方法。

  • void setAttribute(String name, Object value):用来存储一个对象;
  • Object getAttribute(String name):用来获取ServletContext中的数据;
  • void removeAttribute(String name):用来移除ServletContext中的域属性;
  • Enumeration getAttributeNames():获取所有域属性的名称;

1.2、常用方法

1、获取请求头数据:

  • String getHeader(String name):获取指定名称的请求头;

2、获取请求相关的其它方法:

  • String getContentType():获取请求类型,如果请求是GET,那么这个方法返回null;如果是POST请求,那么默认为application/x-www-form-urlencoded,表示请求体内容使用了URL编码;
  • String getMethod():返回请求方法,例如:GET/POST
  • String getCharacterEncoding():获取请求编码;默认ISO-8859-1编码;
  • void setCharacterEncoding(String code):设置请求编码,只对请求体有效;
  • String getContextPath():返回上下文路径(/项目名),例如:/hello
  • String getQueryString():返回请求URL中的参数,例如:name=zhangSan
  • String getRequestURI():返回请求URI路径,例如:/hello/oneServlet
  • StringBuffer getRequestURL():返回请求URL路径,例如:http://localhost/hello/oneServlet,即返回除了参数以外的路径信息;
  • String getServletPath():返回Servlet路径,例如:/oneServlet
  • String getRemoteAddr():返回当前客户端的IP地址;
  • String getRemoteHost():返回当前客户端的主机名,但这个方法的实现还是获取IP地址;
  • String getScheme():返回请求协议,例如:http;
  • String getServerName():返回主机名,例如:localhost
  • int getServerPort():返回服务器端口号,例如:8080

1.3、实际应用

防盗链的应用:

可以使用request.getAttribute(“Referer”)如果不是当前页面,那么属于盗链,则跳转到当前页面如果是从地址栏直接输入URL,那么Referee返回的是null。

2、Response

2.1、概述

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

response对象的功能分为以下四种:

  • 设置响应头信息;
  • 发送状态码;
  • 设置响应正文;
  • 重定向。

2.2、响应正文

response是响应对象,向客户端输出响应正文(响应体) 可以使用response的响应流:

  • PrintWriter out = response.getWriter():获取字符流;
  • ServletOutputStream out = response.getOutputStream():获取字节流;

如果响应正文内容为字符(html),那么使用response.getWriter(),如果响应内容是字节(图片等),例如下载时,那么可以使用response.getOutputStream()

在一个请求中,不能同时使用这两个流!
  • 在使用 response.getWriter() 时需要注意默认字符编码为 ISO-8859-1,如果希望设置字符流的字符编码为utf-8,可以使用response.setCharaceterEncoding(“utf-8”) 来设置。这样可以保证输出给客户端的字符都是使用UTF-8编码的!
  • 但客户端浏览器并不知道响应数据是什么编码的!如果希望通知客户端使用UTF-8来解读响应数据,那么还是使用response.setContentType(“text/html;charset=utf-8”)方法比较好,因为这个方法不只会调用response.setCharaceterEncoding(“utf-8”),还会设置content-type响应头,客户端浏览器会使用 content-type 头来解读响应数据。

缓冲区:

  • response.getWriter() 是 PrintWriter 类型,所以它有缓冲区,缓冲区的默认大小为8KB。也就是说,在响应数据没有输出8KB之前,数据都是存放在缓冲区中,而不会立刻发送到客户端。当 Servlet 执行结束后,服务器才会去刷新流,使缓冲区中的数据发送到客户端。
  • 可以使用 response.flushBuffer() 方法手动刷新缓冲区。

字节响应流:

将一张图片转为字节流写入到response中

//读取图片,使用commons-io包的方法。
byte[] image = IOUtils.toByteArray(new FileInputStream(this.getServletContext().getRealPath("/images/cat.jpeg")));

response.getOutputStream().write(image);

2.3、设置响应头信息

可以使用 response 对象的setHeader()方法来设置响应头!使用该方法设置的响应头最终会发送给客户端浏览器!

response.setHeader("Refresh","5; URL=http://www.baidu.cn"):5秒后自动跳转到百度主页。

2.4、设置状态码及其他方法

  • response.setContentType(“text/html;charset=utf-8”)
  • response.setCharacterEncoding(“utf-8”)
  • response.setStatus(200)
  • response.sendError(404, “您要查找的资源不存在”)

2.5、重定向

当你访问http://www.sun.com时,你会发现浏览器地址栏中的URL会变成http://www.oracle.com/us/sun/index.html,这就是重定向了。

重定向是服务器通知浏览器去访问另一个地址,即再发出另一个请求。

1、设置响应码

  • 响应码为302表示重定向。所以完成重定向的第一步就是设置响应码为302。

2、设置Location头

  • 因为重定向是通知浏览器发出第二个请求,所以浏览器需要知道第二个请求的URL,所以完成重定向的第二步是设置Location头,指定第二个请求的URL地址。
public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.setStatus(302);
		response.setHeader("Location", "http://www.baidu.cn");
	}
}

当访问 AServlet 后,会通知浏览器重定向到百度主页。客户端浏览器解析到响应码为302后,就知道服务器让它重定向,所以它会马上获取响应头Location,然发出第二个请求。

不过有更方便的方法:

response.sendRedirect()方法会设置响应头为302,以设置Location响应头。

如果要重定向的URL是在同一个服务器内,那么可以使用相对路径,例如:

public class AServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		response.sendRedirect("/hello/BServlet");
                              //注意是-/项目名/路径(请求URI)
	}
}

3、GET 和 POST 的区别

GET请求和POST请求的区别:

GET请求:

  • 请求参数会在浏览器的地址栏中显示,所以不安全;
  • 请求参数长度限制长度在1K之内;
  • GET请求没有请求体,无法通过 request.setCharacterEncoding() 来设置参数的编码;

POST请求:

  • 请求参数不会显示浏览器的地址栏,相对安全;
  • 请求参数长度没有限制;
  • 无论是GET|POST请求,都可以使用相同的API来获取请求参数。
  • 请求参数有一个key一个value的,也有一个key多个value的。

4、请求转发

无论是请求转发还是请求包含,都表示由多个 Servlet 共同来处理一个请求。例如 Servlet1 来处理请求,然后 Servlet1 又转发给 Servlet2 来继续处理这个请求。

在AServlet中,把请求转发到BServlet:

参数是Servlet路径(servlet-mapping中的url-pattern)—相当于/项目名

public class AServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	RequestDispatcher rd = request.getRequestDispatcher("/BServlet");
	rd.forward(request, response);
}

5、转发和重定向的区别

转发是由服务端进行的页面跳转:

重定向是由客户端进行的页面跳转:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 从零开始重新认识 SpringMVC

    默认情况下,你在方法的参数列表中写什么,前端表单中 input 标签的 name 属性值就要是什么,比如:

    wsuo
  • Ubuntu 19.1 安装配置中文环境

    进去之后千万别乱动,按键盘的 i ,然后右键鼠标 paste ,把下面的内容复制进去,之后按键盘的 esc ,然后输入冒号:(英文输入法下),然后输入 wq ,...

    wsuo
  • 如何理解面向切面编程中的切面?

    我们先从几何的角度出发,我们都知道点构成面,任何一个面都是由很多个点构成的,比如桌面是由很多个木头点构成的,而这些木头点都有相同的特性:他们都是木头,如果我们想...

    wsuo
  • JavaWeb - Servlet:基本概念,HTTP 协议,Tomcat 服务器

    C/S 架构(Client/Server,客户端/服务器模式),是一种比较早的软件体系结构,也是生活中很常见的结构。这种结构将需要处理的业务合理地分配到客户端和...

    RendaZhang
  • Java Lombok 常用注解

    生成toString方法 可使用参数排除部分字段 @ToString(exclude="someField")

    heasy3
  • Java入门 - 面向对象 - 05.封装

    原文地址:http://www.work100.net/training/java-encapsulation.html

    光束云
  • Effective Java-嵌套类(内部类)

    嵌套类(nested class)是指被定义在另一个类内部的类。嵌套类的存在目的应该只是为外围类(enclosing class)提供服务。嵌套类有四种:

    用户2146693
  • Java常用API(ArrayList类)

    为了更加方便的储存对象,因为使用普通的数组来存储对象太过麻烦了,因为数组的一个很大的弱点就是长度从一开始就固定了,所以Java提供了另一个容器 java.uti...

    一只胡说八道的猴子
  • 如果让我回到大学,我会这样学习Servlet

    前言 工作已经有一段时间了,如果让我重新学Servlet,我会怎么学呢?下面抛出两个常见的问题,我分开来解答

    Java3y
  • JAVA设计模式-观察者模式

    观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象状态改变时,他的所有依赖者都会收到通知并自动更新。

    DH镔

扫码关注云+社区

领取腾讯云代金券