嵌入式Jetty

本文详细介绍了如何在嵌入式模式下使用Jetty服务器,从创建简单的HTTP处理程序到配置servlet、使用@WebServlet注解以及启用JavaServer Pages(JSP)支持。通过示例代码,展示了如何在不依赖独立服务器的情况下直接运行JavaWeb应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Jetty 可以在嵌入式模式下运行。这意味着无需构建 WAR 文件并将其部署在独立的 Jetty 服务器中。Jetty 是一个软件组件,可以像任何其他 POJO(Plain Old Java Object)一样实例化和使用。

简单的处理程序

AHandler是处理传入请求的组件。请求在handle方法中处理。处理程序被传递 servlet API 请求和响应对象,但它们不是 servlet。

$ mkdir simplehandler
$ cd simplehandler/
$ mkdir -p src/com/zetcode
$ mkdir build
$ touch src/com/zetcode/SimpleHandlerEx.java

我们创建一个新的项目目录及其结构。

SimpleHandlerEx.java
package com.zetcode;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
 
public class SimpleHandlerEx extends AbstractHandler {
 
    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request, 
        HttpServletResponse response) throws IOException, ServletException {
        
        response.setContentType("text/plain;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        baseRequest.setHandled(true);
        response.getWriter().println("Hello there");
    }
 
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);
        server.setHandler(new SimpleHandlerEx());
        server.start();
        server.join();
    }
}

启动服务器并为传入请求设置处理程序。

response.setContentType("text/plain;charset=utf-8");

由于我们只输出简单的文本,我们设置 text/plain 媒体类型。Internet 媒体类型是 Internet 上使用的标准标识符,用于指示文件包含的数据类型。

response.getWriter().println("Hello there");

作为响应,我们发送一条简单的消息。该getWriter方法返回一个PrintWriter向客户端发送字符文本的对象。

server.join();

join方法使服务器线程加入当前线程。在服务器准备好之前它一直处于阻塞状态。该方法还等到服务器完全停止。

build.xml
<?xml version="1.0" encoding="UTF-8"?>

<project name="SimpleHandlerEx" default="compile">
    
    <property name="name" value="simplehandler"/>
    <property environment="env"/>
    <property name="src.dir" value="src"/>
    <property name="build.dir" value="build"/>
    <property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>
    
    <path id="compile.classpath">
        <fileset dir="${jetty.lib.dir}"> 
            <include name="**/*.jar"/> 
        </fileset>     
    </path>
        
    <path id="run.classpath">
        <pathelement path="${build.dir}"/>
        <fileset dir="${jetty.lib.dir}"> 
            <include name="**/*.jar"/> 
        </fileset>
    </path>   
        
    <target name="init">
        <mkdir dir="${build.dir}"/>
    </target>     
    
    <target name="compile" depends="init">
        <javac srcdir="${src.dir}" destdir="${build.dir}" 
                  includeantruntime="false">
            <classpath refid="compile.classpath"/>
        </javac>
        <echo>Compilation completed</echo>
    </target>
    
    <target name="run" depends="compile">
        <echo>Running the program</echo>
        <java classname="com.zetcode.SimpleHandlerEx" 
                 classpathref="run.classpath"/>
    </target>         
    
    <target name="clean" depends="init">
        <delete dir="${build.dir}"/>
        <echo>Cleaning completed</echo>
    </target>  
    
</project>

构建文件包括一个运行任务,它执行编译的应用程序。

<property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>

要编译和运行我们的示例,我们需要一些 JAR。它们位于lib/Jetty 主页的子目录中。

$ ant run
$ curl localhost:8080
Hello there

当我们向 curl应用程序发送一个简单的 GET 请求时,我们会收到此消息。

定义上下文根

Web 应用程序的上下文根决定了哪些 URL 将被委派给应用程序。JettyContextHandler可以定义应用程序的上下文。

ContextEx.java
package com.zetcode;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;

class MyHandler extends AbstractHandler {

    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request,
            HttpServletResponse response) throws IOException, ServletException {

        response.setContentType("text/plain;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        baseRequest.setHandled(true);
        response.getWriter().println("Hello there");
    }
}

public class ContextEx {

