Servlet 3.0 的新特性

本贴最后更新于 2674 天前,其中的信息可能已经时过境迁

Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布。该版本在前一版本(Servlet 2.5)的基础上提供了若干新特性用于简化 Web 应用的开发和部署。

TOMCAT7 支持 Servlet 3.0

新增的注解支持

Servlet 3.0 的部署描述文件 web.xml 的顶层标签 <web-app> 有一个 metadata-complete 属性,该属性指定当前的部署描述文件是否是完全的。如果设置为 true,则容器在部署时将只依赖部署描述文件,忽略所有的注解(同时也会跳过 web-fragment.xml 的扫描,亦即禁用可插性支持,具体请看后文关于 可插性支持 的讲解);如果不配置该属性,或者将其设置为 false,则表示启用注解支持(和可插性支持)。

 

@WebServlet

@WebServlet 用于将一个类声明为 Servlet,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为 Servlet。该注解具有下表给出的一些常用属性(以下所有属性均为可选属性,但是 vlaue 或者 urlPatterns 通常是必需的,且二者不能共存,如果同时指定,通常是忽略 value 的取值):

表 1. @WebServlet 主要属性列表

属性名  类型  描述  
name  String  指定 Servlet 的 name 属性,等价于 <servlet-name>。如果没有显式指定,则该 Servlet 的取值即为类的全限定名。 
value  String[]  该属性等价于 urlPatterns 属性。两个属性不能同时使用。 
urlPatterns  String[]  指定一组 Servlet 的 URL 匹配模式。等价于 <url-pattern> 标签。 
loadOnStartup  int  指定 Servlet 的加载顺序,等价于 <load-on-startup> 标签。 
initParams  WebInitParam[]  指定一组 Servlet 初始化参数,等价于 <init-param> 标签。 
asyncSupported  boolean  声明 Servlet 是否支持异步操作模式,等价于 <async-supported> 标签。 
description  String  该 Servlet 的描述信息,等价于 <description> 标签。 
displayName  String  该 Servlet 的显示名,通常配合工具使用,等价于 <display-name> 标签。

下面是一个简单的示例:

@WebServlet(urlPatterns = {"/simple"}, asyncSupported = true, 
loadOnStartup = -1, name = "SimpleServlet", displayName = "ss", 
initParams = {@WebInitParam(name = "username", value = "tom")} 
) 
public class SimpleServlet extends HttpServlet{ … } 
 

如此配置之后,就可以不必在 web.xml 中配置相应的 <servlet> 和 <servlet-mapping> 元素了,容器会在部署时根据指定的属性将该类发布为 Servlet。它的等价的 web.xml 配置形式如下:

<servlet>
    <display-name>ss</display-name>
    <servlet-name>SimpleServlet</servlet-name>
    <servlet-class>footmark.servlet.SimpleServlet</servlet-class>
    <load-on-startup>-1</load-on-startup>
    <async-supported>true</async-supported>
    <init-param>
        <param-name>username</param-name>
        <param-value>tom</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>SimpleServlet</servlet-name>
    <url-pattern>/simple</url-pattern>
</servlet-mapping>

 

@WebInitParam

该注解通常不单独使用,而是配合 @WebServlet 或者 @WebFilter 使用。它的作用是为 Servlet 或者过滤器指定初始化参数,这等价于 web.xml 中 <servlet> 和 <filter> 的 <init-param> 子标签。@WebInitParam 具有下表给出的一些常用属性:

表 2. @WebInitParam 的常用属性

属性名  类型  是否可选  描述  
name  String  否 指定参数的名字,等价于 <param-name>。 
value  String  否 指定参数的值,等价于 <param-value>。 
description  String  是 关于参数的描述,等价于 <description>

 

@WebFilter

@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解具有下表给出的一些常用属性 ( 以下所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值 ):

表 3. @WebFilter 的常用属性

属性名  类型  描述  
filterName  String  指定过滤器的 name 属性,等价于 <filter-name>  
value  String[]  该属性等价于 urlPatterns 属性。但是两者不应该同时使用。 
urlPatterns  String[]  指定一组过滤器的 URL 匹配模式。等价于 <url-pattern> 标签。 
servletNames  String[]  指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中的 name 属性的取值,或者是 web.xml 中 <servlet-name> 的取值。 
dispatcherTypes  DispatcherType  指定过滤器的转发模式。具体取值包括:
ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。 
initParams  WebInitParam[]  指定一组过滤器初始化参数,等价于 <init-param> 标签。 
asyncSupported  boolean  声明过滤器是否支持异步操作模式,等价于 <async-supported> 标签。 
description  String  该过滤器的描述信息,等价于 <description> 标签。 
displayName  String  该过滤器的显示名,通常配合工具使用,等价于 <display-name> 标签。

下面是一个简单的示例:

