Introduction

beSee architecture consists of several parts which are presented here. You can read this document to understand how beSee enhances your application.

  1. Dedicated class loading with dynamic proxy
  2. Class PreProcessor
  3. Chained PreProcessor
  4. Lazy JMX registration
  5. Flow depth support

Dedicated class loading

beSee runs with JDK 1.3 (as WebLogic Server do), and uses two third party API:

  • Jakarta regexp for regular expression handling in configuration phase (official site)
  • Jakarta Log4J both for internal logging and instrumentation logging (official site)

In this part you will learn how beSee uses state of the art java (dynamic proxy, reflection) to enhance your applications on demand without interfering with the versions of software you are already using.

Why an advanced class loading mechanism is needed ?

As beSee (besee.jar) is in the application classpath (J2EE application server or J2SE application), the third party API needed by beSee like log4j should - standard assumption - be loaded by the main class loader.

Due to the deleagtion model of class loader, if your J2EE application itself uses log4j (for example), it will use the log4j beSee uses (from the classpath), which ends in "beSee forces me to use log4j version X and interferes with my application which was using log4j version Y" and is not what you need.

Off course it is not how beSee works. beSee does not interferes with the third party API you are already using in your deployed application or in your classpath that it has in common.

How beSee works ?

beSee has a special directory ( see besee.runtime configuration directive ) where a beSee dedicated special class loader looks for classes. This class loader is visible only from beSee and class loaded this way are not directly visible from your application.

The log4j.jar beSee uses is dropped in this directory. Your app server can have another log4j.jar in the classpath or your application can have another log4j.jar bundled in it (ejb-jar or ear).

At runtime, when a class (beSee internal or insrumented class from your application) calls

com.gnilux.besee.log.Logger log = com.gnilux.besee.log.LoggerFactory.getLogger("besee.struts");
...
log.debug("the method yy took xx seconds to complete");
				
the log object is in fact a Java dynamic proxy on a log4j Logger object loaded by the beSee dedicated class loader, and all calls on log will be forwarded to the real object this proxy targets.

dedicated class loader to avoid third party API conflicts

The same mechanism (but this time using simple java reflection) is used for regular expression isolation.

Want to know more ?

Just remind that beSee will never interferes with your application and your own running environment, but will still provides you with an efficient way to instrument them using rich API like Log4J. The only jar needed at the application server classpath level are besee.jar, javassist.jar (in fact only a sub part is needed) and jadvise.jar if you are using Aspect Oriented enhancements (read more in User guide part).

Further reading:

Class PreProcessor

A class preprocessor is a java component which preprocess java classed just before they are loaded. It can add them lot of on-demand features.

A class preprocessor receives the bytecode representing the class beeing loaded, can modify it and returns it, so that the given class is enhanced.

The schematic API of a preprocessor is as simple as follow: (source copyrighted from WebLogic)

public interface ClassPreProcessor
{
    public abstract void initialize(Hashtable hashtable);

    public abstract byte[] preProcess(String className, byte almostReadyBytecode[]);
}
		

BEA made Class PreProcessor usage possible by providing a small hook in their class loading mechanism.

Starting WebLogic Server with the -Dweblogic.classloader.preprocessor=... option makes the runtime instrumentation possible without providing a custom base class loader, as it is done when using beSee in J2SE mode.

Using a low level API for bytecode modification, the preprocessor enhances the class without impacting the class contract (name, interfaces, method signatures, packages, inheritance...). beSee uses Javassist API (official site) , but BCel (official site) is a famous similar one (used by commercial concurent product like Precise ).

Chained PreProcessor

Having a PreProcessor is great, but beSee provides an easy way to isolate runtime instrumentation thru Chained PreProcessor. A Chained PreProcessor is simply a PreProcessor which is part of a chain.

During class loading, the class is modified by every PreProcessor chained in the PreProcessor chain and which are configured to handle the given class.

Such an architecture makes it easy to provide specific PreProcessor enhancing a specific point in a class. All is then done in configuration: you can decide to add a "method response time point of mesure" in all the public methods of all your stateless EJB of the package com.yourcompany and have the EJB com.yourcompany.SpecificBean increments a JMX counter during the method createNewUser(...) by using two PreProcessors, each class beeing instrumented according to the configured chain.

An exemple of "preprocessor centric configuration" :

(configuration sample - illustration purpose)

besee.first=A

besee.A = com.gnilux.besee.preprocessor.AllMethodsPreProcessor
besee.A.next = B
besee.A.classMatch = com.yourcompany..*Bean
besee.A.classNotMatch = com.yourcompany.excludedpackage.*
besee.B.mt.after = com.gnilux.besee.log.LoggerFactory.getLogger("besee.sample").info(...);

besee.B = com.gnilux.besee.preprocessor.AllMethodsPreProcessor
besee.B.next =
besee.B.classMatch = com.yourcompany.SpecificBean
besee.B.methodMatch = createNewUser
besee.B.mt.before = com.gnilux.besee.jmx.JMXService.get("besee.sample").increment("$$method");
		
An exemple of "class centric configuration" :

(configuration sample - illustration purpose)
besee.class.A = com.yourcompany..*Bean
besee.class.A.chain = A, B
besee.class.A.A.methodMatch = .*
besee.class.A.B.methodMatch = createNewUser

besee.A = com.gnilux.besee.preprocessor.AllMethodsPreProcessor
besee.B.mt.after = com.gnilux.besee.log.LoggerFactory.getLogger("besee.sample").info(...);

besee.B = com.gnilux.besee.preprocessor.AllMethodsPreProcessor
besee.B.mt.before = com.gnilux.besee.jmx.JMXService.get("besee.sample").increment("$$method");
		

For specific needs, it is also possible to write a custom Chained PreProcessor.

Lasy JMX registration

beSee registers the PreProcessor chain as a JMX component to allow for runtime monitoring of beSee itself and hot configuration. It allows preprocessor configuration change without restarting the app server.

Since beSee is loaded during the startup stage of the J2EE application server, the JMX server in which beSee registers itself is not available before the server starts it.

To solve this issue, beSee has a lazy registration mechanism which allows to have JMX registration occur in background, waiting the JMX server part to be ready. This problem does not occurs when using beSee in J2SE mode.

Flow depth support

When you track the execution flow of an application, it is important to keep trace of the flow depth. For example if the method Sample.main() calls Foo.newInstance(), the newInstance() method is deeper in the execution flow - as the developper probably wrote it in its source code:

public class Sample {

	public static void main(String args[]) {
		foo = Foo.newInstance(args[0]);
		foo.do();
	}		
	
}
		
When monitoring your application, you probably don't want to see:
Sample#main(["hello", "guten tag", "bonjour"])
Foo#newInstance("hello") = foo[value="hello"]
Foo.do()
		
but rather something more natural like
Sample#main(["hello", "guten tag", "bonjour"])
	Foo#newInstance("hello") = foo[value="hello"]
	Foo.do()
		
To achieve this kind of feature, beSee provides thru the CflowPreProcessor an easy way to keep track of the flow depth. This module is proven to work in a multithreaded environment: each thread has it owns flow depth at any given time.