    public static void main(String[] args) throws Exception {

        Server server = new Server(8080);

        ContextHandler con = new ContextHandler();
        con.setContextPath("/path");
        con.setHandler(new MyHandler());
        
        server.setHandler(con);

        server.start();
        server.join();
    }
}

我们应用程序的处理程序将为名为 的上下文根调用/path

ContextHandler context = new ContextHandler();
context.setContextPath("/path");

实例化AContextHandler并设置路径。

context.setHandler(new MyHandler());

处理程序是为上下文处理程序对象设置的。

server.setHandler(context);

我们使用该方法将上下文处理程序对象设置为服务器setHandler 。

$ curl localhost:8080/path/
Hello there

我们将请求发送到定义的上下文根。对于不匹配的上下文,Jetty 返回404错误消息。

简单servlet

在以下示例中,将在向嵌入式 Jetty 发送带有特定 URL 的请求时启动 Java servlet。

$ mkdir simpleservlet
$ cd simpleservlet/
$ mkdir -p src/com/zetcode/
$ touch src/com/zetcode/SimpleServlet.java
$ touch src/com/zetcode/SimpleApp.java
$ touch src/web/WEB-INF/web.xml
$ touch build.xml

我们创建一个项目目录及其结构。我们使用 Ant 构建项目并使用web.xml部署描述符来配置我们的 Web 应用程序。

$ tree
.
├── build.xml
└── src
    ├── com
    │   └── zetcode
    │       ├── SimpleApp.java
    │       └── SimpleServlet.java
    └── web
        └── WEB-INF
            └── web.xml

5 directories, 4 files

此时这些是项目目录的内容。

SimpleApp.java
package com.zetcode;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;

public class SimpleApp {

    public static void main(String[] args) throws Exception {

        String webdir = "src/web/";

        Server server = new Server(8080);
        WebAppContext wcon = new WebAppContext();

        wcon.setContextPath("/simserv");
        wcon.setDescriptor(webdir + "/WEB-INF/web.xml");
        wcon.setResourceBase(webdir);
        
        wcon.setParentLoaderPriority(true);

        server.setHandler(wcon);

        server.start();
        server.join();
    }
}

为了在嵌入式模式下配置我们的 Web 应用程序,我们使用了 WebAppContext该类。此类是 ContextHandler协调 Web 应用程序处理程序的构造和配置的扩展。

wcon.setContextPath("/simserv");

使用该setContextPath方法定义上下文路径。

wcon.setDescriptor(webdir + "/WEB-INF/web.xml");

部署描述符是使用该setDescriptor方法设置的。

wcon.setResourceBase(webdir);

我们为我们的应用程序设置了文档根目录。它是一个目录(或目录或 URL 的集合),其中包含上下文的静态资源。这些可以是图像、HTML 文件或 JSP 文件。

SimpleServlet.java
package com.zetcode;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;

public class SimpleServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        response.setContentType("text/plain");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println("Simple servlet");
    }
}

SimpleServlet将以纯文本消息响应 。

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
         
  <servlet>
    <servlet-name>SimpleServlet</servlet-name>
    <servlet-class>com.zetcode.SimpleServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>SimpleServlet</servlet-name>
    <url-pattern>simple.do</url-pattern>
  </servlet-mapping>
  
</web-app>

web.xml文件中,执行com.zetcode.SimpleServlet 被映射到simple.do字符串。这必须在请求 URL 的末尾指定。

build.xml

<?xml version="1.0" encoding="UTF-8"?>

<project name="SimpleServlet" default="compile">
  
    <property name="name" value="simple"/>
    <property environment="env"/>
    <property name="src.dir" value="src"/>
    <property name="web.dir" value="${src.dir}/web"/>
    <property name="build.dir" location="${web.dir}/WEB-INF/classes"/>
    <property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>
  
    <path id="compile.classpath">
        <fileset dir="${jetty.lib.dir}"> 
            <include name="**/*.jar"/> 
        </fileset>     
    </path>
        
    <path id="run.classpath">
        <pathelement path="${build.dir}" />
        <fileset dir="${jetty.lib.dir}"> 
            <include name="**/*.jar"/> 
        </fileset>
    </path>  
  
    <target name="init">
        <mkdir dir="${build.dir}"/>
    </target>     
  
    <target name="compile" depends="init">
        <javac srcdir="${src.dir}" destdir="${build.dir}" 
               includeantruntime="false">
            <classpath refid="compile.classpath"/>
        </javac>
        <echo>Compilation completed</echo>
    </target>
  
    <target name="clean" depends="init">
        <delete dir="${build.dir}"/>
        <echo>Cleaning completed</echo>
    </target>  
    
    <target name="run" depends="compile">
        <echo>Running the program</echo>
        <java classname="com.zetcode.SimpleApp" 
              classpathref="run.classpath"/>
    </target>      
    
