Table of Contents
This chapter aims to give some suggestion to developers of MX4J, in order to have some common base to start from. It gives also some indication on how to create and distribute a new MX4J release, and the general architecture of the MX4J implementation.
The base to start from are the coding conventions for Java,
see here.
We require as additional convention the one-line one-brace style:
Example 9.1. One-line one-brace style
public class Main { // bad private String name; public Main(String n) { // bad if (n != null) { // bad name = n; } } } public class Main { // good private String name; public Main(String n) { // good if (n != null) { // good name = n; } } }
It is good practice to insert logging statements in the code, to help debugging and to record information about what the program is doing.
MX4J has a built-in logging system based on the mx4j.log.Logger class, whose usage is very similar to the Category class of the Log4J project.
Class Logger has six methods that logs at a different priority; from the lowest priority to the highest they are:
public void trace
(...);
public void debug
(...);
public void info
(...);
public void warn
(...);
public void error
(...);
public void fatal
(...);
Class Logger has another useful method that returns the priority enabled for that instance of Logger:
public boolean isEnabledFor
(...);
Example 9.2. Saving run-time cost of String concatenation
Logger logger = ...; if (logger.isEnabledFor(Logger.TRACE)) { logger.trace("Result is: " + result + " on item: " + item + " for process: " + process); }
Another useful way to avoid creation of temporary String objects is to use the StringBuffer class, following this example:
Example 9.3. Saving run-time cost of String concatenation with StringBuffer
Logger logger = ...; if (logger.isEnabledFor(Logger.TRACE)) { StringBuffer message = new StringBuffer("Result is: ").append(result).append(" on item: ").append(" for process: ").append(process); logger.trace(message); }
How do you obtain an instance of the Logger class ? You must use the mx4j.log.Log class, this way:
Example 9.4. Retrieving a Logger instance
Logger logger = Log.getLogger("MyCategory");
Choosing the right priority is important, and here there are few guidelines:
Use Logger.trace
to log execution flow.
Always surround a log with trace priority with a call
to Logger.isEnabledFor
.
Use Logger.debug
to log variables values.
Always surround a log with debug priority with a call
to Logger.isEnabledFor
.
Use Logger.info
to log information that
can be of interest for the user. For every public method there should
be at least one log with info priority.
Always surround a log with info priority with a call
to Logger.isEnabledFor
.
Use Logger.warn
to log recoverable errors that
in normal situations should not happen. The warn priority is the default priority
for MX4J.
Use Logger.error
to log exceptions.
Typically, log with error priority are inside catch blocks, just before rethrowing:
Example 9.5. Logs with error priority
Logger logger = ...; try { thisMethodThrows(); } catch (Exception x) { logger.error("Exception happened: ", x); throw x; }
Use Logger.fatal
(...); to log fatal errors that
will force the JVM to terminate.
Typically log with fatal priority are inside catch blocks, just before
calling System.exit
:
Example 9.6. Logs with fatal priority
Logger logger = ...; try { Class.forName("java.lang.Object"); } catch (ClassNotFoundException x) { logger.fatal("Can't find core classes", x); System.exit(1); }
System.exit
is never called.
The documentation that comes with MX4J is written using DocBook. It is very easy to use DocBook, and an on-line manual is available here.
Generally, the layout is defined in the file index.xml. All other files are DocBook sections belonging to a chapter, also defined in index.xml.
The following steps are required for a new release of MX4J:
The primary implementation class of the MX4J JMX Agent is mx4j.server.MX4JMBeanServer.
The MBeanServer implementation class accomplishes these roles:
The information about the JMX Agent are returned by several methods such as, for example,
getDefaultDomain()
, and are implemented directly in the
mx4j.server.MX4JMBeanServer class.
The registered MBeans are stored into a repository.
The MBeanServer implementation delegates to implementations of the
mx4j.server.MBeanRepository interface the repository task; therefore the
MBeanServer implementation acts as a factory for MBeanRepository instances,
but the implementation is delegated to MBeanRepository instances.
It is possible to specify custom MBeanRepository implementations by specifying the full qualified name
of the implementation class as value of the system property "mx4j.mbeanserver.repository".
When an MBean is registered several checks must be made to determine the MBean's type, if it is a
compliant MBean or not and to retrieve its MBeanInfo information.
The MBeanServer implementation delegates this task to the mx4j.server.MBeanIntrospector
class.
Objects of this class are first asked to fully introspect the given MBean; after the MBeanIntrospector has
gathered all information about the MBean into an instance of mx4j.server.MBeanMetaData
class, the MBeanServer implementation asks the MBeanIntrospector to check the compliance of the MBean.
If the MBean is a standard one, the MBeanIntrospector must create the MBeanInfo information for it using
introspection on the MBean's management interface, and create the MBean invoker for it, see below.
The role of the MBeanIntrospector is thus to check the MBean compliance, to create the MBeanInfo information
for standard MBeans and to act as a factory for MBean invokers.
The MBeanServer implementation acts as an invoker, on behalf of the user, of operations on the registered
MBeans.
The architecture is interceptor-based, that is whenever you call
from a client an MBeanServer method that will end up to call the MBean instance, the call is dispatched to
the interceptor chain and eventually to the MBean instance.
The interceptors are configurable via the MBeanServer Interceptor Configurator MBean, that is an MBean
of class mx4j.server.MBeanServerInterceptorConfigurator registered under the
"JMImplementation" domain.
When the call is about to arrive to the MBean instance, the last interceptor dispatches the call depending on
the MBean type: if the MBean is a dynamic MBean, the call is dispatched directly using the methods of the
DynamicMBean interface; if the MBean is a standard
MBean, then an MBean invoker is delegated to invoke the operation on the MBean instance.
MBean invokers are implementations of the mx4j.server.MBeanInvoker interface.
There are 2 default MBean invoker implementations: the first is based on reflection to invoke the MBean instance,
the second is based on an on-the-fly generated class that invokes the MBean directly.
This on-the-fly generated class is created at registration time; its bytecode is built at runtime using the
Byte Code Engineering Library.
Both versions make use of TernaryTree as fast caches for MBean information, so that the invocations on
standard MBeans are really FAST.
Early performance benchmarks reveal that the on-the-fly generated version is 2 times faster than the one that use
reflection.
For further information, see the Javadocs of the cited classes.