05798: | ClassNotFoundExceptions when mobile code uses Component Services |
Category: Component Status: ClosedSeverity: MEDIUM Reported against release: 0.9 Fixed in release: 0.9.1
PROBLEM:A user reported the following stack trace in a client program that used
a service:
javax.jms.MessageFormatException:
com.gd.openwings.connector.asynchronous.openjms.SerializableMethod
at org.exolab.jms.message.ObjectMessageImpl.getObject(Unknown
Source)
at
com.gd.openwings.connector.asynchronous.openjms.broadcast.BroadcastConne
ctorSubscriberProxyImpl.onMessag
(BroadcastConnectorSubscriberProxyImpl.java:289)
at org.exolab.jms.client.JmsMessageConsumer.onMessage(Unknown
Source)
at org.exolab.jms.client.JmsSession.execute(Unknown Source)
at org.exolab.jms.client.JmsSession.onMessage(Unknown Source)
at org.exolab.jms.client.rmi.RmiJmsSessionStub.onMessage
(Unknown Source)
at java.lang.reflect.Method.invoke(Native Method)
at sun.rmi.server.UnicastServerRef.dispatch
(UnicastServerRef.java:241)
at sun.rmi.transport.Transport$1.run(Transport.java:152)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:148)
at sun.rmi.transport.tcp.TCPTransport.handleMessages
(TCPTransport.java:465)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run
(TCPTransport.java:706)
Apparently the implementation of the service was a smart proxy that
used Component Services (and an OpenJMS asynchronous connector) to
subscribe to some message interface. These error messages would occur
whenever the smart proxy received a message. ANALYSIS:Quick background: Service implementations can either be the UserProxy
side of an Openwings connector, or any Serializable object that
implements the service interface - aka "smart proxy".
Some users have written smart proxies that implement services mostly or
even completely on the client side. Some of these smart proxies
actually use Component Services to do other service interactions behind
the scenes.
Analysis: the exception shown in the problem description is actually a
JMSException wrapping a ClassNotFoundException (or
NoClassDefFoundError). Here's a few steps that describe what is
happening:
- The service (smart proxy) is discovered by the client. Because of how
Java RMI works, the smart proxy class is actually loaded in its own
URLClassLoader.
- When the smart proxy's connect() method is called by Component
Services, the smart proxy makes a call back in to Component Services -
Component.subscribeService() on some messaging interface.
- Behind the scenes, Component Services distributes the object (just
like calling Component.distributeObject(). This causes the OpenJMS
connector for the messaging interface to be loaded, and the
SubscriberProxy is instantiated.
- When the SubscriberProxy is instantiated, what actually happens is an
RMI exportObject() call (since we're using the RMI-based version of
OpenJMS), which creates a RMI thread that listens on a socket. Because
this call occurs from a thread initiated by Component Services, the RMI
thread has the same context ClassLoader that Component Services (and
the rest of the program has).
- When a message comes in via the JMS connector, the connector attempts
to unmarshal the arguments. However, the context class loader for the
thread is the main program's classloader, and not the smart proxy's
classloader so the classes representing any types on the message must
be on the main program classpath. If a class is not on the main program
classpath, a ClassNotFoundException will be thrown, and this will be
wrapped in a JMSException by the OpenJMS connector.
Note that this problem could also occur if a smart proxy calls
Component.distributeObject() and provides the distributed object as a
service, or passes the distributed object to another program directly.
The suggested solution is to modify the implementation of
Component.distributeObject(). When an object is distributed, it's class
should be mapped to a ClassLoader. If this ClassLoader is not the
context ClassLoader, it should be set as the context ClassLoader
temporarily while the object is being distributed, then the original
context ClassLoader should be replaced. In this way, any threads
created by distributing the object will have the correct context
ClassLoader, since threads inherit their parent thread's context
ClassLoader at creation. |