</project>

这是随附的 Ant 构建文件。

$ ant run
$ curl localhost:8080/simserv/simple.do
Simple servlet

我们构建并运行应用程序。发送 GET 消息以 localhost:8080/simserv/simple.do返回“Simple servlet”纯消息。

@WebServlet 注释

@WebServlet注释用于声明一个 servlet 。注释由 servlet 容器在部署时处理。声明的 servlet 在指定的 URL 模式下可用。

$ tree
.
├── build.xml
└── src
    ├── com
    │   └── zetcode
    │       ├── AnnotatedAppEx.java
    │       └── MyServlet.java
    └── web
        └── WEB-INF

5 directories, 3 files

这些是我们项目目录的内容。在此示例中,我们不包含该web.xml文件,因为对于 Servlet 3.1 API,包含此文件是可选的。

AnnotatedAppEx.java
package com.zetcode;

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;

public class AnnotatedAppEx {

    public static void main(String[] args) throws Exception {
    
        String webdir = "src/web/";

        Server server = new Server(8080);
        WebAppContext wcon = new WebAppContext();
        wcon.setResourceBase(webdir);

        wcon.setContextPath("/annotated");

        wcon.setConfigurations(new Configuration[] {
            new AnnotationConfiguration(), new WebXmlConfiguration(),
            new WebInfConfiguration(), new PlusConfiguration(), 
            new MetaInfConfiguration(), new FragmentConfiguration(), 
            new EnvConfiguration() });    

        wcon.setParentLoaderPriority(true);

        server.setHandler(wcon);
        server.start();
        server.join();
    }
}

此类启动启用了注释的嵌入式 Jetty 服务器。

wcon.setConfigurations(new Configuration[] {
    new AnnotationConfiguration(), new WebXmlConfiguration(),
    new WebInfConfiguration(), new PlusConfiguration(), 
    new MetaInfConfiguration(), new FragmentConfiguration(), 
    new EnvConfiguration() }); 

要启用注解,我们需要设置这些配置类。这提供了对多种功能的支持。

MyServlet.java
package com.zetcode;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet(urlPatterns = { "/aserv" })
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        response.setContentType("text/plain");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println("MyServlet called");
    }
}

MyServlet.java文件中,我们提供@WebServlet注释。servlet 在/aservurl 上可用。

build.xml

<?xml version="1.0" encoding="UTF-8"?>

<project name="AnnotatedServlet" default="compile">
  
    <property name="name" value="annotated"/>
    <property environment="env"/>
    <property name="src.dir" value="src"/>
    <property name="web.dir" value="${src.dir}/web"/>
    <property name="build.dir" location="${web.dir}/WEB-INF/classes"/>
    <property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>
  
    <path id="compile.classpath">
        <fileset dir="${jetty.lib.dir}"> 
            <include name="**/*.jar"/> 
        </fileset>     
    </path>
        
    <path id="run.classpath">
        <pathelement path="${build.dir}"/>
        <fileset dir="${jetty.lib.dir}"> 
            <include name="**/*.jar"/> 
        </fileset>
    </path>         
  
    <target name="init">
        <mkdir dir="${build.dir}"/>
    </target>     
  
    <target name="compile" depends="init">
        <javac srcdir="${src.dir}" destdir="${build.dir}" 
               includeantruntime="false">
            <classpath refid="compile.classpath"/>
        </javac>
        <echo>Compilation completed</echo>
    </target>
  
    <target name="clean" depends="init">
        <delete dir="${build.dir}"/>
        <echo>Cleaning completed</echo>
    </target>  
    
    <target name="run" depends="compile">
        <echo>Running the program</echo>
        <java classname="com.zetcode.AnnotatedAppEx" 
              classpathref="run.classpath"/>
    </target>         
    
</project>

这是我们项目的 Ant 构建文件。

