0

I'm monitoring, via JMX, a servlet running in Tomcat with another servlet running in the same instance of Tomcat. When I setup the get()s to return standard Java datatypes( String, int, byte[], etc. ), this works fine. But when I use a user-defined class, I get a ClassCastException which gives this message:

java.lang.ClassCastException: blah.My_UserDefinedClass cannot be cast to blah.My_UserDefinedClass

I'm fairly sure this is because of the different class loaders at the instrumentation and at the management layer ( the monitored servlet and the monitoring servlet, respectively ). I've double-checked the .jar file that contains the user-defined class for each servlet and both jar files are identical to each other.

I'm using standard MBeans, and have set up the monitored servlet to return this attribute:

public interface MyMonitorMBean
{
    public My_UserDefinedClass getAllData();
}

implementation:

public class MyMonitor implements MyMonitorMBean
{
    private My_UserDefinedClass mAllData;

    @Override
    public My_UserDefinedClass getAllData()
    {
        return mAllData;
    }   
}

Code in management servlet to access this data:

private void getAllDataFromMBean()
{
    try
    {
        // this line generates the ClassCastException
        My_UserDefinedClass allData = (My_UserDefinedClass)mMBS.getAttribute( mObjectName, "AllData" );
    }
    catch( Exception e )
    {
    }
}

While I could generate multiple get()s with each sending a standard Java datatype, I would like to build/use my own POD/POJO class that encapsulates all of the various standard bits and bobs ( which are standard Java datatypes ) that I want to send back so that I can get my data in a single call.

Any thoughts?

Thanks,

Bill

Using tomcat7, java6, windows xp, 32bit

joe7pak
  • 300
  • 1
  • 4
  • 16
  • Well, here's a possible solution: instead of calling MBS.getAttribute(), call MBS.getAttributes() which should return *all* attributes with their values. Still can't simplify the MBean classes, but can simplify somewhat at the management layer. – joe7pak Mar 16 '12 at 22:16

1 Answers1

0

This may work for you. You need to set the context class loader of the thread [temporarily] to the class loader of the MBean. You can determine this by requesting the class loader instance from the MBeanServer.

private void getAllDataFromMBean()
{
    try
    {
        final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader mbeanClassLoader = mMBS.getClassLoaderFor(mObjectName);
            Thread.currentThread().setContextClassLoader(mbeanClassLoader);
            My_UserDefinedClass allData = (My_UserDefinedClass)mMBS.getAttribute( mObjectName, "AllData" );
            // Process allData here
        } finally {
            Thread.currentThread().setContextClassLoader(originalClassLoader);
        }
    }
    catch( Exception e )
    {
    }
}

==== UPDATE ====

There is also a closely related question more comprehensively answered here: ClassCastException when casting to the same class.

==== UPDATE ====

I am not totally clear on the exact setup you have, but one way or the other, you definitely have a classloader issue where, at runtime, you have [at least] two different classloaders that have loaded My_UserDefinedClass.

These are some options:

Lifecycle

Make sure that the class defining getAllDataFromMBean is classloaded in the same classloader so there is only one classloader. (Might be tricky)

Alternatively, put the jar containing the class into the system classpath before it is ever loaded. (i.e. the boot classpath, or system classpath)

Reflection

There is a core issue with classloaders here because your getAllDataFromMBean method is in a class which will have triggered a load of My_UserDefinedClass as soon as it was loaded, so right off the bat, you have two different classloaders and therefore incompatible types. Accordingly, since the underlying values are all standard java types, you could access the instance reflectively.

** Generification **

Rather than creating a user defined class for your values, keep them in a name/value map. That way, all the data being passed is in core java types.

** Externalization **

If you force the user defined instance to be serialized (say to a byte[]) and then deserialize it on the reading end, you will bypass this problem.

** Open Types **

This is the solution I think is the best. Define your type as a CompositeData. That way, there is no custom class dependency at all. This has added advantages like the fact that you can display the values in a remote JMX client (like JConsole etc.) that does not even have the custom class in it's classpath.

//Nicholas

Community
  • 1
  • 1
Nicholas
  • 15,916
  • 4
  • 42
  • 66
  • Thanks, this looked like it might be the answer, but, alas, I get the exact same results. I'm really surprised by this apparent limitation to JMX that only standard java classes can be passed around. I suppose this is why the examples I saw showed only ints and Strings and *never* a user defined class, and there was *never* a discussion about user defined classes in the JMX context. – joe7pak Mar 18 '12 at 16:06
  • Another thought about this answer. I suspect that I need the class loader from the monitored servlet instead of from the MBeanServer. The problem is, I'm trying to not include references to the monitored servlet into the monitoring servlet ... this is so I can add more monitored servlets without having to edit the monitoring servlet for each new servlet being monitored. – joe7pak Mar 19 '12 at 13:16
  • Okay, I think we have a winner here. – joe7pak Mar 21 '12 at 12:49
  • Okay, I think we have a winner here. The name/value map sounded like a good idea, but I didn't do it, I still wanted to do it with my class. As far as manual serialization/deserialization, yes, that too may have worked, but I considered it too much work with too many complications. The winner is to use MXBeans not because of their support of OpenTypes ( I don't need that feature ), but because of their support of 'CompositeData', which is a way to simulate accessing a user-defined class. This accessing of a user-defined class was my goal, and the MXBeans did it cleanly & simply. Thx Nicholas! – joe7pak Mar 21 '12 at 12:56