In this lab you’ll use some of the features of Spring 3.0 that support RESTful web services. Note that there’s more than we can cover in this lab, please refer back to the presentation for a good overview.
What you will learn
Specific subjects you will gain experience with:
RestTemplate
Estimated time to complete: 50 minutes
Quick instructions for this exercise have been embedded within
the lab materials in the form of TODO comments. To display them,
open the Tasks
view (Window >> Show View >>
Tasks (not Task
List)).
Occasionally, TODO’S defined within XML files may fail to
appear in the Tasks
view (i.e. gaps in
the number sequence). To correct this, go to Preferences >> General >> Editors
>> Structured Text Editor >> Task Tags
pane. Check Enable searching for Task
Tags
and click Clean and Redetect
Tasks
. On the Filters
tab,
ensure XML content type is checked.
The instructions for this lab are organized into sections. In
the first section you’ll add support for retrieving a
JSON-representation of accounts and their beneficiaries and test
that using the RestTemplate
. In the
second section you’ll add support for making changes by
adding an account and adding and removing a beneficiary. The
optional bonus section will let you map an existing exception to a
specific HTTP status code.
In this section you’ll expose accounts and beneficiaries
as RESTful resources using Spring’s URI template support,
HTTP Message Converters and the RestTemplate
.
(TODO-01) First open the RestWsApplication
class in the accounts
package to see how the application
is bootstrapped: it imports the AppConfig
configuration class, which
contains an accountManager
bean that
provides transactional data access operations to manage Account
instances. As RestWsApplication
uses the
SpringBootApplication annotation, it will use component scanning
and will define a bean for the AccountController
class.
Under the src/test/java
source
folder you’ll find an AccountClientTests
JUnit test case: this is
what you’ll use to interact with the RESTful web services on
the server.
Finally, run this Spring Boot application (right click >> Run As >> Spring Boot App) and verify that the application deployed successfully by accessing http://localhost:8080 from a browser. When you see the welcome page, the application was started successfully. If you have a problem starting the application, the most likely cause is that you already have an application from a previous lab still running, so be sure to stop it.
(TODO-02) Open the AccountController
. Notice that it offers
several methods to deal with various requests to access certain
resources. Add the necessary annotations to the accountSummary
method to make it respond to GET requests to /accounts
.
Tip | |
---|---|
You need one annotation to map the method to the correct URL and HTTP Method, and another one to ensure that the result will be written to the HTTP response by an HTTP Message Converter (instead of an MVC View). |
When you’ve done that, save all work and restart the application. Now try to access http://localhost:8080/accounts from that same browser. Depending on the browser used, you may see the response inline or you may see a popup asking you what to do with the response: save it to a local file and open that in a local text editor (Notepad is available on every Windows machine). You’ll see that you’ve just received a response using a JSON representation (JavaScript Object Notation). How is that possible?
The reason is that the project includes the Jackson library on its classpath - check your Maven Dependencies folder for your project (in the Package Explorer).
If this is the case, an HTTP Message Converter that uses Jackson
will be active by default. The library mostly 'just works' with our
classes without further configuration: if you’re interested
you can have a look at the MonetaryAmount
and Percentage
classes and search for the Json
annotations to see the additional configuration.
(TODO 03) A client can process this JSON response anyway it sees
fit. In our case, we’ll rely on the same HTTP Message
Converter to deserialize the JSON contents back into Account
objects. Open the AccountClientTests
class under the src/test/java
source folder in the accounts.client
package. This class uses a
plain RestTemplate
to connect to the
server. Use the supplied template to retrieve the list of accounts
from the server, from the same URL that you used in your
browser.
Tip | |
---|---|
You can use the |
Note | |
---|---|
We cannot assign to a |
When you’ve completed this TODO
, run the test and make sure that the
listAccounts test succeeds. You’ll make the other test
methods pass in the following steps.
(TODO 04) To expose a single account, we’ll use the same
/accounts
URL followed by the entityId
of the Account
, for example /accounts/1
. Switch back to the AccountController
and complete the
accountDetails method.
Tip | |
---|---|
Since the |
Save your work and restart your application. To test your code, just try to access http://localhost:8080/accounts/0 to verify the result.
(TODO 05) When you’re done with the controller, complete
the AccountClientTests
by retrieving
the account with id 0.
Tip | |
---|---|
The |
Run the test and ensure that the getAccount test now succeeds as well.
If the time allocated for the lab is almost used up, this is a good place to stop.
In this section we will create and remove data.
So far we’ve only exposed resources by responding to GET methods. Now you’ll add support for creating a new account as a new resource.
(TODO 06) Update the createAccount method by adding a mapping to
it from POSTs to /accounts
. The body
of the POST will contain a JSON representation of an Account
, just like the representation that
our client received in the previous step. Make sure to annotate the
account
parameter appropriately to let
the request’s body be unmarshaled into an account object.
When the method completes successfully, the client should receive a
201 Created
instead of 200 OK
, so annotate the method to make that
happen as well. We will write a test for this method in an upcoming
step.
(TODO 07) RESTful clients that receive a 201 Created
response will expect a Location
header in the response containing
the URL of the newly created resource. Complete the TODO by
implementing entityWithLocation(). Then save all work and restart
the application.
Tip | |
---|---|
To help you generate the full URL on which the new account can
be accessed, you will need to use a |
(TODO 08 - 09) When you’re done, complete the test method
by POSTing the given Account
to the
/accounts
URL. The RestTemplate
has two methods for this. Use
the one that returns the location of the newly created resource and
assign that to a variable. Then complete TODO
09
by retrieving the new account on the given location. The
returned Account
will be equal to the
one you POSTed, but will also have been given an entityId
when it was saved to the
database.
Run the tests again and see if the createAccount test runs successfully. Regardless of whether this is the case or not, proceed with the next step!
If your create account test worked, and you are running short of time, you may skip this section and move on to "Create and delete a beneficiary". However the HTTP monitor discussed in this section is a useful debugging tool to know.
(TODO 10) If your test did not work, you may be wondering what
caused an error. Because of all the help that you get from Spring,
it’s actually not that easy to see what’s happening at
the HTTP transport level in terms of requests and responses when
you exercise the application. For debugging or monitoring HTTP
traffic, Eclipse ships with a built-in tool that can be of great
value: the TCP/IP Monitor. To open this tool, which is just an
Eclipse View, press CTRL+3
(COMMAND+3
on MacOS) and type 'tcp' in the
resulting popup window; then press Enter
to open the TCP/IP Monitor View. Click
the small arrow pointing downwards (on the top right) and choose
"properties".
Choose "Add…" to add a new monitor. As local monitoring port, enter 8081 since this port is probably unused. As host name, enter "localhost" and as port enter 8080 since this is the port your application is running on. Press OK and then press "Start" to start the newly defined monitor.
Tip | |
---|---|
Don’t forget to start the monitor after adding it! |
Now switch to the AccountClientTests
and change the port
number in the BASE_URL
to 8081 so all
requests pass through the monitor.
Note | |
---|---|
This assumes that you’ve used that variable to construct all your URLs: if that’s not the case, then make sure to update the other places in your code that contain the port number as well! |
Now run the tests again and switch back to the TCP/IP Monitor View (double-click on the tab’s title to maximize it if it’s too small). You’ll see your requests and corresponding responses. Click on the small menu arrow again and now choose 'Show Header': this will also show you the HTTP headers, including the Location header you speficied for the response to the POST that created a new account.
Note | |
---|---|
Actually, there’s one request missing: the request to retrieve the new account. This is because the monitor rewrites the request to use port 8080, which means the Location header will include that port number instead of the 8081 the original request was made to. We won’t try to fix that in this lab, but it wouldn’t be too hard to come up with some interceptor that changes the port number to make all requests pass through the filter. |
If your createAccount test method didn’t work yet, then use the monitor to debug it. Proceed to the next step when the test runs successfully.
(TODO 11) Complete the addBeneficiary method in the AccountController
. This is similar to what
you did in the previous step, but now you also have to use a URI
template to parse the accountId. Make sure to return a 201 Created
status again. This time, the
response’s body will only contain the name of the
beneficiary: an HTTP Message Converter that will convert this to a
String
is enabled by default, so
simply annotate the method parameter again to obtain the name.
(TODO 12) Create a ResponseEntity containing the location of the newly created beneficiary the new beneficiary. Save all work and restart the application.
Note | |
---|---|
As you can see in the |
(TODO 13) Complete the removeBeneficiary method. This time,
return a 204 No Content
status.
(TODO 14 - 17) To test your work, switch to the AccountClientTests
and complete the TODOs.
When you’re done, run the test and verify that this time all
test methods run successfully. If this is the case, you’ve
completed the lab!
BONUS (Optional): return a 409 Conflict when creating an account with an existing number ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^^
(TODO 18) The current test ensures that we always create a new
account using a unique number. Let’s change that and see what
happens. Edit the createAccount method in the test case to use a
fixed account number, like "123123123"
. Run the test: the first time it
should succeed. Run the test again: this time it should fail. When
you look at the exception in the JUnit View or at the response in
the TCP/IP monitor, you’ll see that the server returned a
500 Internal Server Error
. If you look
in the Console View for the server, you’ll see what caused
this: a DataIntegrityViolationConstraint
, ultimately
caused by a SQLException
indicating
that the number is violating a unique constraint.
This isn’t really a server error: this is caused by the
client providing us with conflicting data when attempting to create
a new account. To properly indicate that to the client, we should
return a 409 Conflict
rather than the
500 Internal Server Error
that’s
returned by default for uncaught exceptions. To make it so, add a
new exception handling method that returns the correct code in case
of a DataIntegrityViolationConstraint
.
Tip | |
---|---|
Have a look at the existing |
When you’re done, run the test again (do it twice as the database will re-initialize on redeploy) and check that you now receive the correct status code. Optionally you can even restore the test method and create a new test method that verifies the new behavior.
The AccountController.createAccount() method uses a ServletUriComponentsBuilder
to generate the
location URL of the new Account. It does this by initializing
itself with the URL that invoked the createAccount() method. This
is possible because Spring MVC has put the current request
information in the current thread. This doesn’t happen when
running tests.
Follow the instructions in (TODO-20) to see how to mock up the necessary request for testing.