@WebFilter(servletNames = {"SimpleServlet"},filterName="SimpleFilter") 
public class LessThanSixFilter implements Filter{...} 


如此配置之后,就可以不必在 web.xml 中配置相应的 <filter> 和 <filter-mapping> 元素了,容器会在部署时根据指定的属性将该类发布为过滤器。它等价的 web.xml 中的配置形式为:

<filter> 
    <filter-name>SimpleFilter</filter-name> 
    <filter-class>xxx</filter-class> 
</filter> 
<filter-mapping> 
    <filter-name>SimpleFilter</filter-name> 
    <servlet-name>SimpleServlet</servlet-name> 
</filter-mapping>

 
 

@WebListener

该注解用于将类声明为监听器,被 @WebListener 标注的类必须实现以下至少一个接口:

ServletContextListener 
ServletContextAttributeListener 
ServletRequestListener 
ServletRequestAttributeListener 
HttpSessionListener 
HttpSessionAttributeListener 
该注解使用非常简单,其属性如下:

表 4. @WebListener 的常用属性

属性名  类型  是否可选  描述  
value  String  是 该监听器的描述信息。

一个简单示例如下:

@WebListener("This is only a demo listener") 
public class SimpleListener implements ServletContextListener{...} 

如此,则不需要在 web.xml 中配置 <listener> 标签了。它等价的 web.xml 中的配置形式如下:

<listener> 
    <listener-class>footmark.servlet.SimpleListener</listener-class> 
</listener> 


 

@MultipartConfig

该注解主要是为了辅助 Servlet 3.0 中 HttpServletRequest 提供的对上传文件的支持。该注解标注在 Servlet 上面,以表示该 Servlet 希望处理的请求的 MIME 类型是 multipart/form-data。另外,它还提供了若干属性用于简化对上传文件的处理。具体如下:

表 5. @MultipartConfig 的常用属性

属性名 类型 是否可选 描述 
fileSizeThreshold  int  是 当数据量大于该值时,内容将被写入文件。 
location  String  是 存放生成的文件地址。 
maxFileSize  long  是 允许上传的文件最大值。默认值为 -1,表示没有限制。 
maxRequestSize  long  是 针对该 multipart/form-data 请求的最大数量,默认值为 -1,表示没有限制。

 

异步处理支持

配置方式如下所示:

<servlet> 
    <servlet-name>DemoServlet</servlet-name> 
    <servlet-class>footmark.servlet.Demo Servlet</servlet-class> 
    <async-supported>true</async-supported> 
</servlet> 

 

Servlet 3.0 提供的 @WebServlet 和 @WebFilter 进行 Servlet 或过滤器配置的情况,这两个注解都提供了 asyncSupported 属性,默认该属性的取值为 false,要启用异步处理支持,只需将该属性设置为 true 即可。以 @WebFilter 为例,其配置方式如下所示: 

@WebFilter(urlPatterns = "/demo",asyncSupported = true
) 
public class DemoFilter implements Filter{...} 
 

一个简单的模拟异步处理的 Servlet 示例如下:

@WebServlet(urlPatterns = "/demo", asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws IOException, ServletException {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("进入Servlet的时间:" + new Date() + ".");
        out.flush();
    //在子线程中执行业务调用,并由其负责输出响应,主线程退出
    AsyncContext ctx = req.startAsync();
    new Thread(new Executor(ctx)).start();

    out.println("结束Servlet的时间:" + new Date() + ".");
    out.flush();
}

}

