tags | projects | ||
---|---|---|---|
|
|
This guide walks you through the steps to create asynchronous queries to GitHub. The focus is on the asynchronous part, a feature often used when scaling services.
You’ll build a lookup service that queries GitHub user information and retrieves data through GitHub’s API. One approach to scaling services is to run expensive jobs in the background and wait for the results using Java’s Future
interface. Java’s Future
is essentially a container housed to hold the potential results. It gives you methods to let you poll if the results have arrived yet, and when they have, the ability to access the results.
Before you can create a GitHub lookup service, you need to define a representation for the data you’ll retrieve through GitHub’s API.
To model the user representation, you create a resource representation class. Provide a plain old Java object with fields, constructors, and accessors:
src/main/java/hello/User.java
link:complete/src/main/java/hello/User.java[role=include]
Spring uses the Jackson JSON library to convert GitHub’s JSON response into a User
object. The @JsonIgnoreProperties
annotation signals Spring to ignore any attributes not listed in the class. This makes it easy to make REST calls and produce domain objects.
In this guide, we are only grabbing the name
and the blog
URL for demonstration purposes.
Next you need to create a service that queries GitHub to find user information.
src/main/java/hello/GitHubLookupService.java
link:complete/src/main/java/hello/GitHubLookupService.java[role=include]
The GitHubLookupService
class uses Spring’s RestTemplate
to invoke a remote REST point
(api.github.com/users/), and then convert the answer into a User
object.
The class is marked with the @Service
annotation, making it a candidate for Spring’s component scanning to detect it and add it to the application context.
The findUser
method is flagged with Spring’s @Async
annotation, indicating it will run on a separate thread. The method’s return type is Future<User>
instead of User
, a requirement for any asynchronous service. This code uses the concrete implementation of AsyncResult
to wrap the results of the GitHub query.
Note
|
Creating a local instance of the GitHubLookupService class does NOT allow the findUser method to run asynchronously. It must be created inside a @Configuration class or picked up by @ComponentScan .
|
The timing for GitHub’s API can vary. To demonstrate the benefits later in this guide, an extra delay of one second has been added to this service.
To run a sample, you can create an executable jar. Spring’s @Async
annotation works with web apps, but you don’t need all the extra steps of setting up a web container to see its benefits.
src/main/java/hello/Application.java
link:complete/src/main/java/hello/Application.java[role=include]
@SpringBootApplication
is a convenience annotation that adds all of the following:
-
@Configuration
tags the class as a source of bean definitions for the application context. -
@EnableAutoConfiguration
tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings. -
Normally you would add
@EnableWebMvc
for a Spring MVC app, but Spring Boot adds it automatically when it sees spring-webmvc on the classpath. This flags the application as a web application and activates key behaviors such as setting up aDispatcherServlet
. -
@ComponentScan
tells Spring to look for other components, configurations, and services in the thehello
package.
The main()
method uses Spring Boot’s SpringApplication.run()
method to launch an application. Did you notice that there wasn’t a single line of XML? No web.xml file either. This web application is 100% pure Java and you didn’t have to deal with configuring any plumbing or infrastructure.
The @EnableAsync
annotation switches on Spring’s ability to run @Async
methods in a background thread pool.
https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/build_an_executable_jar_subhead.adoc https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/build_an_executable_jar_with_both.adoc
Logging output is displayed, showing each query to GitHub. Each Future
result is monitored until available, so when they are all done, the log will print out the results along with the total amount of elapsed time.
Looking up PivotalSoftware Looking up Spring-Projects Looking up CloudFoundry Elapsed time: 1855 User [name=Pivotal Software, Inc., blog=http://pivotal.io] User [name=Cloud Foundry, blog=https://www.cloudfoundry.org/] User [name=Spring, blog=http://spring.io/projects]
To compare how long this takes without the asynchronous feature, try commenting out the @Async
annotation and run the service again. The total elapsed time should increase noticeably because each query takes at least a second.
Essentially, the longer the task takes and the more tasks are invoked simultaneously, the more benefit you will see with making things asynchronous. The trade off is handling the Future
interface. It adds a layer of indirection because you are no longer dealing directly with the results, but must instead poll for them. If multiple method calls were previously chained together in a synchronous fashion, converting to an asynchronous approach may require synchronizing results. But this extra work may be necessary if asynchronous method calls solves a critical scaling issue.
Congratulations! You’ve just developed an asynchronous service that lets you scale multiple calls at once.