Spring @Async تمكين يسمح لنا بإنشاء طرق غير متزامنة في الربيع. دعونا نستكشف @Async
في هذا البرنامج التعليمي حول إطار الربيع. باختصار، عند تعليق طريقة في فول الفاصل الزمني بواسطة @Async
, سيقوم الربيع بتنفيذها في موضوع منفصل والمتصل بالطريقة لن ينتظر حتى اكتمال تنفيذ الطريقة. سنقوم بتعريف خدمتنا الخاصة واستخدام Spring Boot 2 في هذا المثال. دعونا نبدأ!
ربيع @Async مثال
سنستخدم Maven لإنشاء مشروع عينة للعرض. لإنشاء المشروع، قم بتنفيذ الأمر التالي في الدليل الذي ستستخدمه كمساحة عمل:
mvn archetype:generate -DgroupId=com.journaldev.asynchmethods -DartifactId=JD-SpringBoot-AsyncMethods -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
إذا كنت تقوم بتشغيل Maven للمرة الأولى ، فسيستغرق الأمر بضع ثوانٍ لإكمال أمر الإنشاء لأن Maven يحتاج إلى تنزيل جميع الوصول اللازمة والفنون من أجل إتمام مهمة الإنشاء. هكذا يبدو إنشاء المشروع:
بمجرد أن تقوم بإنشاء المشروع ، لا تتردد في فتحه في بيئة التطوير المفضلة لديك. الخطوة التالية هي إضافة تبعيات Maven المناسبة إلى المشروع. إليك ملفpom.xml
مع التبعيات المناسبة:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
وأخيرًا ، لفهم جميع ملفات JAR التي تمت إضافتها إلى المشروع عند إضافة هذا التبعية ، يمكننا تشغيل أمر بسيط في Maven يسمح لنا برؤية شجرة التبعيات الكاملة للمشروع عند إضافة بعض التبعيات إليه. إليك الأمر الذي يمكننا استخدامه:
mvn dependency:tree
عند تشغيل هذا الأمر ، سيظهر لنا الشجرة التالية للتبعيات:
تمكين الدعم اللامتزامن
قم بتمكين دعم البرمجة اللامزامنة أيضًا، مجرد مسألة واحدة من التعليق. بالإضافة إلى تمكين التنفيذ اللامزامن، سنستخدم أيضًا Executor الذي يتيح لنا تحديد حدود الخيوط أيضًا. سنتحدث أكثر عن هذا بمجرد كتابة الشيفرة:
package com.journaldev.asynchexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@SpringBootApplication
@EnableAsync
public class AsyncApp {
...
}
هنا قمنا باستخدام تعليق @EnableAsync
الذي يمكن تمكين قدرة Spring على تشغيل الطرق اللامزامنة في خلفية حوض الخيوط. بعد ذلك، نضيف أيضًا Executor المذكور:
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("JDAsync-");
executor.initialize();
return executor;
}
هنا، نعين أن الحد الأقصى للخيوط التي يجب أن تعمل بشكل متزامن هو 2 وحجم الطابور تم تعيينه إلى 500. فيما يلي الشيفرة الكاملة للصنف مع تصريحات الاستيراد:
package com.journaldev.asynchexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@SpringBootApplication
@EnableAsync
public class AsyncApp {
public static void main(String[] args) {
SpringApplication.run(AsyncApp.class, args).close();
}
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("JDAsync-");
executor.initialize();
return executor;
}
}
سنقوم بعمل خدمة بعد ذلك تستخدم في الواقع تنفيذات الخيوط.
إنشاء نموذج
سنستخدم واجهة برمجة التطبيقات العامة Movie API التي تعيد ببساطة بيانات الفيلم. سنقوم بتحديد نموذجنا لنفس الشيء:
package com.journaldev.asynchexample;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class MovieModel {
private String title;
private String producer;
// الحصول على وتعيينات القيم
@Override
public String toString() {
return String.format("MovieModel{title='%s', producer='%s'}", title, producer);
}
}
لقد استخدمنا @JsonIgnoreProperties
حتى إذا كانت هناك مزيد من السمات في الاستجابة، يمكن تجاهلها بشكل آمن من قبل Spring.
إنشاء الخدمة
حان الوقت لتحديد خدمتنا التي ستتصل بواجهة برمجة تطبيقات الأفلام المذكورة. سنستخدم RestTemplate بسيط للوصول إلى API GET والحصول على النتائج بشكل غير متزامن. دعنا نلقي نظرة على الشيفرة البرمجية العينة التي نستخدمها:
package com.journaldev.asynchexample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.CompletableFuture;
@Service
public class MovieService {
private static final Logger LOG = LoggerFactory.getLogger(MovieService.class);
private final RestTemplate restTemplate;
public MovieService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
@Async
public CompletableFuture lookForMovie(String movieId) throws InterruptedException {
LOG.info("Looking up Movie ID: {}", movieId);
String url = String.format("https://ghibliapi.herokuapp.com/films/%s", movieId);
MovieModel results = restTemplate.getForObject(url, MovieModel.class);
// تأخير اصطناعي لمدة 1 ثانية لأغراض التوضيح
Thread.sleep(1000L);
return CompletableFuture.completedFuture(results);
}
}
هذا الفصل هو @Service
مما يجعله مؤهلاً لفحص المكونات الربيعية. نوع إرجاع طريقة lookForMovie
هو CompletableFuture
وهو متطلب لأي خدمة غير متزامنة. نظرًا لأن التوقيت للواجهة البرمجة التطبيقية يمكن أن يختلف ، لقد أضفنا تأخيرًا لمدة 2 ثانية للتوضيح.
جعل CommandLineRunner
سنقوم بتشغيل تطبيقنا باستخدام CommandLineRunner وهو أسهل طريقة لاختبار تطبيقنا. يقوم CommandLineRunner بالتشغيل مباشرة بعد تهيئة جميع الحبات في التطبيق. دعنا نرى الشيفرة لـ CommandLineRunner:
package com.journaldev.asynchexample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
@Component
public class ApplicationRunner implements CommandLineRunner {
private static final Logger LOG = LoggerFactory.getLogger(ApplicationRunner.class);
private final MovieService movieService;
public ApplicationRunner(MovieService movieService) {
this.movieService = movieService;
}
@Override
public void run(String... args) throws Exception {
// بدء الساعة
long start = System.currentTimeMillis();
// قم بتشغيل العديد من الاستعلامات غير المتزامنة
CompletableFuture page1 = movieService.lookForMovie("58611129-2dbc-4a81-a72f-77ddfc1b1b49");
CompletableFuture page2 = movieService.lookForMovie("2baf70d1-42bb-4437-b551-e5fed5a87abe");
CompletableFuture page3 = movieService.lookForMovie("4e236f34-b981-41c3-8c65-f8c9000b94e7");
// انضم إلى جميع الخيوط حتى نتمكن من الانتظار حتى تنتهي جميعها
CompletableFuture.allOf(page1, page2, page3).join();
// طباعة النتائج ، بما في ذلك الوقت المنقضي
LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
LOG.info("--> " + page1.get());
LOG.info("--> " + page2.get());
LOG.info("--> " + page3.get());
}
}
لقد استخدمنا فقط RestTemaplate للوصول إلى واجهة برمجة التطبيق التجريبية التي استخدمناها مع بعض معرفات الأفلام التي تم اختيارها عشوائيًا. سنقوم بتشغيل التطبيق لنرى ما الإخراج الذي يعرضه.
تشغيل التطبيق
عند تشغيل التطبيق، سنرى الإخراج التالي:
2018-04-13 INFO 17868 --- [JDAsync-1] c.j.a.MovieService : Looking up Movie ID: 58611129-2dbc-4a81-a72f-77ddfc1b1b49
2018-04-13 08:00:09.518 INFO 17868 --- [JDAsync-2] c.j.a.MovieService : Looking up Movie ID: 2baf70d1-42bb-4437-b551-e5fed5a87abe
2018-04-13 08:00:12.254 INFO 17868 --- [JDAsync-1] c.j.a.MovieService : Looking up Movie ID: 4e236f34-b981-41c3-8c65-f8c9000b94e7
2018-04-13 08:00:13.565 INFO 17868 --- [main] c.j.a.ApplicationRunner : Elapsed time: 4056
2018-04-13 08:00:13.565 INFO 17868 --- [main] c.j.a.ApplicationRunner : --> MovieModel{title='My Neighbor Totoro', producer='Hayao Miyazaki'}
2018-04-13 08:00:13.565 INFO 17868 --- [main] c.j.a.ApplicationRunner : --> MovieModel{title='Castle in the Sky', producer='Isao Takahata'}
2018-04-13 08:00:13.566 INFO 17868 --- [main] c.j.a.ApplicationRunner : --> MovieModel{title='Only Yesterday', producer='Toshio Suzuki'}
إذا لاحظت بعناية، فقد تم إنشاء خيطين فقط ليتم تنفيذهما في التطبيق، وهما JDAsync-1
و JDAsync-2
.
الاستنتاج
في هذا الدرس، قمنا بدراسة كيفية استخدام قدرات البرنامج المساعد للتنفيذ الغير متزامن مع Spring Boot 2. اقرأ المزيد من المقالات المتعلقة بـ Spring هنا.
تنزيل الشفرة المصدرية
Source:
https://www.digitalocean.com/community/tutorials/spring-async-annotation