Learn how to use JAX-RS, CDI, and JSON-P to build a MicroProfile application.
MicroProfile is an initiative to optimize Enterprise Java for a microservices architecture. You will write REST web services with JAX-RS and JSON-P and also use CDI to inject dependencies and help manage the lifecycle of your application.
You will create an application that provides an inventory
service, which allows you to retrieve
the information for a particular host or a list of all previously registered hosts.
If the requested host is not in the inventory, the inventory
service attempts to retrieve
that host’s information by calling its system
service.
For example, to retrieve a list of all hosts in the inventory, enter the following URL:
http://localhost:9080/inventory/hosts
The service responds with a JSON message that contains host information for all previously registered hosts, such as in the following example:
{
"hosts": {
"localhost": {
"os.name": "Mac OS X",
"user.name": "foo"
},
"jon-doe": {
"os.name": "Windows 10",
"user.name": "bar"
}
},
"total": 2
}
To retrieve information about a specific host, call this address:
http://localhost:9080/inventory/hosts/<hostname>
The service responds with a JSON representation of the system properties that are retrieved
from the system
service of the specified host. The system
service is a simple REST service
that returns the system properties of a host. This service is implemented for you.
You will be running both system
and the inventory
services locally, on a single server.
First, create the JAX-RS service that serves as the systems
endpoint:
src/main/java/io/openliberty/guides/microprofile/InventoryResource.java
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryResource.java[role=include]
The @Path
annotation identifies the URI path template that the resource responds to.
All @GET
annotated methods do not yet have implemented bodies, which are planned to be written at a later point.
The getPropertiesForHost
method responds to a GET
HTTP request and returns the system properties
JSON object for the specified host. You can use the @PathParam
annotation inside the method
signature to retrieve the hostname as an argument.
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryResource.java[role=include]
The listContents
method also responds to a GET
request and returns the currently stored
system properties, the contents of the inventory.
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryResource.java[role=include]
The Inventory Manager is responsible for storing the data about the systems in the inventory and managing them.
src/main/java/io/openliberty/guides/microprofile/InventoryManager.java
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryManager.java[role=include]
For simplicity, use a ConcurrentMap
to store the data. Alternatively, you can use
a database or another service to do so.
This class will contain three simple methods: add
, get
, and list
.
The add
method adds the JSON object that contains the system properties to the inventory.
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryManager.java[role=include]
The get
method returns a JSON object that contains the system properties for the specified hostname.
Notice the call to InventoryUtil
. This class contains utility methods that test connections
and access the system
service. InventoryUtil
is covered in more detail in a later section.
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryManager.java[role=include]
The list
method returns a JSON representation of the current contents of the inventory.
For simplicity, list
returns only the OS name and username of each registered host.
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryManager.java[role=include]
CDI is a Java EE specification that makes it simple for developers to use enterprise beans in web applications. It simplifies dependency and lifecycle management and enables developers to focus on their code.
To perform a dependency injection, use the @Inject
annotation .
In the previous section, you created an InventoryManager
bean. Inject that bean into
the InventoryResource
class to use what the bean provides without the need for its instantiation.
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryResource.java[role=include]
With the injection in place, implement the @GET
annotated methods:
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryResource.java[role=include]
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryResource.java[role=include]
The CDI specification defines multiple scopes. Use the following annotations to define the scopes that you need for your objects:
-
The
@RequestScoped
annotation indicates that a new instance is created for each request. -
The
@ApplicationScoped
annotation indicates that only one instance is created for each application.
Annotate InventoryResource
with @ApplicationScoped
because it does not depend on any request-specific state.
In general, annotate all resources with @ApplicationScoped
unless they need to be created once
every request.
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryResource.java[role=include]
Annotate InventoryManager
with @ApplicationScoped
because it, too, does not depend on any request-specifc state.
For example, when you use a real database resource instead of a 'Map', you improve performance because
the container will not re-initialize database resources on each request.
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryManager.java[role=include]
You can have a beans.xml
file under the META-INF directory or under the WEB-INF directory
of your web application for CDI scanning. Since CDI 1.1 and Java EE 7, scanning is enabled by default, so
you do not need a beans.xml
file here. Annotated beans are automatically discovered by default.
Recall the InventoryUtil.getProperties(hostname)
call from Creating the Inventory Manager.
This class is used to access the system
service from the inventory
service as a JAX-RS client.
link:finish/src/main/java/io/openliberty/guides/microprofile/util/InventoryUtil.java[role=include]
The InventoryUtil
class contains a getProperties
method that makes an HTTP GET
request to
the system
service and returns the retrieved JSON object. The responseOk
method tests for a valid
connection to the system
service at the specified host. Together, these methods enable interaction with
the system
service.
Finally, create an application class and identify the base URI where the application serves requests.
Create the following JAX-RS application class: src/main/java/io/openliberty/guides/microprofile/InventoryApplication.java
link:finish/src/main/java/io/openliberty/guides/microprofile/InventoryApplication.java[role=include]
You now have your inventory
service, and your application has a starting point:
http://localhost:9080/inventory/properties
To see the new application in action, run the Maven liberty:start-server
from the start
directory:
cd start
mvn liberty:start-server
Once the server is running, you can find both services at the following locations:
The application provides a simple inventory management REST service that you can access at the following URLs:
http://localhost:9080/inventory/hosts
http://localhost:9080/inventory/hosts/{hostname}
# Example URL to retrieve or register a system
http://localhost:9080/inventory/hosts/localhost
The first URL returns the current contents of the inventory. The second URL returns the system
properties for the specified hostname. The service retrieves these properties from the inventory.
If the inventory does not have an entry for the specified hostname, the service instead calls
the system
service that runs on the hostname to retrieve system properties. The system properties
that are retrieved from the system
service are then stored in the inventory and returned.
Once you start the server, you can access the two previous URLs from your browser to manually test the application. However, you should rely on automated tests because they will trigger a failure if a code change introduces a defect. JUnit and the JAX-RS Client API provide a simple environment to test the application.
Create a test class, src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java
.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Mark each test method with the @Test
annotation.
Use the @Before
and @After
annotations to perform setup and teardown tasks for each
of your individual tests.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
The test code needs some information about the application in order to make requests. The server port and the application context root are key and are dictated by the server configuration. To make the information easier to change, specify it in a single place like the Maven pom.xml file instead of hardcoding it.
<testServerHttpPort>9080</testServerHttpPort>
<testServerHttpsPort>9443</testServerHttpsPort>
These Maven properties can then be passed to the Java test program as a series of system properties:
<systemPropertyVariables>
<liberty.test.port>${testServerHttpPort}</liberty.test.port>
<running.bluemix>${running.bluemix}</running.bluemix>
<cf.context.root>${cf.context.root}</cf.context.root>
</systemPropertyVariables>
You can then access these properties with the System.getProperties
method. Use these properties
to create URL representations for each running service:
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
You can use the JAX-RS client to make the REST call and convert the payload to and from a JSON-P
representation. To do so, configure the client with the JsrJsonpProvider
property:
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Normally, you cannot control the order of execution for test methods. However, you can place
each test method within a single container method. This method is then annotated with @Test
.
The following test suite contains four test methods, which run in the order they appear in the suite:
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Create a test called testEmptyInventory
. This test asserts that the inventory
is empty when the inventory
service first starts. Use the Response
object to receive a
response from the target URL. You can then retrieve the returned JSON from this response.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Write a getResponse
helper method to reuse the same line of code to retrieve a response from a
particular URL. This method helps keep your code neat and organized.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Write another helper called assertResponse
. This method ensures that the received response code is
valid and returns a status code 200.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Use the returned Response
object to retrieve the JSON file:
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Write an assertion to make sure that the total
field is set to 0, which indicates that the inventory is empty.
Close the response before the method returns.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Create a test called testHostRegistration
. This test asserts that registering the localhost
system
is successful.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Write a helper method called visitLocalhost
. This method makes a simple GET
request to the
system
service at localhost
, registering the localhost
system.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Create a test called testSystemPropertiesMatch
. This test asserts that the currently stored system
properties for localhost
match the ones returned by the system
service.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Write a helper method called assertProperty
. Use this method to assert that the properties
os.name
and user.name
match.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
Finally, create a test called testUnknownHost
. This test asserts that the inventory
service
returns an error when you make a registration attempt on an unknown hostname.
link:finish/src/test/java/it/io/openliberty/guides/microprofile/EndpointTest.java[role=include]
To rebuild and run the tests, navigate to the start
directory and run the mvn clean install
command
from the command line.
# If the server is still running from previous steps, stop it first
mvn liberty:stop-server
# Then execute
mvn clean install
It might take some time before the tests are executed. If the tests pass, you will see the following output:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.microprofile.EndpointTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.668 sec - in it.io.openliberty.guides.microprofile.EndpointTest
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
Congratulations! You have just built and tested a MicroProfile application with JAX-RS, CDI, JSON-P and Open Liberty.