public class Executor implements Runnable {
private AsyncContext ctx = null;
public Executor(AsyncContext ctx){
this.ctx = ctx;
}

public void run(){
    try {
        //等待十秒钟,以模拟业务方法的执行
        Thread.sleep(10000);
        PrintWriter out = ctx.getResponse().getWriter();
        out.println("业务处理完毕的时间:" + new Date() + ".");
        out.flush();
        ctx.complete();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}

 

Servlet 3.0 还为异步处理提供了一个监听器,使用 AsyncListener 接口表示。它可以监控如下四种事件:

异步线程开始时,调用 AsyncListener 的 onStartAsync(AsyncEvent event) 方法; 
异步线程出错时,调用 AsyncListener 的 onError(AsyncEvent event) 方法; 
异步线程执行超时,则调用 AsyncListener 的 onTimeout(AsyncEvent event) 方法; 
异步执行完毕时,调用 AsyncListener 的 onComplete(AsyncEvent event) 方法; 
要注册一个 AsyncListener,只需将准备好的 AsyncListener 对象传递给 AsyncContext 对象的 addListener() 方法即可,如下所示:

AsyncContext ctx = req.startAsync(); 
ctx.addListener(new AsyncListener() { 
    public void onComplete(AsyncEvent asyncEvent) throws IOException { 
        // 做一些清理工作或者其他
    } 
    ... 
}); 

 

可插性支持

使用该特性,现在我们可以在不修改已有 Web 应用的前提下,只需将按照一定格式打成的 JAR 包放到 WEB-INF/lib 目录下,即可实现新功能的扩充,不需要额外的配置。

Servlet 3.0 引入了称之为“Web 模块部署描述符片段”的 web-fragment.xml 部署描述文件,该文件必须存放在 JAR 文件的 META-INF 目录下,该部署描述文件可以包含一切可以在 web.xml 中定义的内容。JAR 包通常放在 WEB-INF/lib 目录下,除此之外,所有该模块使用的资源,包括 class 文件、配置文件等,只需要能够被容器的类加载器链加载的路径上,比如 classes 目录等。

现在,为一个 Web 应用增加一个 Servlet 配置有如下三种方式 ( 过滤器、监听器与 Servlet 三者的配置都是等价的,故在此以 Servlet 配置为例进行讲述,过滤器和监听器具有与之非常类似的特性 ):

编写一个类继承自 HttpServlet,将该类放在 classes 目录下的对应包结构中,修改 web.xml,在其中增加一个 Servlet 声明。这是最原始的方式; 
编写一个类继承自 HttpServlet,并且在该类上使用 @WebServlet 注解将该类声明为 Servlet,将该类放在 classes 目录下的对应包结构中,无需修改 web.xml 文件。 
编写一个类继承自 HttpServlet,将该类打成 JAR 包,并且在 JAR 包的 META-INF 目录下放置一个 web-fragment.xml 文件,该文件中声明了相应的 Servlet 配置。web-fragment.xml 文件示例如下: 

<?xml version="1.0" encoding="UTF-8"?>
<web-fragment 
    xmlns=http://java.sun.com/xml/ns/javaee
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd"
    metadata-complete="true">
    <servlet>
        <servlet-name>fragment</servlet-name>
        <servlet-class>footmark.servlet.FragmentServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>fragment</servlet-name>
        <url-pattern>/fragment</url-pattern>
    </servlet-mapping>
</web-fragment>


 

从上面的示例可以看出,web-fragment.xml 与 web.xml 除了在头部声明的 XSD 引用不同之外,其主体配置与 web.xml 是完全一致的。

由于一个 Web 应用中可以出现多个 web-fragment.xml 声明文件,加上一个 web.xml 文件,加载顺序问题便成了不得不面对的问题。Servlet 规范的专家组在设计的时候已经考虑到了这个问题,并定义了加载顺序的规则。

web-fragment.xml 包含了两个可选的顶层标签,<name> 和 <ordering>,如果希望为当前的文件指定明确的加载顺序,通常需要使用这两个标签,<name> 主要用于标识当前的文件,而 <ordering> 则用于指定先后顺序。一个简单的示例如下:

<web-fragment...>
    <name>FragmentA</name>
    <ordering>
        <after>
            <name>FragmentB</name>
            <name>FragmentC</name>
        </after>
    <before>
        <others/>
    </before>
    </ordering>
    ...
</web-fragment>


 

如上所示, <name> 标签的取值通常是被其它 web-fragment.xml 文件在定义先后顺序时引用的,在当前文件中一般用不着,它起着标识当前文件的作用。

在 <ordering> 标签内部,我们可以定义当前 web-fragment.xml 文件与其他文件的相对位置关系,这主要通过 <ordering> 的 <after> 和 <before> 子标签来实现的。在这两个子标签内部可以通过 <name> 标签来指定相对应的文件。比如:

<after> 
    <name>FragmentB</name> 
    <name>FragmentC</name> 
</after> 


 

以上片段则表示当前文件必须在 FragmentB 和 FragmentC 之后解析。<before> 的使用于此相同,它所表示的是当前文件必须早于 <before> 标签里所列出的 web-fragment.xml 文件。

除了将所比较的文件通过 <name> 在 <after> 和 <begin> 中列出之外,Servlet 还提供了一个简化的标签 <others/>。它表示除了当前文件之外的其他所有的 web-fragment.xml 文件。该标签的优先级要低于使用 <name> 明确指定的相对位置关系。

 

ServletContext 的性能增强

除了以上的新特性之外,ServletContext 对象的功能在新版本中也得到了增强。现在,该对象支持在运行时动态部署 Servlet、过滤器、监听器,以及为 Servlet 和过滤器增加 URL 映射等。以 Servlet 为例,过滤器与监听器与之类似。ServletContext 为动态配置 Servlet 增加了如下方法:

ServletRegistration.Dynamic addServlet(String servletName,Class<? extends Servlet> servletClass) 
ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) 
ServletRegistration.Dynamic addServlet(String servletName, String className) 
<T extends Servlet> T createServlet(Class<T> clazz) 
ServletRegistration getServletRegistration(String servletName) 
Map<String,? extends ServletRegistration> getServletRegistrations() 


其中前三个方法的作用是相同的,只是参数类型不同而已;通过 createServlet() 方法创建的 Servlet,通常需要做一些自定义的配置,然后使用 addServlet() 方法来将其动态注册为一个可以用于服务的 Servlet。两个 getServletRegistration() 方法主要用于动态为 Servlet 增加映射信息,这等价于在 web.xml( 抑或 web-fragment.xml) 中使用 <servlet-mapping> 标签为存在的 Servlet 增加映射信息。

以上 ServletContext 新增的方法要么是在 ServletContextListener 的 contexInitialized 方法中调用,要么是在 ServletContainerInitializer 的 onStartup() 方法中调用。

ServletContainerInitializer 也是 Servlet 3.0 新增的一个接口,容器在启动时使用 JAR 服务 API(JAR Service API) 来发现 ServletContainerInitializer 的实现类,并且容器将 WEB-INF/lib 目录下 JAR 包中的类都交给该类的 onStartup() 方法处理,我们通常需要在该实现类上使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup() 处理的类。



HttpServletRequest 对文件上传的支持

此前,对于处理上传文件的操作一直是让开发者头疼的问题,因为 Servlet 本身没有对此提供直接的支持,需要使用第三方框架来实现,而且使用起来也不够简单。如今这都成为了历史,Servlet 3.0 已经提供了这个功能,而且使用也非常简单。为此,HttpServletRequest 提供了两个方法用于从请求中解析出上传的文件:

Part getPart(String name) 
Collection<Part> getParts() 


前者用于获取请求中给定 name 的文件,后者用于获取所有的文件。每一个文件用一个 javax.servlet.http.Part 对象来表示。该接口提供了处理文件的简易方法,比如 write()、delete() 等。至此,结合 HttpServletRequest 和 Part 来保存上传的文件变得非常简单,如下所示:

Part photo = request.getPart("photo"); 
photo.write("/tmp/photo.jpg"); 
// 可以将两行代码简化为 request.getPart("photo").write("/tmp/photo.jpg") 一行。


 

另外,开发者可以配合前面提到的 @MultipartConfig 注解来对上传操作进行一些自定义的配置,比如限制上传文件的大小,以及保存文件的路径等。其用法非常简单,故不在此赘述了。

需要注意的是,如果请求的 MIME 类型不是 multipart/form-data,则不能使用上面的两个方法,否则将抛异常。



总结

Servlet 3.0 的众多新特性使得 Servlet 开发变得更加简单,尤其是异步处理特性和可插性支持的出现,必将对现有的 MVC 框架产生深远影响。虽然我们通常不会自己去用 Servlet 编写控制层代码,但是也许在下一个版本的 Struts 中,您就能切实感受到这些新特性带来的实质性改变。

 

【原文地址】

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1083 引用 • 3461 回帖 • 287 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    169 引用 • 799 回帖
  • CodeMirror
    1 引用 • 2 回帖 • 116 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 42 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 693 关注
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    83 引用 • 165 回帖 • 48 关注
  • 大疆创新

