There are several vendors that enable interoperability between .NET Framework applications and Java applications with .NET Remoting as the underlying connecting protocol. Runtime bridges expose Java objects and methods in a way that enables you to address them with .NET Remoting and vice versa. Two of the main products in this category are Ja.NET from Intrinsyc and JNBridgePro from JNBridge, Inc.
Ja.NET and JNBridgePro differ in that JNBridgePro allows calls from a .NET Remoting client to Java objects and classes whereas Ja.NET also works in the opposite direction. This difference does not affect the scenarios that this book covers.
Ja.NET provides a two-way implementation of the .NET Remoting stack for Java. Using Ja.NET, you can generate Java proxies that expose or consume components using the .NET Remoting protocol.
Because Ja.NET is a bi-directional bridge, accessing Java from .NET generates a set of C# proxies. Similarly, accessing .NET from Java generates a set of Java proxies.
If you examine the generated proxies, you should notice how the by value class contains all its fields, and the remote by reference class only a shell that defines the class definitions. Ja.NET needs these for compile definitions, and then again at run time for the _TransparentProxy class to mimic the remote server. If you attempt to create a local copy of this class, this generates an exception. You can only have a remote instance of this class. The proxies use no custom code, just pure .NET Remoting.
If you look at the Java proxies generated when analyzing a .NET assembly, you should see a lot of code for marshalling and un-marshalling the call parameters to the Ja.NET runtime. When accessing .NET from Java, you need custom code to initialize the Ja.NET runtime, as well as any remote send or receive classes. However, the .NET side still does not require any special code, as Ja.NET follows the .NET rules for remotable objects.
Ja.NET enables Java components to appear as CLR components, and CLR components appear as Java components. In addition, the Ja.NET Janetor tool expands upon this functionality by generating the Java proxies within a WAR file. You can then host this WAR file on a Web server, enabling access to EJBs from .NET Framework — again with all of the communication based on .NET Remoting.
The simplest implementation is when the Ja.NET runtime lets a Java client access a .NET Framework server, in this case, implemented in Visual Basic .NET. Figure 4.2 shows how to do this.
Figure 4.2: Connecting Ja.NET to a VB.NET Server component
Figure 4.2 shows the Java platform hosting the Ja.NET runtime component.
Java clients can also use the Ja.NET runtime to access a remote CLR component as if it is a local Java component. Figure 4.3 illustrates this method.
Figure 4.3: Connecting Ja.NET to a .NET component hosted on IIS
Figure 4.3 shows the Ja.NET proxies that handle communication between the Java client and the Ja.NET runtime. In this example, IIS is hosting the .NET Framework application.
The third method deploys a WAR file containing the Ja.NET runtime onto any Web server that supports servlets. The CLR client (written in any supported .NET Framework language) can access the EJBs as if accessing local CLR components. Figure 4.4 shows an example of this arrangement.
Figure 4.4: Connecting .NET Clients to an EJB using Ja.NET
In addition, the Ja.NET runtime lets you:
Write clients for EJBs in any language supported by .NET.
Access .NET Framework components from any Java object or EJB.
Reuse components from either platform with the other environment.
Because .NET Remoting is an extensible protocol from both the transport and data formatting perspective, Ja.NET supports HTTP and TCP/IP transport protocols together with SOAP and binary data formatting and can support future transports and formatters.
There are six main parts that make up the Ja.NET toolset. These are:
GenService — The GenJava and GenNet tools use GenService to provide access to .NET Framework assemblies for development. You require only GenService to generate the proxies, so you do not have to install it in the production environment.
GenNet — GenNet generates the .NET Framework proxies that access the Java classes through the Ja.NET runtime.
GenJava — GetJava generates Java proxies that access .NET Framework assemblies through the Ja.NET runtime.
Janetor — The Janetor tool allows you to view and modify the Ja.NET runtime configuration settings. You can also use the Janetor to generate WAR files to assist with deploying the Ja.NET runtime onto a Web server. Janetor includes licensing for the Ja.NET product, locally shared and remotely accessed objects.
Ja.NET TCP server — Ja.NET TCP server provides standalone hosting for Java classes through the Ja.NET runtime where the classes are not hosted on a J2EE server. The Ja.NET runtime includes this component.
Ja.NET runtime — Ja.NET runtime is the main collection of classes that hosts the tools. The Janet.jar file contains the runtime components.
When linking .NET Framework to Java using Ja.NET, you need to understand the links between data types in the .NET Framework and data types in Java. Table 4.1 shows this information.
|.NET Framework Data Type||Java Data Type|
Table 4.2 shows how you can use Ja.NET to map collections between Java and the .NET Framework.
|Java Collection Class||.NET Framework Collection Class|
Table 4.3 shows the opposite mapping from the .NET Framework to Java.
|.NET Framework Collection Class||Java Collection Class|
For more information about type and collection mappings, see the Ja.NET documentation.
There are a few more concepts that you need to understand to appreciate the complete range of .NET Remoting options. These include the following:
Events — Ja.NET provides event support through the ability to extend java.util.EventListener to system events in the .NET Framework. This provides a major advantage in that the server component can now implement a callback to the client through this event model. Events are particularly useful where you want to implement asynchronous operations or transactions with long run times.
Exception handling — Ja.NET provides exception handling facilities, reporting back exceptions on one platform to the alternative platform. Java server exceptions map back to the System.Runtime.Remoting.RemotingException function in .NET Framework and exceptions on .NET Framework map back to com.instrinsyc.janet.RemoteException.
Usually, these derived exceptions contain the text from the original. To deal with these exceptions, trap the resultant exception and then search the text within the embedded exception to obtain more information.
Support for strong naming — Ja.NET supports the use and creation of strong-named assemblies. You can then register these assemblies in the global assembly cache for use with serviced components (COM+).
Ease of configuration — Because Ja.NET is a pure Java implementation of the .NET Remoting protocol, the .NET Framework side requires no special runtime libraries. Configuration on the .NET Framework side is the same as with a normal implementation of .NET Remoting, requiring only a simple DLL that contains class and interface definitions. Anything that you can make remotable in the .NET Framework (derived from MarshallByRefObject), you can also make remotable in Ja.NET. You can also send serialized classes by value, as in .NET Framework-only implementation.
There are several best practices that you should implement when using Ja.NET. These issues include performance, design, and deployment factors. For example:
Upgrade to version 1.5.
Use binary protocol for .NET Remoting.
Deploy WAR packages on the application server to access EJBs and JMS.
Understand .NET Remoting to facilitate good design practice.
Comprehend the differences between pass by reference (PBR) and pass by value (PBV).
You are strongly recommended to upgrade to Ja.NET version 1.5, which is free for users of earlier versions of Ja.NET. Version 1.5 contains many important new features, including some that are necessary to run the examples in this book. Some of the new features include the following:
Support for strong-named assemblies.
Improved type mappings between .NET Framework and Java types.
Significant performance increases.
Improved serialization capabilities for pass by value classes for both platforms.
Integral Web server for non-enterprise level remoting access with HTTP.
Runtime support for .NET Framework versions 1.0 and 1.1.
Integral .NET Framework proxy for JMS.
Stability and performance enhancements.
This chapter describes the benefits of using .NET Remoting rather than SOAP. Although Ja.NET also supports SOAP connections, .NET Remoting provides better performance, with TCP/binary providing the fastest connection. However, if you need to traverse firewalls, want to use the security infrastructure of IIS, or easily deploy components inside an application server, HTTP/binary is the preferred protocol.
HTTP/binary is still significantly faster than HTTP/SOAP.
The fastest way to access an EJB is from within the application server itself. The easiest deployment scenario is to create a WAR file that contains the Ja.NET runtime JAR, the EJB client JARs, and a configuration file. You can use the Ja.NET Janetor tool to create the WAR file easily, which you can then immediately deploy to the application server. Because the WAR file resides in the same process space as the application, access to the EJBs is much faster than accessing it from an external process with RMI.
Because Ja.NET is a pure Java implementation of the .NET Remoting protocol, the same rules for .NET Remoting best practice apply to Ja.NET. Make sure you read the current literature about .NET Remoting to understand how best to employ it in conjunction with Ja.NET.
In a PBR class, every method call results in a request for data. However, if you send a PBV class, this serializes the entire class, including fields of the super class or classes. If you PBV a very large object with numerous fields that contain significant amounts of data, this may generate a sizeable about of network traffic. In this case, it may be better to use PBR. Similarly, if you have a small class, consider using PBV to send a copy of the class, rather than making a call to the network to access each field. Understanding the differences between PBR and PBV reduces network traffic and improves performance.
JNBridgePro is a point-to-point bridging solution that links Java components to .NET Framework. The Java code runs in an ordinary Java Virtual Machine (JVM) or J2EE application server and the .NET Framework code runs in a normal .NET Framework CLR. JNBridgePro then manages the communication between the two sides.
Proxies make Java classes and objects appear as ordinary .NET Framework classes and objects. .NET Framework components interact with those proxies just like normal local .NET Framework function calls, but the proxy objects transparently redirect the calls to the Java side. The Java components process the method calls and field accesses and return the values or data to the proxies. The proxies then present the results back to the .NET Framework components.
An application using JNBridgePro to perform Java/.NET Framework interoperation contains JNBridgePro’s .NET Framework-side and Java-side runtime components, which manage inter-platform communication and object lifecycles, and a .NET Framework assembly (a DLL) containing the proxies for the Java classes exposed to the .NET Framework. Developers generate the proxies using the JNBridgePro toolkit.
Figure 4.5 shows the architecture of JNBridgePro.
Figure 4.5: Internal architecture of JNBridgePro Runtime Bridge
Figure 4.5 shows the runtime component and proxies on the .NET Framework side. Compare this with Figure 4.3.
JNBridgePro employs .NET Remoting to implement the communication between the .NET Framework and Java sides. Because .NET Remoting is an extensible architecture, you can change communications channels as required. JNBridgePro uses this extensible architecture to support both SOAP and binary communications.
.NET Remoting also includes concepts alien to conventional “local” object-oriented development, such as leasing, well-known objects, and client-activated and server-activated objects. JNBridgePro can encapsulate these concepts and conceal them from the user. Hence JNBridgePro interoperation between Java and .NET Framework code appears to the developer as local development.
JNBridgePro builds additional capabilities on top of .NET Remoting. For example, basic .NET Remoting can access members of actual objects, but it cannot access static members of classes that are not members of actual instances of the class. JNBridgePro provides the ability to access those static class members.
JNBridgePro allows access to Java classes and objects through calls to the corresponding JNBridgePro proxies. JNBridgePro supports access to both static and instance members, in addition to supporting access requests to fields and calls to methods. You can make explicit calls from .NET Framework to Java by having the .NET Framework code call .NET Framework proxies for Java classes and objects, and you can make implicit calls from Java to .NET Framework by having the .NET Framework code register a callback object and having the Java code access the callback object through its implemented listener interface.
The Java side components can reside in a standalone JVM or in a J2EE application server. Communication between the .NET Framework and Java sides can use either SOAP or the faster binary protocol.
JNBridgePro supports transactions, giving you the ability to make a sequence of calls from .NET Framework to Java as part of the same transaction. If the transaction fails, the entire sequence rolls back to the start point. This prevents application calls from hanging between the .NET Framework and the Java side. JNBridgePro can also create .NET Framework proxies for dynamically generated Java classes at execution time. Such dynamically generated classes frequently appear in J2EE applications, particularly when linking to JNDI and EJB components.
JNBridgePro also supports the following:
Mapping between .NET Framework and Java primitives.
Support for pass by value (PBV) and pass by reference (PBR).
Connections to Java-based messaging servers.
Choose JNBridgePro for J2EE and .NET Framework interoperability if your application requires synchronous, point-to-point communication between .NET Framework and Java objects, and has one or more of the following requirements:
High performance — Your application requires a high performance interoperability mechanism with a very low overhead, where the Java platform and the .NET Framework are located on the same local area network or intranet, or on the same machine.
Expose a rich object-oriented Java API to .NET Framework interface — Your application requires that Java exposes a large number of Java class APIs to .NET Framework applications, or the Java API returns custom Java objects or requires custom Java objects as parameters.
“Chatty” interaction — Your application performs frequent fine-grained calls between Java and the .NET Framework. Web services work better with less frequent and more coarse-grained “chunky” interaction.
Desire to stay with “local,” object-oriented development model — Your application architects decide that they will not implement a service-oriented model that exposes the APIs of a few classes as services. Instead, they want the .NET Framework code to construct new Java objects using the new operator, employ cross-platform garbage collection, and provide a conventional “local” object-oriented model.
There are a number of best practices that you should implement with JNBridgePro, regardless of the interoperability scenario. Performance-related best practices include increasing the speed of the communications channel, or reducing the number of inter platform round trips, thereby improving the apparent execution speed of the application.
If you are using JNBridgePro version 1.2 or earlier, you should consider upgrading to version 1.3, particularly if your .NET Framework and Java components reside on different computers. Version 1.3 or later provides a significant performance enhancement when using binary communications to connect over a network. Some implementations demonstrate a reduction in the time to access an EJB from 200 milliseconds to 2.3 milliseconds.
In addition to the network communications performance improvement, version 1.3 supports value objects and directly mapped collections, both of which can improve performance.
To take advantage of this improvement, download and install version 1.3, and then regenerate your proxies using the version 1.3 proxy generation tool. Replace the old copies of Jnbshare.dll and Jnbcore.jar in your application with the version 1.3 files.
Binary/TCP communication is over an order of magnitude faster than SOAP/HTTP. Use SOAP/HTTP only when using the Internet to link the .NET Framework and Java sides and the communication path traverses firewalls.
To use the binary/TCP communications channel, make sure that the setting in the Jnbproxy.config remoting configuration file uses the JTCP: protocol instead of the HTTP: protocol. Also, set the servertype property in the Java-side configuration file Jnbcore.properties to TCP, not HTTP.
If you are using JNBridgePro to access EJBs running inside a J2EE application server, deploy the JNBridgePro Java side inside the application server. These components comprise the WAR file containing Jnbcore.jar, the JAR files containing the EJB stubs, and the associated configuration files. Figure 4.6 shows this in place.
Figure 4.6: Java-side component in J2EE application server
This arrangement ensures that Jnbcore.jar, which performs the EJB calls, bypasses RMI and makes direct calls to the EJBs.
If a single .NET Framework client accesses the Java classes, place the Java-side component, including the Java classes, on the same computer as the .NET Framework-side component, if possible. This minimizes the communication overhead. Figure 4.7 shows this in operation.
Figure 4.7: Java-side component on the computer running the .NET Framework component
Any increase in round trip time and latency for the communication link affects the overall responsiveness of the platform interface.
One of the best ways to improve JNBridgePro performance is to reduce the number of round trips from the .NET Framework side to the Java side and back by reducing the number of calls to proxies. One example of how to do this is when a number of proxy calls are necessary to obtain information from which another value is calculated. To reduce the number of proxy calls, create a Java fa ade class that performs the calls, does the calculation, and returns the value. Then, create a proxy for this class. The value can now be obtained through a single proxy call.
JNBridgePro offers full access to Java APIs, so there is a great temptation to use the full API functionality. For example, if you have a Java-side Vector object, you should extract an array from the vector and return that array by value. .NET Framework then represents that array natively, removing the need for iterations to extract further values from the original Vector object.
Some Java APIs support stateful objects that you may want to call repeatedly to obtain additional information. The JDBC class ResultSet, for example, represents the results of a query and can contain multiple rows through which you have to scroll. Again, this can result in multiple round trip calls to ResultSet.
To improve performance, create a Java wrapper class that returns an array of objects with each containing one row of results. If there is a chance that the results array is very large, and that returning its entire contents will take a long time, modify the wrapper class to return a limited number of results at a time (for example, no more than 50).
The default setting for object calls from .NET Framework to Java are as pass by reference (PBR) results. The called object remains on the Java side, and only the reference to that object returns to .NET Framework. References are much smaller than the actual object, helping performance, but they involve a trip back to the Java side to extract any useful information. Even getting a field value requires a round trip to obtain the data. If you are performing multiple queries on an the object’s fields for data or using accessor methods to look up data on an EJB, you would be better to designate objects of that class as value objects.
A value object is a snapshot of an object from the Java side then copied back to the .NET Framework side. Depending on the kind of value object it is, this copies either the values of its public fields, or in the case of a Java Bean value object, the values of its accessor (get) methods. This process does not copy the object’s methods other than get methods for a Java Bean value object, because it is problematic to translate the meanings of Java methods to .NET Framework automatically.
Consider using value objects when the object is really just a large package of data that you want to access in many different ways. For example, an object representing a customer’s bank account might have fields for the customer’s account number, first name, last name, address, current balance, previous balances, and the date the owner opened the account.
The default setting is that the account object userAcct passes by reference, resulting in each field access in userAcct generating a round trip. However, if you set up Account to pass by value, this copies the data in userAcct from the Java side to the .NET Framework side and each subsequent field access is a local call.
Whether it is advisable to pass an object by value or by reference depends on the size of the object and how much of its data you want to access. For example, in the bank account example, if the only data you access in the account is the current balance, it is probably not worthwhile to pass this object by value. In this case, the time taken to copy all the data to the .NET Framework side outweighs the time savings that result by making all field accesses local to .NET Framework.
The code is the same regardless of whether the object is passed by reference or by value; the only difference is whether the object’s class is designated as a value object or a reference object. Ideally, the decision on whether to pass the object by value or by reference depends on observed measurements within the pilot environment rather than the developer’s judgment.
Individual objects cannot be designated as pass by reference or pass by value; all objects of a given class have the designation.
For more information about value objects and reference objects in JNBridgePro, see the JNBridgePro Users’ Guide.
Directly mapped collections return certain object collections from the Java side and automatically convert them to native .NET Framework collections on the Java side. You can then access these elements quickly after conversion without a round trip.
JNBridgePro supports a variety of directly mapped collections. Java Vectors, array lists, linked lists, and hash sets map directly to .NET Framework array lists. Java hash tables and hash maps map to .NET Framework hash tables. You can also use directly mapped collections to pass parameters from .NET Framework to Java. For more information, see the JNBridge Users’ Guide.
As with value objects, directly mapped collections take longer to pass between Java and .NET Framework than reference objects, but you can then access their elements faster. Deciding whether to use a directly mapped collection depends on the size of the collections being transferred, the number and frequency of accesses, and tests in the pilot environment.
Each exposed class results in the generation of a proxy of the same name. The generated proxy’s members (including constructors, methods, and fields) correspond to the members of the Java class underlying the proxy.
When generating proxies, generate all the supporting proxies for each specifically requested proxy class. It is often possible to omit generating supporting proxies (such as proxies for the classes of all parameters, return values, thrown exceptions, implemented interfaces, and superclasses); however, it is never harmful to generate such supporting classes and it may be harmful to leave them out. In addition, creating proxies for supporting classes ensures that you can call methods and access fields requiring proxies for those additional classes without having to regenerate the proxy assemblies.
An additional problem with not generating all supporting proxies is that in some situations, applications throw an exception if you do not do so.
Depending on the side of the original set of seed proxies, generating all supporting proxies adds about 200–300 additional proxy classes to the original set. Typically this takes no more than five minutes to generate the additional proxies.
With care, it is possible, in many cases, to avoid generating supporting classes. However, it is never harmful to generate such classes, and doing so may avoid various issues in the future, even when the lack of supporting classes does not represent a problem now.