In the first blog entry of this series, we showed how to configure a message body reader (MBR) on an instance of Client. Specifically, our example required the registration of an MBR to convert an application/json representation of the Money bean.
Client client = ClientFactory.newClient(); client.configuration().register(MoneyMBR.class);
As stated in that blog entry, a Client instance can be used to create one or more instances of Target, which in turn, can be used to create and execute one or more requests. As we shall see shortly, targets can be used to create other targets as well. An interesting aspect of the API is that configurations can be inherited by descendants: thus, a target will inherit a client or another target's configuration and a request will inherit a target's configuration. Inheriting a configuration from a parent object creates a deep copy, so updates to descendants' configurations have no impact on a parent's configuration.
From the point of view of the Client API, instances of Client are heavyweight while instances of Target are lightweight. Generally speaking, a single instance of Client should be created and configured with the providers that are needed for the targets created from it. Different targets can be created with different configurations to access the various services needed by the application. Lastly, a particular request can be further configured if so required. Lets take a look at an example in which two targets are created, one from a Client and one from another Target:
// Get client and register MyProvider1 Client client = ClientFactory.newClient(); client.configuration().register(MyProvider1.class); // Create atm and register MyProvider2 // Inherits MyProvider1 from client Target atm = client.target("http://.../atm"); atm.configuration().register(MyProvider2.class); // Create balance and register MyProvider3 // Inherits MyProvider1, MyProvider2 from atm Target balance = atm.path("balance"); // new instance balance.configuration().register(MyProvider3.class);
In this example, an instance of Client is created and a provider registered; a target atm is created from it and a second provider registered and, finally, a target balance is created from atm with a third provider. Note that instances of Target are immutable with respect to their URI (but not their configurations!), thus the call to atm.path("balance") creates a new instance.
Configuration of an individual request takes an extra step given that request creation and request configuration operate on different method chains. For example,
Invocation.Builder b = client.target("http://.../atm/withdrawal") .queryParam("card", "111122223333") .queryParam("pin", "9876") .request("application/json"); b.configuration().register(MoneyMBR.class); Money mon = b.post(text("50.0"), Money.class);
In addition to provider registration, a Configuration object can be used to set properties (name-value pairs) as well as features. A feature is a user-provided class implementing the Feature interface that can be used to register providers, properties and other features as a single unit. We will take a look at features in later blog.