1. jabsorb Manual
This manual covers the architecture and implementation details of jabsorb and contains a reference guide to the various components and interfaces. Please start with the Tutorial if you want to get started quickly.
2. Architecture
jabsorb consists of two main user visible components, the JSONRPCBridge and the JSONRPCServlet.
2.1. JSONRPCBridge
The JSONRPCBridge holds references to classes and objects that are exported to allow remote invocation by a JSON-RPC client. It is passed JSON-RPC requests from the transport (JSONRPCServlet) and it then performs the unmarshalling of JSON objects to Java objects, the method invocation and finally marshalls the method's Java object result back to JSON. Serializer objects perform the actual type conversion between Java and JavaScript objects.
The JSONRPCServlet (which implements the JSON-RPC HTTP transport) will use either a global singleton JSONRPCBridge instance or optionally one in the user's HttpSession (if it exists). You can place a JSONRPCBridge into a user's HttpSession to allow the JSONRPCServlet to make calls on objects that are exported only to that specific user session (these objects may for instance contain stateful data related to the specific user). A session specific bridge will delegate requests for objects it does not know about to the global singleton JSONRPCBridge instance.
Session specific bridges are useful for a number of reasons:
- to improve the security of the application
- export object methods to specific users
- hold state specific to a user in an exported object
- hold references to objects returned to a specific client
The JSONRPCServlet looks for the session specific bridge object under the attribute "JSONRPCBridge" in the HttpSession associated with the request (without creating a session if one does not already exist). If it can't find a session specific bridge instance, it will default to invoking against the global bridge.
An example of creating a session specific bridge in JSP is as follows:
<jsp:useBean id="JSONRPCBridge" scope="session"
class="org.jabsorb.JSONRPCBridge"/>
An example in Java (i.e. in a Servlet):
HttpSession session = request.getSession();
JSONRPCBridge bridge = (JSONRPCBridge) session.getAttribute("JSONRPCBridge");
if(bridge == null)
{
bridge = new JSONRPCBridge();
session.setAttribute("JSONRPCBridge", bridge);
}
To export all instance methods of an object to a client:
bridge.registerObject("myObject", myObject);
To export all static methods of a class to a client:
bridge.registerClass("MyClass", com.example.MyClass.class);
If registerObject and registerClass are called multiple times with the same key, then the object is replaced with the new one.
2.2. Global bridge
The global bridge singleton object allows exporting objects to all HTTP clients. This can be used for registering factory classes although care must be taken with authentication and security issues as these objects will be accessible to all clients. It can be fetched with JSONRPCBridge.getGlobalBridge().
To export all instance methods of an object to all clients:
JSONRPCBridge.getGlobalBridge().registerObject("myObject", myObject);
To export all static methods of a class to all clients:
JSONRPCBridge.getGlobalBridge().registerClass("MyClass", com.example.MyClass.class);
Note that the global bridge does not support registering of opaque or callable references and attempting to do so will throw an Exception. These operations are inherently session based and are disabled on the global bridge because there is currently no safe simple way to garbage collect such references across the JavaScript/Java barrier.
See the JSONRPCBridge Javadocs for more info.
2.3. JSONRPCServlet
This servlet, the transport part of jabsorb, handles JSON-RPC requests over HTTP and dispatches them to a JSONRPCBridge instance registered in the HttpSession if one exists (without creating a session if there isn't one already), or otherwise the global bridge.
The following would be used in your web.xml to export the servlet under the URI "/JSON-RPC" (this is the standard location):
<servlet>
<servlet-name>org.jabsorb.JSONRPCServlet</servlet-name>
<servlet-class>org.jabsorb.JSONRPCServlet</servlet-class>
<!--
the gzip_threshold indicates the response size at which the servlet will attempt to gzip the response
if it can.
Set this to -1 if you want to disable gzip compression for some reason,
or if you have another filter or other mechanism to handle gzipping for you.
Set this to 0 to attempt to gzip all responses from this servlet.
otherwise, set it to the minimum response size at which gzip compression is attempted.
note: if the browser making the request does not accept gzip compressed content,
or the result of gzipping would cause the response size to be larger (this could happen
with very small responses) then the content will be returned without gzipping regardless
of this setting, so it is very reasonable idea to set this to 0 for maximum bandwidth
savings, at the (very minor) expense of having the server attempt to gzip all responses.
-->
<init-param>
<param-name>gzip_threshold</param-name>
<param-value>200</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>org.jabsorb.JSONRPCServlet</servlet-name>
<url-pattern>/JSON-RPC</url-pattern>
</servlet-mapping>
Please note: due to relative mapping of URIs in your web container. You may need to set the URL in your client to: "/<web-app-name>/JSON-RPC".
See the JSONRPCServlet Javadocs for more info.
3. Type mapping
To allow jabsorb to transparently unmarshall complex nested objects and with that, the usage of Java's container classes, JSON-RPC needs a mechanism to preserve type information.
This comes from the combination of the JavaScript's typeless nature and the method that Java container classes gain their genericity (through the usage of a single base class 'Object', rather than using parameterized types such as C++ templates. Note: Java 5.0 however supports these parameterized collection types but support is not yet included in jabsorb for this).
In the case of unmarshalling a JavaScript object into a Java method argument with a generic container interface (such as List, Map, Set, or their concrete counterparts), the need for additional type information is apparant. We have no type information on either side to work out the mapping from the contained type of JavaScript array to the contained type of the Java container class.
With normal array method arguments ie. a Java method argument of class Foo[], we know the items in the JavaScript array must (or should) be all be class Foo.
With a Java method argument of class ArrayList, we only have Object as the contained type and no type information with a regular JavaScript object.
This leads to the method that jabsorb maintains it's transparent mapping - 'class hinting'.
3.1. Class Hinting
In the case of regular Java Beans an extra field javaClass is added that maps the typeless JavaScript object back to the Java class. The is used on the Java side during unmarshalling to ease the transparent mapping of the object back to it's Java Class when it is sent back to the server (although jabsorb can in some cases map the objects without the additional type information if the mapping is unambiguos ie. if the object is not inside of a generic container class, then the method class signature can be used).
For Java container classes such as the List, we can't map them to a JavaScript native array as we would have nowhere to store the type hint. So Java container classes have a special type mapping from Java to JavaScript described here:
3.1.1. Bean
Java beans (objects conforming to get getProperty, setProperty, etc. syntax) map directy to a JavaScript Object with the additional of the string member javaClass containing the Java class name. The property names have the get or set prefix removed and the first letter lowercased. eg.
{
"javaClass": "com.example.MyBean",
"someStringProperty": "foo",
"someBooleanProperty": true,
"someIntegerProperty": 10
}
3.1.2. List
List (ArrayList, LinkedList, Vector) maps to a JavaScript object with a string member javaClass containing the Java class name and a native JavaScript array member list containing the data. eg.
{
"javaClass": "java.util.ArrayList",
"list": [0, 1, 2, 3, 4]
}
3.1.3. Map
Map (HashMap, TreeMap, LinkedHaspMap) maps to a JavaScript object with a string member javaClass containing the Java class name and a native JavaScript object containing the key value pairs. eg.
{
"javaClass": "java.util.HashMap",
"map": {"foo key": "foo value"}
}
The Java string representation of the key object is used as the native JavaScript object type only supports strings as keys.
3.1.4. Set
Set (HashSet, TreeSet, LinkedHashSet) maps to a JavaScript object with a string member javaClass containing the Java class name and a native JavaScript object containing the set item string values as keys and set objects as values. eg.
{
"javaClass": "java.util.HashSet",
"set": {"foo key": "foo key"}
}
The Java string representation of the key object is used as the native JavaScript object type only supports strings as keys.
4. JavaScript Client
The JavaScript client JSONRpcClient constructs a transparent proxy providing method access to all methods on the JSON-RPC server.
It is constructed synchronously like this:
var jsonrpc = new JSONRpcClient("/webapp/JSON-RPC/")
and asynchronously like this:
var jsonrpc;
var cb = function(result, e) {
if(e) {
reportError(e);
} else {
// Continuation - client is ready to use
}
}
jsonrpc = new JSONRpcClient(cb, "/webapp/JSON-RPC/");
HTTP authentication can also be used
var jsonrpc = new JSONRpcClient("/webapp/JSON-RPC/", user, pass)
The constructor of the JSONRpcClient object queries the server using the internal method system.listMethods which returns an array with the list of object methods available on the server. This process involves the constructor making a request to the server, which if no callback is provided, will be executed synchronously.
After system.listMethods returns, proxy delegating functions are then automatically added to the new JSONRpcClient object with each of the method names on the server. If a callback function is provided, the callback will be executed when all proxy methods have been created and the JSONRpcClient object is ready to be used. The Asynchronous constructor style is recommended (see below).
4.1. Synchronous calls
Use of synchronous calls is highly discouraged in production code because it can cause the browser to freeze if the server does not respond in a timely manner. Asynchronous calls are recommended in all but trivial applications.
A synchronous call can be made on one of the server methods by calling the associated object method on the JSONRpcClient object. eg. to call the method echo on the object exported with the name test, we would use:
jsonrpc.test.echo("hello");
4.2. Asynchronous calls
Asynchronous calls are simply made by inserting a JavaScript callback function as the first argument.
jsonrpc.test.echo(cb, "hello");
Anonymous functions can also be used:
jsonrpc.test.echo(function (msg) { print(msg); }, "hello");
The callback function is passed two arguments:
function cb(result, exception) {
if(exception) { alert(exception.message); }
// do stuff here ...
}
The second argument to callback functions is required to capture exception information. You must be aware of the following when using async callback functions:
result == null && exception != null when an exception occured.
result != null && exception == null when the call completed successfully.
4.3. Exceptions
The JSONRpcClient constructor proxy methods can throw an exception of type JSONRpcClient.Exception (ie. e.constructor == JSONRpcClient.Exception). The JSONRpcClient.Exception object is derived from the JavaScript Error object and thus inherits its general properties such as message.
Two types of exceptions are thrown from proxy methods on the JSONRpcClient object:
- Client exceptions - exceptions that occured during the remote communication with the JSON-RPC server.
- Java native exceptions - exceptions throw by the remote code.
4.3.1. Client Exceptions
Thrown if a communication error occurs, a method cannot be found. It has the following properties:
- e.name == "JSONRpcClientException"
- e.code an integer error code containing either an HTTP status code or one of the following codes:
- JSONRpcClient.Exception.CODE_ERR_PARSE = 590
- JSONRpcClient.Exception.CODE_ERR_NOMETHOD = 591
- JSONRpcClient.Exception.CODE_ERR_UNMARSHALL = 592
- JSONRpcClient.Exception.CODE_ERR_MARSHALL = 593
- e.message a string containing descriptive text of the exception.
4.3.2. Java native exceptions
Thrown if the remote Java method raises an exception. It has the following properties:
e.name == "<class name of remote exception>"
- e.code == JSONRpcClient.Exception.CODE_REMOTE_EXCEPTION
- e.message a string containing descriptive text of the exception.
- e.javaStack a string containing the Java stack trace.
5. References
jabsorb is an ORB (Object Request Broker) with the ability to pass objects by reference and keep these references in the user's session.
Two types of references are handled: opaque references and callable references.
5.1. Opaque References
Objects of classes registered as References will be returned as opaque reference objects to JavaScript instead of passed by value which is the default behavior. When these opaque reference objects are passed to successive Java method calls, they will then be re-associated back to the original Java object (great for security sensitive objects).
A class can be registered as an opaque reference on the JSONRPCBridge as follows:
bridge.registerReference(org.jabsorb.test.Foo.class)
A reference in JSON format looks like this:
{ "javaClass":"org.jabsorb.test.Foo",
"objectID":5535614,
"JSONRPCType":"Reference" }
References should be used for privileged objects that contain information that needs to be kept secure or large or complex types that are not required in the JavaScript client but need to be passed as a reference in methods of exported objects.
5.2. Callable References
Objects of classes registered as Callable References will return dynamic proxies to allow invocation on the particular object instance in the server-side Java. There are extensions to the JSON-RPC protocol in the provided JSON-RPC JavaScript client for dynamic proxy creation support.
A class can be registered as a callable reference on the JSONRPCBridge as follows:
bridge.registerCallableReference(org.jabsorb.test.Bar)
A callable reference in JSON format looks list this:
{ "javaClass":"org.jabsorb.test.Bar",
"objectID":4827452,
"JSONRPCType":"CallableReference" }
CallableReferences can be registered for classes that for instance are returned from factory classes as a convenient way to avoid having to manually export these objects returned from these factory methods.
Note: In jabsorb 1.1.x and earlier, a limitation exists in the JSON-RPC client where only the top most object returned from a method can be made into a proxy. In jabsorb 1.2 and above this limitation does not exist.
As mentioned in the section on the global bridge, the global bridge does not support registering of opaque or callable references and attempting to do so will throw an Exception. These operations are inherently session based and are disabled on the global bridge because there is currently no safe simple way to garbage collect such references across the JavaScript/Java barrier. You should also be aware that using references on long running sessions could potentially cause memory issues as well, because any reference used will be retained until the session is released. In the future an API may be added for more carefully managing the disposal of references.
6. Local Argument Resolvers
LocalArgResolvers are classes that can resolve an argument from the exported method signatures on the server-side. The exported signature of methods that contain a class registered as a LocalArgResolver have that class removed. It does not need to be provided in call in the JSON-RPC client.
The LocalArgResolver provides a mechanism to get access to the HttpServletRequest, HttpServletResponse, HttpSession or JSONRPCBridge object associated with a request/method invocation. There are 4 LocalArgResolvers that are enabled by default:
HttpServletRequestArgResolver
HttpServletResponseArgResolver
HttpSessionArgResolver
- JSONRPCBridgeServletArgResolver
Additional LocalArgResolvers can be created by implementing the LocalArgResolver interface and calling JSONRPCBridge.registerLocalArgResolver()
An example method that has HttpServletRequest in the method signature:
public String getMyCountryName(HttpServletRequest request)
{
Locale locale = request.getLocale();
return locale.getDisplayCountry();
}
This can be called from JavaScript like this:
jsonserver.myobject.getMyCountryName(function (r,e) { alert("my country is " + r);});
The HttpServletRequest object associated with the users request will be resolved on the server-side and passed in to the remote method. Likewise you can add HttpSession, HttpServletResponse or JSONRPCBridge to your methods and have them filled in automatically.
The order, position and frequency of local arguments on any given method signature does not matter, they will all be resolved to their respective local arguments when the method is called, and they will all be invisible to the client.
7. Custom Serializers
Every time data is received from the client, or needs to be passed to it, data deserialization (unmarshalling) and serialization (marshalling) takes place. For example, when a method on the server is called, the parameters are serialized into JSON format by the client to be sent to the server. When the data reaches the server, the JSON object is deserialized into a equivalent hierarchy of Java instances that finally will be received by the method to execute. When this method produces a result, the returned object is then serialized into JSON format to be received by the client. Serialization logic is performed by a set of java classes known as JSON Serializers.
The library provides a number of built in Serializers to handle common objects and data structures in a generic way. There are serializers for primitives, Strings, Dates, collections, beans, etc. However, sometimes this is not enough, and additional logic is required to properly transform the objects according to our needs. A couple of common reasons for Custom Serializers are:
- The representation of an object needs to be different on the client and the server. In some cases it is more advantageous to have the bean exposed in a different format on the client than it is represented on the server.
- The actual instantiation of a client object into a Java instance depends on some of the parameters of the object. For example, sometimes beans contain attributes that are polymorphic (i.e. the concrete class to use cannot be determined through the getter and setter method signatures, but from other means, such as one of the bean's fields).
Custom Serializers can be added in a plug-in fashion, to cover such needs.
7.1. Serialization Framework
When raw JSON text is received by the server, the serialization framework first transforms it into the JSONObject generic JSON wrapper instance which can contain a hierarchy of primitive java objects, and other JSONObjects and JSONArray objects, depending on the structure of the JSON. These instances hold the values to be passed through the Serializers that eventually will produce the final equivalent Java objects hierarchy.
In the same manner, when a Java object is serialized to JSON format, the serializers can produce Java primitive objects, JSONObject, and JSONArray instances that the framework will use to produce the JSON text that is sent across the transport to the client. It is the responsibility of the Serializer's unmarshalling mechanism to receive the JSON instances holding the values and produce the equivalent Java object; as well as the responsibility of the marshalling to receive the Java object and produce the equivalent JSON instances.
7.2. Implementing a Serializer
All Custom Serializers must implement the org.jabsorb.serializer.Serializer interface, which contains the following methods:
public void setOwner(JSONSerializer ser);
This method is called automatically by the framework upon registration of the Serializer and is used to set the org.jabsorb.JSONSerializer parent instance to which this Serializer belongs to. The passed value can be stored as an instance field within the Serializer.
Having the JSONSerializer instance accessible inside the Serializer method is a powerful tool. It allow us to call the JSONSerializer generic marshall and unmarshall methods to recursively serialize subsections of objects we are in the process of serializating. within the Custom Serializer's marshalling and unmarshalling logic, thus leveraging part of the task to the other existing Serializers.
public boolean canSerialize(Class clazz, Class jsonClazz);
This method determines if this serializer can perform both, the serialization (from Java to JSON) and deserialization (from JSON to Java), given the target Java class (clazz), and the JSON wrapper class (jsonClazz - the class of the object holding the value to be serialized; usually a Java primitive wrapper, org.json.JSONObject, or org.json.JSONArray). This method is used by the framework to determine which one of the registered Serializers will be applied.
It is important to notice that the second parameter (jsonClazz) might be null (for example when determining the Serializer to use when marshalling, since in that case there is not instance holding a JSON value; that's what the marshalling has to produce). Thus, usually implementations of this method always use the first parameter to determine if the Serializer can be applied, and the second parameter when available and necessary. A typical implementation tries to match the parameters with the classes returned by the methods getSerializableClasses and getJSONClasses respectively.
public Class[] getSerializableClasses();
This method defines the target Java classes that the Serializer can serialize (from Java to JSON) and deserialize (from JSON to Java). Be aware that this information will be used by the framework to associate a serializable class with a particular Serializer, and that there cannot be two Serializers registered that serialize the same class. An exception will be thrown in that case, when registering the Serializer that produces the duplicate.
If your Serializer is meant to be broad and doesn't target a specific class, then this method might as well return an empty array, and leave the decision whether this Serializer can be applied to the canSerialize method. In this case there won't be a direct mapping between serializable classes and the Serializer, and its applicability will depend on two things: 1) the Serializers registered before and after this one, and whether they represent a direct match for the serializable class (according to the classes their getSerializableClasses method returns), and 2) the Serializers registered after this one, and what their canSerialize method returns. More on registering serializers below.
public Class[] getJSONClasses();
Defines the possible JSON classes that this Serializer is able to serialize (from Java to JSON) and deserialize (from JSON to Java). As we have seen, these will typically be primitive class type wrappers, org.json.JSONObject, or org.json.JSONArray. This information, along with that coming from the getSerializableClasses() method, is meant to be used to determine when this Serializer can be applied, through the logic defined by the canSerialize method.
public ObjectMatch tryUnmarshall(SerializerState state, Class clazz,
Object json) throws UnmarshallException;
This function is used only to resolve a dispute when there is more than one potential method that can be called when trying to invoke an overloaded Java method via JSON-RPC. It will receive the target class (clazz - the Java class to which the unmarshalling aims to transform the JSON object), and the Java abstraction of the JSON object (json) which holds the values to unmarshall. The method also receives a SerializeState instance (state), which can be used to maintain the state of the Serializer instance, and pass it around when recursive calls to the JSONSerializer.tryUnmarshall are necessary (i.e. the current Serializer cannot alone unmarshall, or in this case, resolve the umarshalling dispute by itself).
The SerializerState instance allows reading and writing of state in process state information of this Serializer and others in the serialization chain, by getting and setting instances to a common map. There is a single get method, which receives a java.lang.Class. If the passed Class doesn't correspond to an already mapped key then a new instance of that Class will be created (calling the no-argument constructor) and added to the map, using the Class as the key. If a mapping is found, then the stored instance will be returned.
The logic used in tryUnmarshall is almost identical to unmarshall itself, with the primary difference being that the result of the tryUnmarshall is an org.jabsorb.serializer.ObjectMatch instance indicating the maximum number of fields that didn't match in any given Object unmarshalling; whereas the result of unmarshall is the JSON object converted to an equivalent Java object.
The ObjectMatch class has a single constructor that receives an integer representing the number of fields of the serializable class that could not be matched by the Serializer, when trying to unmarshall the JSON object. It is usually only necessary to calculate the number of mismatches when creating some kind of Serializer where the unmarshalling dispute cannot be solved by checking Class type information, and instead it's necessary to compare at the field level.
A couple of constants are provided for specific cases:
ObjectMatch.OKAY - If the unmarshalling can take place.
ObjectMatch.NULL - If the JSON object being unmarshalled is null, and this serializer can/should handle it anyway. If there is another Serializer that returns ObjectMatch.OKAY then that will be used.
It important to mention that throwing UnmarshallException could be used as a mechanism to resolve the dispute. Instead of returning an ObjectMatch instance, throwing an exception will invalidate this Serializer as a possible fit for unmarshalling the JSON object, thus invalidating the current method being tested as a potential match for the call.
The safest implementation for this method is actually trying to unmarshall the json Object, executing that logic and returning the proper ObjectMatch object or throwing an UnmarshallException. However that might imply a bigger performance hit. In some cases it is possible to resolve the conflict by using class type information, accessing the javaClass property in the JSON object, when available. Other possibility is knowing that the canSerialize() method is taking into account the classes returned by getJSONClasses() and being sure the Serializer's tryUnmarshall won't be called for JSON objects that cannot unmarshall, return any random ObjectMatch instance just to comply with the method signature. The latter is only applicable to Primitive wrapper classes, for which we already have default serializers, so this might not be very common/useful when writing a Custom Serializer.
There's also the possibility of assuming this Serializer won't be involved in any overloaded method dispute and return any random ObjectMatch instance, assuming the method will never be called (although obviously that's not very safe).
public Object unmarshall(SerializerState state, Class clazz, Object json)
throws UnmarshallException;
This is the method in charge of deserializing the JSON object (json) that comes from the client into an instance of one of the serializable classes (clazz) defined for this Serializer. The passed parameters are exactly the same as those that the tryUnmarshall would receive if called to resolved an overloading method conflict.
The SerializerState instance (state) has the exact same functionality as in the tryUnmarshall method.
The target class (clazz) could be any of the serialization classes, if provided, or any class the canSerialize method let go through this Serializer. It doesn't mean that's necessarily the class of the instance the unmarshall method has to produce. When some polymorphic behavior is involved, the special javaClass class hint property can be used (if available) to resolve the Class of the instance that has to be produced. The JSON object (json) will be anything the canSerialize method let go through, and the unmarshall should be able to handle it.
public Object marshall(SerializerState state, Object o)
throws MarshallException;
This method is in charge of the serialization of a Java object into a JSON object (in case of primitive values this could be as well a primitive value).
The SerializerState instance (state) has the exact same functionality as in the unmarshall method.
The passed Object is an instance of one of the serializable classes returned by the getSerializableClasses method (if any), or any instance the canSerialize method could have let go through, if the method doesn't return any class. In most cases this method will return one JSON object (org.json.JSONObject, or org.json.JSONArray.); it could also return primitive class type wrappers or direct primitive types, but since the default serializers already handle that, it most likely won't be the case for a custom serializer.
7.3. Extending AbstractSerializer
The library provides a convenience class for implementing Serializers, which provides implementations for setOwner and canSerialize methods.
The setOwner method just stores the JSONSerializer instance passed as a parameter as a class field.
The canSerialize method returns true id the passed clazz parameter to be one of the serializable classes returned by the getSerializableClasses method, and if the second parameter, jsonClazz, is null or is one of the classes returned by getJSONClasses.
8. Registering the Custom Serializer
Custom Serializers are registered by calling the registerSerializer(Serializer s) method on the JSONRPCBridge instance, passing the Serializer instance. Then it will be registered for that bridge.
The order in which Serializers are registered is important, considering how the Serializer registration and resolution mechanisms works:
*Registration.* When a Serializer is registered, through the registerSerializer method, the serializable classes its getSerializableClasses method returns are associated directly to it. Thus, dupplicates are not allowed and an exception will be thrown.
*Resolution.* The resolution mechanism follows two steps:
- # It first checks if the class to serialize/deserialize has a direct Serializer registered for it. If it does then that Serializer is used and the order of registration doesn't matter. # If there's no direct match between a serializable class and a registered Serializer, the framework looks for the appropriate Serializer on the opposite order in which they are registered ( i.e. the most generic one and will be checked at the very last), using the canSerialize method.
Thus, if it is necessary that certain Custom Serializer A to be checked before another Serializer B (following the rule of the most generic checked after the least one), then A should be registered after B.
JSONRPCBridge bridge = new JSONRPCBridge(false);
bridge.registerSerializer( new B() );
bridge.registerSerializer( new A() );
The same applies for existing default serializers: if a Custom Serializer needs to be checked after a default one, then it will be necessary to "re-register" all the default serializers after the point where the custom one needs to be placed.