tomcat-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Christopher Schultz <ch...@christopherschultz.net>
Subject Re: ClassNotFoundException when calling custom MBean operation
Date Wed, 07 Mar 2018 14:41:45 GMT
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Konstantin,

On 3/6/18 8:26 PM, Konstantin Kolinko wrote:
> 2018-03-06 18:43 GMT+03:00 Christopher Schultz
> <chris@christopherschultz.net>:
>> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256
>> 
>> All,
>> 
>> I think I already know the answer to this question, but I'm going
>> to ask anyway in case it helps others understand what's going
>> on.
>> 
>> I have a custom MBean for a loadable-object in my web
>> application. This is an object that lives in the
>> application-scope and needs to be periodically re-loaded from our
>> relational database when we add new data to it.
>> 
>> The bean looks like this:
>> 
>> public static class ReloadableObjectBean implements
>> ReloadableObjectBeanMBean { private ReloadableObject _rlo; 
>> private Date _loadTime;
>> 
>> public ReloadableObjectBean() { _rlo = null; _loadTime = null; }
>> 
>> public ReloadableObjectBean(ReloadableObject dxe) { _loadTime =
>> new Date(); _rlo = dxe; }
>> 
>> @Override public int getItemCount() { return
>> _rlo.getItems().size(); }
>> 
>> protected Date getLoadTimeInternal() { return _loadTime; }
>> 
>> @Override public Date getLoadTime() { return
>> (Date)getLoadTimeInternal().clone(); }
>> 
>> @Override public void reload() throws ReloadableObjectException, 
>> ServiceException { ReloadableObject rlo = 
>> ReloadableObjectHelper.newReloadableObject(); _loadTime = new
>> Date(); _rlo = rlo; } }
>> 
>> This is an inner class defined in one of my
>> ServletContextListeners, and it's inserted into the MBeanServer
>> like this:
>> 
>> ReloadableObject rlo = 
>> ReloadableObjectHelper.newReloadableObject();
>> 
>> try { MBeanServer mbs = getServer(); ObjectName objectName = new 
>> ObjectName("com.chadis:type=ReloadableObject");
>> 
>> if(mbs.isRegistered(objectName)) 
>> mbs.unregisterMBean(objectName);
>> 
>> mbs.registerMBean(new ReloadableObjectBean(rlo), objectName) ; }
>> catch (MBeanException mbe) { logger.warn("Cannot register MBean",
>> mbe); } catch (InstanceAlreadyExistsException iaee) { 
>> logger.warn("Cannot register MBean", iaee); } catch
>> (NotCompliantMBeanException ncme) { logger.warn("Cannot register
>> MBean", ncme); } catch (MalformedObjectNameException mone) { 
>> logger.warn("Cannot register MBean", mone); } catch
>> (InstanceNotFoundException infe) { logger.warn("Cannot
>> de-register MBean", infe); }
>> 
>> application.setAttribute("reloadbleObject", rlo);
>> 
>> Everything goes well until I try to invoke the "reload" operation
>> on this MBean from a JMX client, where I get
>> ClassNotFoundException:
>> 
>> 2018-03-06 10:26:07,271 [RMI TCP Connection(1)-169.254.211.40]
>> FATAL ReloadableObjectHelper- Could not load ReloadableObject: 
>> ServiceException: Cannot obtain database connection at
>> ReloadableObjectHelper.getConnection(ReloadableObjectHelper.java:147
>>
>> 
)
>> at 
>> ReloadableObjectHelper.newDiagnosisEngine(ReloadableObjectHelper.java
:55
>>
>> 
)
>> at
>> InitListener$ReloadableObjectBean.reloadEngine(InitListener.java:159
>>
>> 
)
>> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
>> at 
>> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
jav
>>
>> 
a:62)
>> .... Caused by: Caused by:
>> javax.naming.NoInitialContextException: Cannot instantiate class:
>> org.apache.naming.java.javaURLContextFactory [Root exception is 
>> java.lang.ClassNotFoundException: 
>> org.apache.naming.java.javaURLContextFactory] at 
>> javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:6
74)
>>
>> 
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:31
>> 3) at javax.naming.InitialContext.init(InitialContext.java:244) 
>> at javax.naming.InitialContext.<init>(InitialContext.java:192) at
>> ReloadbleObjectHelper.getConnection(ReloadableObjectHelper.java:136)
>>
>> 
... 39 more
>> Caused by: java.lang.ClassNotFoundException: 
>> org.apache.naming.java.javaURLContextFactory at
>> java.net.URLClassLoader.findClass(URLClassLoader.java:381) at
>> java.lang.ClassLoader.loadClass(ClassLoader.java:424) at
>> sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at
>> java.lang.ClassLoader.loadClass(ClassLoader.java:357) at
>> java.lang.Class.forName0(Native Method) at
>> java.lang.Class.forName(Class.java:348) at 
>> com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.jav
a:7
>>
>> 
2)
>> at 
>> com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.jav
a:6
>>
>> 
1)
>> at 
>> javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:6
72)
>>
>> 
... 43 more
>> 
>> 
>> The error I get on VisualVM (my JMX client, here) is that it
>> can't load my ServiceException class -- one that is defined only
>> within the web application.
>> 
>> I suspect the problem is that the thread's context class loader
>> (TCCL) is set to Tomcat's ClassLoader, since the request is being
>> handled by Tomcat's internal JMX server. If I were to invoke this
>> operation via the Manager's JMXProxyServlet, I'd probably be
>> dealing with the Manager's WebappClassLoader, instead, but the
>> problem would be the same: those ClassLoaders are unaware of my
>> application's classes.
>> 
>> I *believe* the solution is to change the TCCL in this "reload" 
>> method, but that means I'll need to capture the TCCL during the 
>> invocation of the MBean itself and hang on to it... something
>> like this:
>> 
>> ClassLoader originalCL = 
>> Thread.currentThread().getContextClassLoader(); ReloadableObject
>> rlo = ReloadableObjectHelper.newReloadableObject();
>> 
>> try { MBeanServer mbs = getServer(); ObjectName objectName = new 
>> ObjectName("com.chadis:type=ReloadableObject");
>> 
>> if(mbs.isRegistered(objectName)) 
>> mbs.unregisterMBean(objectName);
>> 
>> mbs.registerMBean(new ReloadableObjectBean(rlo, originalCL),
>> objectName); }
>> 
>> Then, later, in the reload() method:
>> 
>> 
>> @Override public void reload() throws ReloadableObjectException, 
>> ServiceException { ClassLoader tccl = 
>> Thread.currentThread().getContextClassLoader();
>> 
>> try { Thread.currentThread().setContextClassLoader(originalCL) ;
>> 
>> ReloadableObject rlo = 
>> ReloadableObjectHelper.newReloadableObject(); _loadTime = new
>> Date(); _rlo = rlo; } finally { 
>> Thread.currentThread().setContextClassLoader(tccl); } }
>> 
>> Does that sound about right?
>> 
>> I'll probably want to future-proof it by wrapping those calls
>> into PrivilegedActions, etc. but is this the right approach for
>> what I'm trying to do, here? Or am I missing something?
>> 
> 
> From those error messages, it looks like your code does a JNDI
> lookup.

