我的编程空间,编程开发者的网络收藏夹
学习永远不晚

SpringBoot异步处理的方法有哪些

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

SpringBoot异步处理的方法有哪些

小编给大家分享一下SpringBoot异步处理的方法有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

异步请求与同步请求

我们先通过一张图来区分一下异步请求和同步请求的区别:

SpringBoot异步处理的方法有哪些

在上图中有三个角色:客户端、Web容器和业务处理线程。

两个流程中客户端对Web容器的请求,都是同步的。因为它们在请求客户端时都处于阻塞等待状态,并没有进行异步处理。

在Web容器部分,第一个流程采用同步请求,第二个流程采用异步回调的形式。

通过异步处理,可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加了服务器对客户端请求的吞吐量。但并发请求量较大时,通常会通过负载均衡的方案来解决,而不是异步。

Servlet3.0中的异步

Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由一个线程从头到尾处理。当涉及到耗时操作时,性能问题便比较明显。

Servlet 3.0中提供了异步处理请求。可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加服务的吞吐量。

Servlet 3.0的异步是通过AsyncContext对象来完成的,它可以从当前线程传给另一个线程,并归还初始线程。新的线程处理完业务可以直接返回结果给客户端。

AsyncContext对象可以从HttpServletRequest中获取:

@RequestMapping("/async")public void async(HttpServletRequest request) {    AsyncContext asyncContext = request.getAsyncContext();}

在AsyncContext中提供了获取ServletRequest、ServletResponse和添加监听(addListener)等功能:

public interface AsyncContext {    ServletRequest getRequest();    ServletResponse getResponse();    void addListener(AsyncListener var1);    void setTimeout(long var1);    // 省略其他方法}

不仅可以通过AsyncContext获取Request和Response等信息,还可以设置异步处理超时时间。通常,超时时间(单位毫秒)是需要设置的,不然无限等下去不就与同步处理一样了。

通过AsyncContext的addListener还可以添加监听事件,用来处理异步线程的开始、完成、异常、超时等事件回调。

addListener方法的参数AsyncListener的源码如下:

public interface AsyncListener extends EventListener {    // 异步执行完毕时调用    void onComplete(AsyncEvent var1) throws IOException;    // 异步线程执行超时调用    void onTimeout(AsyncEvent var1) throws IOException;    // 异步线程出错时调用    void onError(AsyncEvent var1) throws IOException;    // 异步线程开始时调用    void onStartAsync(AsyncEvent var1) throws IOException;}

通常,异常或超时时返回调用方错误信息,而异常时会处理一些清理和关闭操作或记录异常日志等。

基于Servlet方式实现异步请求

下面直接看一个基于Servlet方式的异步请求示例:

@GetMapping(value = "/email/send")public void servletReq(HttpServletRequest request) {    AsyncContext asyncContext = request.startAsync();    // 设置监听器:可设置其开始、完成、异常、超时等事件的回调处理    asyncContext.addListener(new AsyncListener() {        @Override        public void onTimeout(AsyncEvent event) {            System.out.println("处理超时了...");        }        @Override        public void onStartAsync(AsyncEvent event) {            System.out.println("线程开始执行");        }        @Override        public void onError(AsyncEvent event) {            System.out.println("执行过程中发生错误:" + event.getThrowable().getMessage());        }        @Override        public void onComplete(AsyncEvent event) {            System.out.println("执行完成,释放资源");        }    });    //设置超时时间    asyncContext.setTimeout(6000);    asyncContext.start(new Runnable() {        @Override        public void run() {            try {                Thread.sleep(5000);                System.out.println("内部线程:" + Thread.currentThread().getName());                asyncContext.getResponse().getWriter().println("async processing");            } catch (Exception e) {                System.out.println("异步处理发生异常:" + e.getMessage());            }            // 异步请求完成通知,整个请求完成            asyncContext.complete();        }    });    //此时request的线程连接已经释放了    System.out.println("主线程:" + Thread.currentThread().getName());}

启动项目,访问对应的URL,打印日志如下:

主线程:http-nio-8080-exec-4
内部线程:http-nio-8080-exec-5
执行完成,释放资源

可以看出,上述代码先执行完了主线程,也就是程序的最后一行代码的日志打印,然后才是内部线程的执行。内部线程执行完成,AsyncContext的onComplete方法被调用。

如果通过浏览器访问对应的URL,还可以看到该方法的返回值“async processing”。说明内部线程的结果同样正常的返回到客户端了。

基于Spring实现异步请求

基于Spring可以通过Callable、DeferredResult或者WebAsyncTask等方式实现异步请求。

基于Callable实现

对于一次请求(/email),基于Callable的处理流程如下:

Spring MVC开启副线程处理业务(将Callable提交到TaskExecutor);

DispatcherServlet和所有的Filter退出Web容器的线程,但是response保持打开状态;

Callable返回结果,SpringMVC将原始请求重新派发给容器(再重新请求一次/email),恢复之前的处理;

DispatcherServlet重新被调用,将结果返回给用户;

代码实现示例如下:

@GetMapping("/email")public Callable<String> order() {    System.out.println("主线程开始:" + Thread.currentThread().getName());    Callable<String> result = () -> {        System.out.println("副线程开始:" + Thread.currentThread().getName());        Thread.sleep(1000);        System.out.println("副线程返回:" + Thread.currentThread().getName());        return "success";    };    System.out.println("主线程返回:" + Thread.currentThread().getName());    return result;}

访问对应URL,控制台输入日志如下:

主线程开始:http-nio-8080-exec-1
主线程返回:http-nio-8080-exec-1
副线程开始:task-1
副线程返回:task-1

通过日志可以看出,主线程已经完成了,副线程才进行执行。同时,URL返回结果“success”。这也说明一个问题,服务器端的异步处理对客户端来说是不可见的。

Callable默认使用SimpleAsyncTaskExecutor类来执行,这个类非常简单而且没有重用线程。在实践中,需要使用AsyncTaskExecutor类来对线程进行配置。

这里通过实现WebMvcConfigurer接口来完成线程池的配置。

@Configurationpublic class WebConfig implements WebMvcConfigurer {    @Resource    private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;        @Bean(name = "asyncPoolTaskExecutor")    public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();        taskExecutor.setCorePoolSize(2);        taskExecutor.setMaxPoolSize(10);        taskExecutor.setQueueCapacity(25);        taskExecutor.setKeepAliveSeconds(200);        taskExecutor.setThreadNamePrefix("thread-pool-");        // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());        taskExecutor.initialize();        return taskExecutor;    }    @Override    public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {        // 处理callable超时        configurer.setDefaultTimeout(60 * 1000);        configurer.setTaskExecutor(myThreadPoolTaskExecutor);        configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());    }    @Bean    public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {        return new TimeoutCallableProcessingInterceptor();    }}

为了验证打印的线程,我们将实例代码中的System.out.println替换成日志输出,会发现在使用线程池之前,打印日志如下:

2021-02-21 09:45:37.144  INFO 8312 --- [nio-8080-exec-1] c.s.learn.controller.AsynController      : 主线程开始:http-nio-8080-exec-1
2021-02-21 09:45:37.144  INFO 8312 --- [nio-8080-exec-1] c.s.learn.controller.AsynController      : 主线程返回:http-nio-8080-exec-1
2021-02-21 09:45:37.148  INFO 8312 --- [         task-1] c.s.learn.controller.AsynController      : 副线程开始:task-1
2021-02-21 09:45:38.153  INFO 8312 --- [         task-1] c.s.learn.controller.AsynController      : 副线程返回:task-1

线程名称为“task-1”。让线程池生效之后,打印日志如下:

2021-02-21 09:50:28.950  INFO 8339 --- [nio-8080-exec-1] c.s.learn.controller.AsynController      : 主线程开始:http-nio-8080-exec-1
2021-02-21 09:50:28.951  INFO 8339 --- [nio-8080-exec-1] c.s.learn.controller.AsynController      : 主线程返回:http-nio-8080-exec-1
2021-02-21 09:50:28.955  INFO 8339 --- [  thread-pool-1] c.s.learn.controller.AsynController      : 副线程开始:thread-pool-1
2021-02-21 09:50:29.956  INFO 8339 --- [  thread-pool-1] c.s.learn.controller.AsynController      : 副线程返回:thread-pool-1

线程名称为“thread-pool-1”,其中前面的“thread-pool”正是我们配置的线程池前缀。

除了线程池的配置,还可以配置统一异常处理,这里就不再演示了。

基于WebAsyncTask实现

Spring提供的WebAsyncTask是对Callable的包装,提供了更强大的功能,比如:处理超时回调、错误回调、完成回调等。

@GetMapping("/webAsyncTask")public WebAsyncTask<String> webAsyncTask() {    log.info("外部线程:" + Thread.currentThread().getName());    WebAsyncTask<String> result = new WebAsyncTask<>(60 * 1000L, new Callable<String>() {        @Override        public String call() {            log.info("内部线程:" + Thread.currentThread().getName());            return "success";        }    });    result.onTimeout(new Callable<String>() {        @Override        public String call() {            log.info("timeout callback");            return "timeout callback";        }    });    result.onCompletion(new Runnable() {        @Override        public void run() {            log.info("finish callback");        }    });    return result;}

访问对应请求,打印日志:

2021-02-21 10:22:33.028  INFO 8547 --- [nio-8080-exec-1] c.s.learn.controller.AsynController      : 外部线程:http-nio-8080-exec-1
2021-02-21 10:22:33.033  INFO 8547 --- [  thread-pool-1] c.s.learn.controller.AsynController      : 内部线程:thread-pool-1
2021-02-21 10:22:33.055  INFO 8547 --- [nio-8080-exec-2] c.s.learn.controller.AsynController      : finish callback

基于DeferredResult实现

DeferredResult使用方式与Callable类似,但在返回结果时不一样,它返回的时实际结果可能没有生成,实际的结果可能会在另外的线程里面设置到DeferredResult中去。

DeferredResult的这个特性对实现服务端推技术、订单过期时间处理、长轮询、模拟MQ的功能等高级应用非常重要。

关于DeferredResult的使用先来看一下官方的例子和说明:

@RequestMapping("/quotes")@ResponseBodypublic DeferredResult<String> quotes() {  DeferredResult<String> deferredResult = new DeferredResult<String>();  // Save the deferredResult in in-memory queue ...  return deferredResult;}// In some other thread...deferredResult.setResult(data);

上述示例中我们可以发现DeferredResult的调用并不一定在Spring MVC当中,它可以是别的线程。官方的解释也是如此:

In this case the return value will also be produced from a separate thread. However, that thread is not known to Spring MVC. For example the result may be produced in response to some external event such as a JMS message, a scheduled task, etc.

也就是说,DeferredResult返回的结果也可能是由MQ、定时任务或其他线程触发。来个实例:

@Controller@RequestMapping("/async/controller")public class AsyncHelloController {    private List<DeferredResult<String>> deferredResultList = new ArrayList<>();    @ResponseBody    @GetMapping("/hello")    public DeferredResult<String> helloGet() throws Exception {        DeferredResult<String> deferredResult = new DeferredResult<>();        //先存起来,等待触发        deferredResultList.add(deferredResult);        return deferredResult;    }    @ResponseBody    @GetMapping("/setHelloToAll")    public void helloSet() throws Exception {        // 让所有hold住的请求给与响应        deferredResultList.forEach(d -> d.setResult("say hello to all"));    }}

第一个请求/hello,会先将deferredResult存起来,前端页面是一直等待(转圈)状态。直到发第二个请求:setHelloToAll,所有的相关页面才会有响应。

整个执行流程如下:

  • controller返回一个DeferredResult,把它保存到内存里或者List里面(供后续访问);

  • Spring MVC调用request.startAsync(),开启异步处理;与此同时将DispatcherServlet里的拦截器、Filter等等都马上退出主线程,但是response仍然保持打开的状态;

  • 应用通过另外一个线程(可能是MQ消息、定时任务等)给DeferredResult#setResult值。然后SpringMVC会把这个请求再次派发给servlet容器;

  • DispatcherServlet再次被调用,然后处理后续的标准流程;

通过上述流程可以发现:利用DeferredResult可实现一些长连接的功能,比如当某个操作是异步时,可以先保存对应的DeferredResult对象,当异步通知回来时,再找到这个DeferredResult对象,在setResult处理结果即可。从而提高性能。

SpringBoot中的异步实现

在SpringBoot中将一个方法声明为异步方法非常简单,只需两个注解即可@EnableAsync和@Async。其中@EnableAsync用于开启SpringBoot支持异步的功能,用在SpringBoot的启动类上。@Async用于方法上,标记该方法为异步处理方法。

需要注意的是@Async并不支持用于被@Configuration注解的类的方法上。同一个类中,一个方法调用另外一个有@Async的方法,注解也是不会生效的。

@EnableAsync的使用示例:

@SpringBootApplication@EnableAsyncpublic class App {    public static void main(String[] args) {        SpringApplication.run(App.class, args);    }}

@Async的使用示例:

@Servicepublic class SyncService {    @Async    public void asyncEvent() {        // 业务处理    }}

@Async注解的使用与Callable有类似之处,在默认情况下使用的都是SimpleAsyncTaskExecutor线程池,可参考Callable中的方式来自定义线程池。

下面通过一个实例来验证一下,启动类上使用@EnableAsync,然后定义Controller类:

@RestControllerpublic class IndexController {    @Resource    private UserService userService;    @RequestMapping("/async")    public String async(){        System.out.println("--IndexController--1");        userService.sendSms();        System.out.println("--IndexController--4");        return "success";    }}

定义Service及异步方法:

@Servicepublic class UserService {    @Async    public void sendSms(){        System.out.println("--sendSms--2");        IntStream.range(0, 5).forEach(d -> {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        });        System.out.println("--sendSms--3");    }}

如果先注释掉@EnableAsync和@Async注解,即正常情况下的业务请求,打印日志为:

--IndexController--1
--sendSms--2
--sendSms--3
--IndexController--4

使用@EnableAsync和@Async注解时,打印日志如下:

--IndexController--1
--IndexController--4
--sendSms--2
--sendSms--3

通过日志的对比我们可以看出,使用了@Async的方法,会被当成一个子线程。所以,整个sendSms方法会在主线程执行完了之后执行。

这样的效果是不是跟我们上面使用的其他形式的异步异曲同工?所以在文章最开始已经说到,网络上所谓的“异步调用与异步请求的区别”是并不存储在的,本质上都是一回事,只不过实现形式不同而已。这里所提到异步方法,也就是将方法进行异步处理而已。

@Async、WebAsyncTask、Callable、DeferredResult的区别

所在的包不同:

  • @Async:org.springframework.scheduling.annotation;

  • WebAsyncTask:org.springframework.web.context.request.async;

  • Callable:java.util.concurrent;

  • DeferredResult:org.springframework.web.context.request.async;

通过所在的包,我们应该隐隐约约感到一些区别,比如@Async是位于scheduling包中,而WebAsyncTask和DeferredResult是用于Web(Spring MVC)的,而Callable是用于concurrent(并发)处理的。

对于Callable,通常用于Controller方法的异步请求,当然也可以用于替换Runable的方式。在方法的返回上与正常的方法有所区别:

// 普通方法public String aMethod(){}// 对照Callable方法public Callable<String>  aMethod(){}

而WebAsyncTask是对Callable的封装,提供了一些事件回调的处理,本质上区别不大。

DeferredResult使用方式与Callable类似,重点在于跨线程之间的通信。

@Async也是替换Runable的一种方式,可以代替我们自己创建线程。而且适用的范围更广,并不局限于Controller层,而可以是任何层的方法上。

当然,大家也可以从返回结果,异常处理等角度来分析一下,这里就不再展开了。

springboot是什么

springboot一种全新的编程规范,其设计目的是用来简化新Spring应用的初始搭建以及开发过程,SpringBoot也是一个服务于框架的框架,服务范围是简化配置文件。

以上是“SpringBoot异步处理的方法有哪些”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网行业资讯频道!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

SpringBoot异步处理的方法有哪些

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

SpringBoot异步处理的方法有哪些

小编给大家分享一下SpringBoot异步处理的方法有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!异步请求与同步请求我们先通过一张图来区分一下异步请求和同步
2023-06-15

SpringMVC异步处理的方法有哪些

这篇文章主要介绍SpringMVC异步处理的方法有哪些,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!异步处理 demo如果要启用异步返回,需要开启 @EnableAsync。如下的代码中,使用 DeferredRes
2023-06-06

ADO.NET数据异步处理的方法有哪些

这篇文章主要介绍“ADO.NET数据异步处理的方法有哪些”,在日常操作中,相信很多人在ADO.NET数据异步处理的方法有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”ADO.NET数据异步处理的方法有哪些
2023-06-17

c++异常处理的方法有哪些

在C++中,有以下几种异常处理的方法:1. try-catch语句块:使用try-catch语句块可以捕获和处理异常。在try块中编写可能会引发异常的代码,然后在catch块中捕获并处理这些异常。可以使用多个catch块来处理不同类型的异常
2023-10-10

java异常处理的方法有哪些

Java中常见的异常处理方法有以下几种:1. try-catch语句:使用try块包裹可能抛出异常的代码,然后使用catch块捕获并处理异常。catch块可以根据不同的异常类型进行不同的处理。```javatry {// 可能抛出异常的代码
2023-09-27

golang异常处理的方法有哪些

在Go语言中,异常处理的方法有以下几种:1. 使用`if`语句进行错误检查:在函数调用后,使用`if`语句来检查返回值是否为错误。例如:```goresult, err := someFunction()if err != nil {//
2023-09-27

spring处理异常的方法有哪些

Spring提供了多种处理异常的方法,包括:1. 使用@ControllerAdvice注解的类:可以全局捕获和处理Controller层抛出的异常,可以在该类中定义多个@ExceptionHandler方法来处理不同类型的异常。2. 使用
2023-08-18

redis异常处理方法有哪些

在Redis中,可以通过以下几种方式处理异常:1. 异常捕获:在Redis操作过程中,可以使用try-catch语句来捕获异常,并在catch块中处理异常情况。2. 返回错误信息:Redis的操作会返回相应的错误信息,可以通过检查返回结果来
2023-09-16

java超时异常处理的方法有哪些

Java中超时异常处理的方法有以下几种:使用线程的interrupt()方法:在执行任务的线程中设置一个定时器,当任务执行时间超过预设的时间时,调用线程的interrupt()方法中断任务的执行。任务中需要通过判断线程的中断状态来提前结束任
2023-10-26

Java中异步转同步的方法有哪些

今天小编给大家分享一下Java中异步转同步的方法有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。一、问题应用场景应用中通
2023-07-02

java实现异步的方法有哪些

Java实现异步的方法有以下几种:1. 使用线程池:可以通过ExecutorService接口创建线程池,然后使用submit()或execute()方法提交任务进行异步执行。2. 使用CompletableFuture:Completab
2023-08-09

Java异常的处理方式有哪些

这篇文章主要介绍“Java异常的处理方式有哪些”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java异常的处理方式有哪些”文章能帮助大家解决问题。什么是异常?异常在我们写代码是特别常见,因为程序员大
2023-06-30

C#处理异常的方式有哪些

本篇内容主要讲解“C#处理异常的方式有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#处理异常的方式有哪些”吧!关于异常,从我们一开始写代码的时候就开始伴随着我们,只不过那时还没入门,脑子
2023-06-22

Java异常处理Throwable实现方法有哪些

Java异常处理Throwable实现方法有以下几种方法:1. try-catch语句:使用try-catch语句可以捕获并处理异常。在try块中,将可能抛出异常的代码放在其中,当发生异常时,会跳转到catch块,catch块中可以对异常进
2023-08-15

JavaScript中的异步处理方法

本篇内容介绍了“JavaScript中的异步处理方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在 JavaScript 的世界中,所有代
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录