$ ant run
$ curl localhost:8080/annotated/aserv
MyServlet called

MyServlet成功启动。

启用 JavaServer 页面

为了在嵌入式 Jetty 模式下启用 JavaServer Pages,我们需要创建一个临时 servlet 目录,设置一个非系统类加载器,添加一个jspservlet,并添加一个default servlet。

在以下示例中,我们创建一个简单的 JavaServer Page 并构建一个 WAR 存档。此存档将设置为以嵌入式模式启动的 Jetty。

$ pwd
/home/janbodnar/prog/jetty/jspexample
$ tree
.
├── build.xml
└── src
    ├── com
    │   └── zetcode
    │       └── JSPExample.java
    └── web
        ├── index.jsp
        └── WEB-INF

5 directories, 3 files

这是项目结构。

index.jsp

<!DOCTYPE html>
<html> 
<body>
<p>
   Today's date: <%= (new java.util.Date()).toLocaleString() %>
</p>
</body> 
</html>

我们的 JSP 将输出当前的本地化日期。

package com.zetcode;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import org.apache.jasper.servlet.JspServlet;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.SimpleInstanceManager;
import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;

public class JSPExample {
    
    private void startServer() throws Exception {
        
        String jetty_base = "/home/janbodnar/prog/jetty/my-base";

        File tmpdir = new File(System.getProperty("java.io.tmpdir"));
        File scdir = new File(tmpdir.toString(), "embedded-jetty-jsp");

        if (!scdir.exists()) {
            if (!scdir.mkdirs()) {
                throw new IOException("Unable to create scratch directory: " + scdir);
            }
        }        

        Server server = new Server(8080);

        WebAppContext wcon = new WebAppContext();
        wcon.setParentLoaderPriority(true);
        wcon.setContextPath("/");
        wcon.setAttribute("javax.servlet.wcon.tempdir", scdir);
        wcon.setAttribute(InstanceManager.class.getName(), 
            new SimpleInstanceManager());
        server.setHandler(wcon);
        
        JettyJasperInitializer sci = new JettyJasperInitializer();
        ServletContainerInitializersStarter sciStarter = 
            new ServletContainerInitializersStarter(wcon);
        ContainerInitializer initializer = new ContainerInitializer(sci, null);
        List<ContainerInitializer> initializers = new ArrayList<>();
        initializers.add(initializer);

        wcon.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
        wcon.addBean(sciStarter, true);
        
        ClassLoader jspClassLoader = new URLClassLoader(new URL[0], 
            this.getClass().getClassLoader());
        wcon.setClassLoader(jspClassLoader);
        
        ServletHolder holderJsp = new ServletHolder("jsp", JspServlet.class);
        holderJsp.setInitOrder(0);
        holderJsp.setInitParameter("fork","false");
        holderJsp.setInitParameter("keepgenerated", "true");
        wcon.addServlet(holderJsp, "*.jsp");
        
        ServletHolder holderDefault = new ServletHolder("default", 
            DefaultServlet.class);
        holderDefault.setInitParameter("dirAllowed", "true");
        wcon.addServlet(holderDefault, "/");        
        
        wcon.setWar(jetty_base + "/webapps/jspexample.war");
        server.setHandler(wcon);

        server.start();
        server.join();        
    }

    public static void main(String[] args) throws Exception {

        JSPExample ex = new JSPExample();
        ex.startServer();
    }
}

我们启用 JSP 支持并设置一个名为jspexample.war Web 应用程序上下文的 WAR 文件。

File tmpdir = new File(System.getProperty("java.io.tmpdir"));
File scdir = new File(tmpdir.toString(), "embedded-jetty-jsp");

if (!scdir.exists()) {
    if (!scdir.mkdirs()) {
        throw new IOException("Unable to create scratch directory: " + scdir);
    }
}        

我们为 servlet 上下文创建一个临时目录。它在 JSP 编译期间使用。

System.setProperty("org.apache.jasper.compiler.disablejsr199","false");

此行强制使用标准 javac。(Eclipse 也使用了一种。)

JettyJasperInitializer sci = new JettyJasperInitializer();
ServletContainerInitializersStarter sciStarter = 
    new ServletContainerInitializersStarter(wcon);
ContainerInitializer initializer = new ContainerInitializer(sci, null);
List<ContainerInitializer> initializers = new ArrayList<>();
initializers.add(initializer);

