I've written a custom MBean that deploys in Tomcat 6. One of its tasks is to query a database value. I'm doing this by loading up the database resource using JNDI - the resource is defined in Tomcat's server.xml.
The problem is that when I create an instance of javax.naming.InitialContext
it throws a ClassNotFoundException
as it can't find org.apache.naming.java.javaURLContextFactory
.
This class is in catalina.jar
and loaded by the common classloader. The jar containing my MBean code is loaded by a shared classloader.
Any ideas as to how I can get around this?
To note: my MBean is loaded by a ContextListener which I've defined in the tomcat/conf/web.xml
. I've also defined it in a webapp web.xml
which makes no difference. I can't really move my jar so as to be loaded by the common classloader as it relies on classes loaded by the shared classloader.
Thanks in advance,
Will
It looks a weird classloading or security/permission issue. Below is a workaround.
The main idea: Since the ServletContextListener
could call the new InitialContext()
without the ClassNotFoundException
get it in the listener and pass it to the constructor of the MBean object before you register the MBean. I used a simple web application and I have not modified tomcat/conf/web.xml
.
Resource configuration in the tomcat/conf/context.xml
:
<Context>
...
<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="root" password="..." driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/javatest?autoReconnect=true"/>
...
<Context>
The web.xml
resource configuration:
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/TestDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
The ServletContextListener
which registers the MBean:
import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class ContextListener implements ServletContextListener {
private ObjectName objectName;
public void contextInitialized(final ServletContextEvent sce) {
System.out.println("---> bean context listener started");
final MBeanServer mbeanServer =
ManagementFactory.getPlatformMBeanServer();
try {
final InitialContext initialContext = new InitialContext();
final Context envContext =
(Context) initialContext.lookup("java:/comp/env");
objectName = new ObjectName("com.example:type=Hello");
final Hello helloMbean = new Hello(envContext);
mbeanServer.registerMBean(helloMbean, objectName);
System.out.println("---> registerMBean ok");
} catch (final Exception e) {
e.printStackTrace();
}
}
public void contextDestroyed(final ServletContextEvent sce) {
System.out.println("---> bean context listener destroyed");
final MBeanServer mbeanServer =
ManagementFactory.getPlatformMBeanServer();
try {
mbeanServer.unregisterMBean(objectName);
System.out.println("---> unregisterMBean ok");
} catch (final Exception e) {
e.printStackTrace();
}
}
}
MBean interface:
public interface HelloMBean {
void sayHello();
}
MBean implementation:
import java.sql.Connection;
import javax.naming.Context;
import javax.sql.DataSource;
public class Hello implements HelloMBean {
private final Context envContext;
public Hello(final Context envContext) {
this.envContext = envContext;
System.out.println("new hello " + envContext);
}
@Override
public void sayHello() {
System.out.println("sayHello()");
try {
final DataSource ds =
(DataSource) envContext.lookup("jdbc/TestDB");
final Connection conn = ds.getConnection();
System.out.println(" conn: " + conn);
// more JDBC code
} catch (final Exception e) {
e.printStackTrace();
}
}
}
The MBean Descriptor How To says a mbeans-descriptor.xml
is required but it's worked without it well. I could connect to the HelloMBean with jconsole
. Calling sayHello()
through jconsole
printed the following:
conn: jdbc:mysql://localhost:3306/javatest?autoReconnect=true, \
UserName=root@localhost, MySQL-AB JDBC Driver
Sources: