2015-03-06 6 views
11

Ich versuche, einen asynchronen Controller mit SprintBoot zu implementieren. Ich möchte eine REST-Anforderung an einen Controller senden, so dass der Controller sofort zurückkehrt, während die Arbeit auf dem Server fortgesetzt wird.Wie implementiert man eine asynchrone REST-Anfrage an einen Controller mit Springboot?

Ich folge diesem Frühling Beispiel: http://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support

Ich vermute, das ein Konfigurationsproblem ist. Kann mir bitte jemand sagen, was ich vermisse? Ich bin neu in Spring, also wenn Sie bitte so viele Details wie möglich zur Verfügung stellen könnten, wäre es zu schätzen.

eine Arbeits Controller habe ich die folgenden Änderungen:

// Before 
@RequestMapping(method=RequestMethod.POST) 
public String processUpload(final MultipartFile file) { 
    // ... 
    return "someView"; 
} 

// After 
@RequestMapping(method=RequestMethod.POST) 
public Callable<String> processUpload(final MultipartFile file) { 

    return new Callable<String>() { 
    public Object call() throws Exception { 
     // ... 
     return "someView"; 
    } 
    }; 
} 

Ich kann den neuen Controller nennen, aber ich habe zwei Fragen unter:

  1. Der neue Controller nicht asynchron aufgerufen wird. Der Browser hängt während des Anrufs. Der Aufruf führt den Code jedoch aus.
  2. Die Anforderungszeitüberschreitung mit diesem Fehler:

    2015-03-06 16: 36: 10,592 ERROR 13012 --- [MvcAsync1] oswcrequest.async.WebAsyncManager: Es kann keine vollständige async Verarbeitung wegen Zeitüberschreitung oder Netzwerkfehler

Update: konnte ich das Timeout lösen, indem die folgende Bohne in meiner Anwendung Datei zu erstellen:

@Bean 
public EmbeddedServletContainerFactory servletContainer() { 
     TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(); 
     factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 

      @Override 
      public void customize(Connector connector) { 

       connector.setPort(9000); 
       connector.setAsyncTimeout(60000); 
      } 
     }); 
     return factory; 
    } 

Aber der Aufruf an die Steuerung ist immer noch nicht asynchron. Der Browser bleibt für die Dauer des Anrufs hängen.

Ich bin immer noch auf der Suche nach Hilfe, wie Sie einen REST-Aufruf an die Steuerung sofort zurückkehren, während Sie die Arbeit im Hintergrund erledigen.

Update II

Danke Dave. Ich habe versucht, eine asynchrone Methode in einer Bean zu implementieren.

Hier ist meine Anwendungsklasse:

@SpringBootApplication 
@EnableAsync 
public class Application { 

    public static void main(String[] args) { 
     SpringApplication.run(Application.class, args); 
    } 

    @Bean 
    public EmbeddedServletContainerFactory servletContainer() { 
     TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(); 
     factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 

      @Override 
      public void customize(Connector connector) { 

       connector.setPort(9000); 
       connector.setAsyncTimeout(60000); 
      } 
     }); 
     return factory; 
    } 
} 

Hier meine Bean-Klasse ist:

public class LongProcess { 

    @Async 
    public Future<String> call() { 
     try { 
      System.out.println("Sleeping now..."); 
      Thread.sleep(10000); 
      return new AsyncResult<String>("Hey"); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
      return null; 
     } 
    } 

} 

Meine Konfigurationsklasse:

@Configuration 
@EnableAsync 
public class LongProcessConfiguration implements AsyncConfigurer { 

    @Bean 
    public LongProcess longProcessBean() { 
     return new LongProcess(); 
    } 

    @Override 
    public Executor getAsyncExecutor() { 
     ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); 
     taskExecutor.setMaxPoolSize(10); 
     taskExecutor.setThreadNamePrefix("LULExecutor-"); 
     taskExecutor.initialize(); 
     return taskExecutor; 
    } 

    @Override 
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { 
     return new SimpleAsyncUncaughtExceptionHandler(); 
    } 

} 

Mein Controller-Methode:

@RequestMapping("/utilities/longProcess") 
    public String longProcess() { 

     System.out.println("Starting long process..."); 
     CsvFileDifferConfiguration context = new CsvFileDifferConfiguration(); 
     LongProcess process = context.longProcessBean(); 
     Future<String> result = process.call(); 
     System.out.println("Done!"); 
     return "{success: 1}"; 

    } 

Dies kommt leider immer noch nicht sofort zurück. Beachten Sie, dass mir das Ergebnis von LongProcess nicht wichtig ist. Die Methode wird erfolgreich, aber nicht im Hintergrund aufgerufen. Irgendeine Idee, was ich vermisse?

@RequestMapping("/utilities/longProcess") 
    public String longProcess() throws InterruptedException { 

     System.out.println("Starting long process..."); 
     CsvFileDifferConfiguration context = new CsvFileDifferConfiguration(); 
     LongProcess process = context.longProcessBean(); 
     Future<String> result = process.call(); 
     while (!(result.isDone())) { 
      Thread.sleep(1); //10-millisecond pause between each check 
      System.out.println("Waiting for Long Process..."); 
     } 
     System.out.println("Done!"); 
     return "{success: 1}"; 

    } 

Update-III

I ersetzt

CsvFileDifferConfiguration context = new CsvFileDifferConfiguration(); 
      LongProcess process = context.longProcessBean(); 
:

Als Test, wenn ich die Controller-Methode zu warten, bis das Ergebnis zu ändern, wird der Warteblock nie eingegeben

mit

@Autowired 
private LongProcess process; 

und das Problem gelöst.

Antwort

17

Ich glaube, Sie missverstehen die MVC async (und Servlet 3) Funktionen. Wenn Ihre Controller-Methode viel Zeit in Anspruch nimmt, wird sie in einem anderen Thread aufgerufen als der, der für die eingehende Anfrage verwendet wird. Sie muss jedoch Daten an den Client über die gleiche HTTP-Verbindung zurücksenden dieser Standpunkt. Um sofort zurückzukehren, aber die Verarbeitung im Hintergrund benötigen Sie keine asynchrone MVC, Sie müssen nur die teure Verarbeitung in einem Hintergrund-Thread (z. B. durch Aufruf einer @Async Methode in einem anderen @Bean).

+1

Danke Dave. Ich habe Ihren Vorschlag versucht (siehe Update oben), aber der Anruf wird immer noch nicht im Hintergrund ausgeführt. Irgendwelche Vorschläge geschätzt. –

+2

Sieht aus, als hättest du es repariert (der 'LongProcess' muss ein' @Bean' sein). Wie wäre es mit der Antwort? –

+0

@DaveSyer Ich rufe Async auf andere Bean, aber mein RestService blockiert immer noch. Code hier ... Bean https://pastebin.com/ZejaaUcg Konfiguration https://pastebin.com/Dz6aFya4 Service https://pastebin.com/wbRLrupd – Zaknafein

Verwandte Themen