    深圳市大疆创新科技有限公司(DJI-Innovations,简称 DJI),成立于 2006 年,是全球领先的无人飞行器控制系统及无人机解决方案的研发和生产商,客户遍布全球 100 多个国家。通过持续的创新,大疆致力于为无人机工业、行业用户以及专业航拍应用提供性能最强、体验最佳的革命性智能飞控产品和解决方案。

    2 引用 • 14 回帖
  • 开源中国

    开源中国是目前中国最大的开源技术社区。传播开源的理念,推广开源项目,为 IT 开发者提供了一个发现、使用、并交流开源技术的平台。目前开源中国社区已收录超过两万款开源软件。

    7 引用 • 86 回帖
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖 • 3 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖 • 1 关注
  • Mac

    Mac 是苹果公司自 1984 年起以“Macintosh”开始开发的个人消费型计算机,如:iMac、Mac mini、Macbook Air、Macbook Pro、Macbook、Mac Pro 等计算机。

    164 引用 • 594 回帖 • 1 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    10 引用 • 54 回帖 • 131 关注
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 149 关注
  • 新人

    让我们欢迎这对新人。哦,不好意思说错了,让我们欢迎这位新人!
    新手上路,请谨慎驾驶!

    51 引用 • 226 回帖
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    491 引用 • 1383 回帖 • 372 关注
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    710 引用 • 1173 回帖 • 177 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 17 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 12 关注
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖 • 1 关注
  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 422 关注
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    19 引用 • 31 回帖 • 3 关注
  • 反馈

    Communication channel for makers and users.

    123 引用 • 906 回帖 • 191 关注
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    123 引用 • 168 回帖 • 1 关注
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 372 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 455 关注
  • 招聘

    哪里都缺人,哪里都不缺人。

    189 引用 • 1056 回帖
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 43 关注