Yes.

> And the lookup fails with a NamingException (a 
> javax.naming.NoInitialContextException to be specific, but that is 
> just a detail).
> 
> A JNDI tree is local to a web application. Each web application has
> an independent JNDI tree. Selecting the correct one uses TCCL as
> the key.

That's what I thought. So, I just have to play with the TCCL.

> So if you TCCL class loader is not set correctly, you won't be able
> to access JNDI.
> 
> If you are interested in implementation details, see 
> org.apache.naming.SelectorContext 
> org.apache.naming.ContextBindings 
> org.apache.naming.java.javaURLContextFactory
> 
> 
> On the question on what is the best way to change and restore
> TCCL: Current code uses a helper API method, StandardContext.bind()
> / unbind().

This bean is being defined by my application and loaded from within
it. I'd prefer not to have a compile-time dependency on Tomcat itself.

- -chris
-----BEGIN PGP SIGNATURE-----
Comment: GPGTools - http://gpgtools.org
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iQIzBAEBCAAdFiEEMmKgYcQvxMe7tcJcHPApP6U8pFgFAlqf+ikACgkQHPApP6U8
pFgV7xAAmeQIBYhVlMvsjACJT2NuuxPTe7pdUuiczGe/5GSIpJ/gqfLMRu4nwzns
2IG0uTiC6vH4q3PIvbuNa0VWmy9q0+hF9lISQSj4Pt8bb6PICfqW+6gJPt2IKHDx
Rt6cWwJZQy53Nn6UGRY8eIzr/oZXkmSg0wEOq/rcqBSLUPk7SGnxBd2gW9Fy6KYz
gzqB/esgsMRZqw++DH4AKDoGkm1nD7z1qUTLvDlqzKwupEs8M+XA6F+QkBGCo6PP
sc2tghn2P0yuOrcrNdE9Typr5UMcHerDzT3KtZ1rHi36RMTLWoWza7/w2xC+VIJi
Ofmezhi8PlLhFtiw13T1NFY+gHs23eLpJ7P9v/Vx8zpUVqIje8y7dm/RjikgKCF7
L+IMz6yQv3QRMVYgsW7+3HYw8tTEqlkdl2H3HyU+ac6jUButUqvsVlTPNRssNwRq
xTAwjAUa2HDLIxq0wtpds76VzmWSgBWSmCby8NDCJuHR6fQtGamjR/yxeufyXoKy
+lZla6U4kPuBdo6iNqwSlIluvJyvVpCdkNh1R89bew9QyqK1bSlGg0tLVUXlVeK/
XBQWgOqFB6Ge2ulbTAVto0clKaHu2/93nIyGMz3UnO0xRMOgqoAs8PXYOcGozV5D
ouFCeDo5e3bovL3Obdg49CtLZNve7gEwSuPpakJuvDBhQ1+/J74=
=gg2l
-----END PGP SIGNATURE-----

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Mime
View raw message