非同期リクエストと非同期呼び出しの違い

10278 ワード

非同期リクエストと非同期呼び出しの違い
両者の使用シーンは異なり、非同期リクエストは、同時リクエストがサーバに与える圧力を解決し、リクエストのスループットを向上させるために使用される.非同期呼び出しは、kafkaにログを同期してログ分析を行うなど、非プライマリ・ライン・プロセスを行い、リアルタイムの計算と応答を必要としないタスクです.
非同期リクエストはresponseに対応するものをずっと待っています.結果をクライアントに返す必要があります.非同期呼び出しはすぐにクライアントに応答し、今回の要求全体を完了します.非同期呼び出しのタスクバックグラウンドは自分でゆっくり走ればいいので、クライアントは関心を持っていません.
 
 
1、非同期要求の実現
方式一:サーブレット方式による非同期要求の実現
  @RequestMapping(value = "/email/servletReq", method = GET)
  public void servletReq (HttpServletRequest request, HttpServletResponse response) {
      AsyncContext asyncContext = request.startAsync();
      //     :      、  、  、          
      asyncContext.addListener(new AsyncListener() {
          @Override
          public void onTimeout(AsyncEvent event) throws IOException {
              System.out.println("   ...");
              //           ...
          }
          @Override
          public void onStartAsync(AsyncEvent event) throws IOException {
              System.out.println("    ");
          }
          @Override
          public void onError(AsyncEvent event) throws IOException {
              System.out.println("    :"+event.getThrowable());
          }
          @Override
          public void onComplete(AsyncEvent event) throws IOException {
              System.out.println("    ");
              //              ...
          }
      });
      //      
      asyncContext.setTimeout(20000);
      asyncContext.start(new Runnable() {
          @Override
          public void run() {
              try {
                  Thread.sleep(10000);
                  System.out.println("    :" + Thread.currentThread().getName());
                  asyncContext.getResponse().setCharacterEncoding("utf-8");
                  asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                  asyncContext.getResponse().getWriter().println("         ");
              } catch (Exception e) {
                  System.out.println("  :"+e);
              }
              //        
              //         
              asyncContext.complete();
          }
      });
      //     request          
      System.out.println("   :" + Thread.currentThread().getName());
  }

方法2:簡単で、直接戻ってきたパラメータを使ってcallableを包むことで、WebMvcConfigurerAdapterクラスを継承してデフォルトのスレッドプールとタイムアウト処理を設定することができます.
  @RequestMapping(value = "/email/callableReq", method = GET)
  @ResponseBody
  public Callable callableReq () {
      System.out.println("    :" + Thread.currentThread().getName());

      return new Callable() {

          @Override
          public String call() throws Exception {
              Thread.sleep(10000);
              System.out.println("    :" + Thread.currentThread().getName());
              return "callable!";
          }
      };
  }

  @Configuration
  public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {

  @Resource
  private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;

  @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();
  }
}