wcon.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
wcon.addBean(sciStarter, true);

这是JSP 初始化代码。

ClassLoader jspClassLoader = new URLClassLoader(new URL[0], 
    this.getClass().getClassLoader());
wcon.setClassLoader(jspClassLoader);

JSP 需要一个非系统类加载器,这只是简单地包装了嵌入式系统类加载器,使其适合 JSP 使用。

ServletHolder holderJsp = new ServletHolder("jsp", JspServlet.class);
holderJsp.setInitOrder(0);
holderJsp.setInitParameter("fork","false");
holderJsp.setInitParameter("keepgenerated", "true");
wcon.addServlet(holderJsp, "*.jsp");

我们添加一个JspServlet. 它的名字必须是“jsp”。

ServletHolder holderDefault = new ServletHolder("default", 
    DefaultServlet.class);
holderDefault.setInitParameter("dirAllowed", "true");
wcon.addServlet(holderDefault, "/");   

我们添加一个DefaultServlet. 它的名称必须是“默认”。

wcon.setWar(jetty_base + "/webapps/jspexample.war");

WAR 文件设置为 Web 应用程序上下文。

构建.xml
<?xml version="1.0" encoding="UTF-8"?>

<project name="JSPExample" default="compile">
  
    <property name="name" value="jspexample"/>
    <property environment="env"/>
    <property name="src.dir" value="src"/>
    <property name="web.dir" value="${src.dir}/web"/>
    <property name="build.dir" location="${web.dir}/WEB-INF/classes"/>
    <property name="dist.dir" location="dist"/>
    <property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>
    <property name="jetty.base" location="${env.JETTY_BASE}"/>
    <property name="deploy.path" location="${jetty.base}/webapps"/>
    
    <path id="compile.classpath">
        <fileset dir="${jetty.lib.dir}"> 
            <include name="**/*.jar"/> 
        </fileset>     
    </path>
        
    <path id="run.classpath">
        <pathelement path="${build.dir}"/>
        <fileset dir="${jetty.lib.dir}"> 
            <include name="**/*.jar"/> 
        </fileset>
    </path>      
  
    <target name="init">
        <mkdir dir="${build.dir}"/>
        <mkdir dir="${dist.dir}"/>
    </target>     
  
    <target name="compile" depends="init">
        <javac srcdir="${src.dir}" destdir="${build.dir}" 
               includeantruntime="false">
            <classpath refid="compile.classpath"/>
        </javac>
        <echo>Compilation completed</echo>
    </target>
    
    <target name="archive" depends="compile">
        <war destfile="${dist.dir}/${name}.war" needxmlfile="false">
            <fileset dir="${web.dir}"/>
        </war>
        <echo>Archive created</echo>
    </target>     
    
    <target name="clean" depends="init">
        <delete dir="${build.dir}"/>
        <delete dir="${dist.dir}"/>
        <echo>Cleaning completed</echo>
    </target>  
    
    <target name="deploy" depends="archive">
        <copy file="${dist.dir}/${name}.war" todir="${deploy.path}"/>
        <echo>Archive deployed</echo>
    </target>        
    
    <target name="run" depends="deploy">
        <echo>Running the program</echo>
        <java classname="com.zetcode.JSPExample" 
              classpathref="run.classpath"/>
    </target>     
    
</project>

这是我们项目的 Ant 构建。所有必要的 jars 都可以在该JETTY_HOME目录的 lib 子目录中找到。

发出ant run会导致以下安全异常:java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "getClassLoader")。

我们需要在java.policy文件中添加一个配置选项。

$ vi $JAVA_HOME/jre/lib/security/java.policy

我们编辑java.policy文件。

permission java.lang.RuntimePermission "getClassLoader", "read";

我们添加了这个权限。

$ ant run
$ curl localhost:8080/index.jsp
<!DOCTYPE html>
<html> 
<body>
<p>
    Today's date: Sep 16, 2014 1:54:05 PM
</p>
</body> 
</html>

JSP 返回当前日期。

在 Jetty 教程的这一部分中,我们在嵌入式模式下使用了 Jetty。我们已经定义了一个简单的处理程序和 servlet,使用了一个@WebServlet注解,并启用了 JSP 支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值