方式3:と方式の2差は多くなくて、Callableで1階をアウトソーシングして、WebAsyncTaskに1つのタイムアウトのコールバックを設置して、タイムアウトの処理を実現することができます
    @RequestMapping(value = "/email/webAsyncReq", method = GET)
    @ResponseBody
    public WebAsyncTask webAsyncReq () {
        System.out.println("    :" + Thread.currentThread().getName());
        Callable result = () -> {
            System.out.println("      :" + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (Exception e) {
                // TODO: handle exception
            }
            logger.info("     ");
            System.out.println("      :" + Thread.currentThread().getName());
            return "success";
        };
        WebAsyncTask wat = new WebAsyncTask(3000L, result);
        wat.onTimeout(new Callable() {

            @Override
            public String call() throws Exception {
                // TODO Auto-generated method stub
                return "  ";
            }
        });
        return wat;
    }

方式4:DeferredResultは比較的複雑なビジネスロジックを処理することができ、最も主要なのはやはり別のスレッドの中でビジネス処理と戻りを行うことができ、2つの全く関係のないスレッド間の通信を行うことができる.
@RequestMapping(value = "/email/deferredResultReq", method = GET)
    @ResponseBody
    public DeferredResult deferredResultReq () {
        System.out.println("    :" + Thread.currentThread().getName());
        //      
        DeferredResult result = new DeferredResult(60*1000L);
        //             
        result.onTimeout(new Runnable() {

            @Override
            public void run() {
                System.out.println("DeferredResult  ");
                result.setResult("   !");
            }
        });
        result.onCompletion(new Runnable() {

            @Override
            public void run() {
                //   
                System.out.println("    ");
            }
        });
        myThreadPoolTaskExecutor.execute(new Runnable() {

            @Override
            public void run() {
                //      
                System.out.println("    :" + Thread.currentThread().getName());
                //    
                result.setResult("DeferredResult!!");
            }
        });
       return result;
    }

二、SpringBootにおける非同期呼び出しの使用
1、紹介
非同期リクエストの処理.非同期リクエストを除いて、一般的には非同期呼び出しを使用することが多いはずです.通常、開発の過程で、実際のビジネスとは関係なく、緊密性がない方法に遭遇します.例えばログ情報を記録するなどの業務.このとき、通常は新しいスレッドを起動して、プライマリスレッドが他のビジネスを非同期で実行できるようにするビジネス処理を行います.
2、使い方(スプリングベース)
起動クラスに@EnableAsyncを追加して非同期呼び出し@Async注記を有効にする必要があります
この注記を非同期で実行する必要があるメソッドに追加すると@Async("threadPool")になります.threadPoolはカスタムスレッドプールです.
コード略...ラベルは2つだけで、自分で試してみればいいです.
3、注意事項
デフォルトではTaskExecutorが設定されていない場合、デフォルトではSimpleAsyncTaskExecutorというスレッドプールが使用されますが、このスレッドは本当の意味でのスレッドプールではありません.スレッドは再利用されないため、呼び出すたびに新しいスレッドが作成されます.コンソール・ログの出力から、出力スレッド名が増加するたびに増加していることがわかります.スレッドプールを定義したほうがいいです
呼び出しの非同期メソッドは、同じクラスのメソッド(同じクラスの内部クラスを含む)では使用できません.簡単に言えば、Springはスキャンの開始時にエージェントクラスを作成しますが、同じクラスの呼び出し時には、独自のエージェントクラスを呼び出すので、通常の呼び出しと同じです.
他の注釈も@Cacheなどと同じ理屈で、はっきり言えばSpringのエージェントメカニズムによるものです.したがって,開発では,非同期サービスを単独でクラスを抽出して管理することが望ましい.以下、重点的に述べる.
4、@Async非同期メソッドが無効になるのはどのような場合ですか?
a.同じクラスを呼び出すには@Async非同期メソッドがあります.springで@Asyncや@Transactional、cacheなどの注釈は本質的に動的エージェントを使用していますが、Springコンテナは初期化時にSpringコンテナがAOP注釈を含むクラスオブジェクトをエージェントオブジェクトに「置き換え」ます(簡単に理解します).では、注釈が失効する原因は明らかです.メソッドを呼び出すのはエージェントオブジェクトではなくオブジェクトそのものであるため,Springコンテナを通過していないため,解決策もこの考え方に沿って解決される.
b.静的(static)メソッドを呼び出す
c.呼び出し(private)私有化方法
5、4の中の問題1を解決する方法(他の2、3の2つの問題は自分で注意すればいい)
非同期で実行するメソッドを1つのクラスに単独で抽出する原理は、非同期を実行するメソッドを1つのクラスに単独で抽出すると、このクラスはSpringによって管理されているに違いありません.他のSpringコンポーネントが呼び出す必要がある場合は必ず注入され、実際に注入されるのはエージェントクラスです.
実際,我々の注入オブジェクトはSpringコンテナから現在のSpringコンポーネントにメンバー変数を付与するものであり,一部のクラスではAOP注釈が使用されているため,実際にSpringコンテナに実際に存在するのはそのエージェントオブジェクトである.では、コンテキストを使用して、独自のエージェントオブジェクト呼び出し非同期メソッドを取得できます.
@Controller
@RequestMapping("/app")
public class EmailController {

    //  ApplicationContext       ,     ,           
    @Autowired
    private ApplicationContext applicationContext;

    @RequestMapping(value = "/email/asyncCall", method = GET)
    @ResponseBody
    public Map asyncCall () {
        Map resMap = new HashMap();
        try{
            //                  
            //this.testAsyncTask();
            //                    
            EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class);
            emailController.testAsyncTask();
            resMap.put("code",200);
        }catch (Exception e) {
            resMap.put("code",400);
            logger.error("error!",e);
        }
        return resMap;
    }

    //     public,   static  
    @Async
    public void testAsyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println("        !");
    }

}

6、cglibエージェントを開き、Springエージェントクラスを手動で取得し、同類の非同期メソッドを呼び出す.
まず,起動クラスに@EnableAspectJAutoProxy(exposeProxy = true)注記を加える.
コード実装:
@Service
@Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
public class EmailService {

    @Autowired
    private ApplicationContext applicationContext;

    @Async
    public void testSyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println("        !");
    }


    public void asyncCallTwo() throws InterruptedException {
        //this.testSyncTask();
//        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
//        emailService.testSyncTask();
        boolean isAop = AopUtils.isAopProxy(EmailController.class);//       ;
        boolean isCglib = AopUtils.isCglibProxy(EmailController.class);  //   CGLIB       ;
        boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class);  //   JDK           ;
        //      !!!
        EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);
        EmailService proxy = (EmailService) AopContext.currentProxy();
        System.out.println(emailService == proxy ? true : false);
        proxy.testSyncTask();
        System.out.println("end!!!");
    }
}