CSDN博客

img foresthou

Understanding JBoss

发表于2002/9/25 9:53:00  639人阅读

Understanding JBoss

Forest Hou
 
之所以用英文写这篇文章,是想看看自己能否用另一种文字表达清楚这东东。丝毫没有别的意思。
 
This article analyzes some critical techniques of JBoss: interceptor, dynamic proxy in rmi, and automatic object persistence. It is based on JBoss 2.2.1 source code. Through this article you can not only understand how JBoss implements the EJB model, but also realize these techniques could be used in other projects. The UML pictures only show the critical classes that would help you to understand the code. The code is abstracted from JBoss software to describe these concepts.

1. Basic EJB Interfaces

 

1.1  Does EJB specification only define interfaces? We can understand major parts of EJB by above class diagram. It shows the relationship of interfaces.

1.2  Using HelloWorld as an example, the EJB developer needs implement at least following classes:
    public interface Hello extends EJBObject;
    public interface HelloHome extends EJBHome;
    public class HelloBean implements SessionBean (or EntityBean);

1.3   “Hello” and “HelloHome” are remote interfaces. It is the EJB container (or application server)’s responsibility to implement the remote object and stub objects. The remote object delegates remote call for the enterprise bean (here is HelloBean). Hence the container separates the enterprise bean from remote world, and adds diverse services for the enterprise bean.

1.4  The container should also implements EJBContext and EJBMetaData interfaces for a specified enterprise bean.
 

2. Jboss Infrastructure

 

2.1  Application is the deployment unit. It is represented as a jar file physically, such as hello.jar.

2.2  An application contains several enterprise beans and it creates a Container instance for each enterprise bean. You can regard the container as the implementation of the EJBObject and EJBHome interfaces.

2.3  The configuration information of Enterprise beans is described in the ejb-jar.xml file. Their containers are configured in the standardjboss.xml and jboss.xml files. The ejb-jar.xml and jboss.xml files are included in the application jar file as the metadata . The standardjboss.xml defines the stand bean containers (CMP EntityBean, BMP EntityBean, Stateless SessionBean, Stateful SessionBean, Message Driven Bean).
Because the ejb-jar.xml file defines the bean type, JBoss can select the container type for an enterprise bean, then create the container instance for it.

2.4  The “deployments” field of the ContainerFactory class stores current deployed applications. ContainerFactory is a singleton object; it is used to create different containers for an application. The application server can run many applications simultaneously.

2.5  ContainerInvoker is the linkage of the remote world and the Container, and Container is the delegation of your enterprise beans.

2.6  The container is responsible to automatically persist its enterprise bean. This is the most attractive feature of EJB. The StatefulSessionPersistenceManager and EntityPersistenceManager interfaces are called by the container when it decides to persist an enterprise bean instance. Because the Stateless bean has no state, it need not to be persisted.

2.7  When a remote call enters a container instance, it must first pass through the interceptor list. When all interceptors agree the request then it is routed to the enterprise bean instance.

2.8  The InstancePool is used to store unused enterprise beans. When the container find the requested enterprise bean is not in the memory (actually cache), it get an empty instance from the InstancePool and initialize the bean. Of course it returns the unused instance to the InstancePool.

2.9  As its name indicates, the InstanceCache is used to cache the enterprise beans. The container first find the enterprise bean from the cache, if it failed, then it try to load the bean instance by the PersistenceManager. The InstanceCache is never used for the Stateless bean.

2.10  The container always tries to use the invoking thread. That means it uses the ContainerInvoker thread to finish the task. Supposing two remote calls enter the container, and calling the same method of the same enterprise bean. These two requests are in different threads, and the container will create two enterprise beans to serve them, but actually these beans are identical. So the thread fire extends to the background of the enterprise beans. You should understand why the EJB specification prohibits using multithread now. :-)
 

3. Interceptors

 

3.1  Interceptor is the critical part of JBoss. Above figure shows different interceptor relationships. All concrete interceptors are inherited from the AbstractInterceptor. JBoss implements following interceptors:

  • LogInterceptor implements logging service.
  • MetricsInterceptor gathers data from the container for administration.
  • SecurityInterceptor implements EJB declarative security model.
  • TxInterceptorCMT is container managed transaction interceptor.
  • TxInterceptorBMT is bean managed transaction interceptor.
  • StatelessSessionInstanceInterceptor acquires the specified stateless session bean instance.
  • StatefulSessionInstanceInterceptor acquires the specified stateful session bean instance.
  • EntityInstanceInterceptor acquires the specified entity bean instance.
  • EntitySynchronizationInterceptor takes care of EntityBean persistence.

3.2   The enterprise bean related messages are stored in the MethodInvocation argument of the invoke() and invokeHome() methods. Even the bean instance itself is embedded into the EnterpriseContext instance, which is also stored into the MethodInvocation instance.

3.3  The concrete interceptors should overload the invoke() and invokeHome() methods of AbstractInterceptor class, and must pass the call to the next interceptor as following:

public Object invoke(MethodInvocation mi) throws Exception {
    // do this interceptor’s actual work here.
    return getNext().invoke(mi);
}

3.4  Interceptors are added to the container through following scenario:

// org.jboss.ejb.ContainerFactory
// standardjboss.xml defines interceptors for each kind of container.
initializeContainer() {
    // container is org.jboss.ejb.Container
    // conf.getContainerInterceptorsConf() is org.w3c.dom.Element
    ContainerInterceptors.addInterceptors(container, ...,
            conf.getContainerInterceptorsConf());
}

// org.jboss.ejb.ContainerInterceptors
addInterceptors(Container container, ..., Element element) {
    // get the iterator for "interceptor"
    Iterator  interceptorElements
                = Metadata.getChildrenByTagName(element, “interceptor”);

    ClassLoader loader = container.getClassLoader();
    // First build the container interceptor stack from interceptorElements
    ArrayList istack = new ArrayList();

    while( interceptorElements != null && interceptorElements.hasNext() ) {
        Element ielement = (Element) interceptorElements.next();
        String className = MetaData.getElementContent(ielement);
        Class clazz = loader.loadClass(className);
        Interceptor interceptor = (Interceptor) clazz.newInstance();
        istack.add(interceptor);
    }

    // Now add the interceptors to the container
    for(int i = 0; i < istack.size(); i ++) {
        Interceptor interceptor = (Interceptor) istack.get(i);
        container.addInterceptor(interceptor);
    }
    // Finally we add the last interceptor from the container
    container.addInterceptor(container.createContainerInterceptor());
}

// org.jboss.ejb.Container
// createContainerInterceptor is an abstract method
abstract Interceptor createContainerInterceptor();

//
//  for StatelessSessionContainer
//
// org.jboss.ejb.StatelessSessionContainer
Interceptor createContainerInterceptor() {
    return new ContainerInterceptor();
}

// This class is an inner class of StatelessSessionContainer
// This is the last step before invocation - all interceptors are done
class ContainerInterceptor implements Interceptor { ... ... }
 

//
//  for StatefulSessionContainer
//
// org.jboss.ejb.StatelessSessionContainer
Interceptor createContainerInterceptor() {
    return new ContainerInterceptor();
}

// This class is an inner class of StatefulSessionContainer
// This is the last step before invocation - all interceptors are done
class ContainerInterceptor implements Interceptor { ... ... }
 

Something interesting things in above code:

  • The interceptors are added to the container in the order declared in the XML file.
  • The last interceptor is always an instance of an interceptor defined in the concrete container, such as StatefulSessionContainer.ContainerInterceptor. We will see later that it is this special interceptor that actually calls the enterprise bean methods.

3.5  Let's look at standjboss.xml file, and list session bean's interceptors:

  • Standard Stateless SessionBean
    LogInterceptor


    SecurityInterceptor
    <!—CMT -->
        TxInterceptorCMT
        MetricsInterceptor
        StatelessSessionInstanceInterceptor
    <!—BMT -->
        StatelessSessionInstanceInterceptor
        TxInterceptorBMT
        MetricsInterceptor

  •  Standard Stateful SessionBean
    LogInterceptor


        <!—CMT -->
        TxInterceptorCMT
        MetricsInterceptor
        StatefulSessionInstanceInterceptor
    <!—BMT -->
        StatefulSessionInstanceInterceptor
        TxInterceptorBMT
        MetricsInterceptor
    SecurityInterceptor

3.6  The calling sequence RemoteObject->ContainerInvoker->Interceptor->Enterprise Bean  is interesting. In this article we only analyze following scenario:

  1. RemoteObject->ContainerInvoker
  2. StatefulSessionInstanceInterceptor
  3. Last interceptor->Enterprise Bean method

RemoteObject->ContainerInvoker uses Dynamic Proxy technique. StatefulSessionInstanceInterceptor shows how the application server uses instance pool and instance cache,  persists object (the most attractive part of application server). The "Last interceptor->Enterprise Bean method" tells us how our bean methods are called. You will understand how JBoss works if you master these scenarios.

 

4. From Client to ContainerInvoker

 

4.1  From above discussion we know every enterprise bean has a container, and every container has a ContainerInvoker. This container invoker (org.jboss.ejb.plugins.jrmp.server.JRMPContainerInvoker,  we call it plugin invoker) inherits the java.rmi.server.RemoteServer, so it is a remote object, and its remote interface is ContainerRemote. In plugin invoker's start() method:

// org.jboss.ejb.plugins.jrmp.server.ContainerInvoker
start() {
    UnicastRemoteObject.exportObject(this, rmiPort,
        clientSocketFactory, serverSocketFactory);
}

So this remote object is exported by the UnicastRemoteObject, and the remote call first comes into this object. It is the container's gate to the enterprise bean.

4.2  Although JBoss creates the stub object of plugin invoker, the stub is not bound to the JNDI server. The actual object bound to the JNDI server is a proxy object:

// org.jboss.ejb.plugins.jrmp.server.ContainerInvoker
start() {
    // after exprot the JRMPContainerInvoker (plugin invoker) object
    rebind(context, container.getBeanMetaData().getJndiName(),
((ContainerInvokerContainer)container).getContainerInvoker().getEJBHome() );
        // above red code equals: this.getEJBHome()
}

So complicated invocation of getEJBHome(). It is the plugin invoker itself, and its getEJBHome() method is:

// org.jboss.ejb.plugins.jrmp.server.ContainerInvoker
public EJBHome getEJBHome() {
    return ciDelegate.getEJBHome();
}

Because dynamic proxy is implemented in jdk1.3, and JBoss supports jdk1.2, so it uses delegation to implement proxy object. The ciDelegate can be instance of  org.jboss.plugins.jrmp13.server.JRMPContainerInvoker (v13 invoker), or  org.jboss.plugins.jrmp12.server.JRMPContainerInvoker (v12 invoker). We only look at v13 invoker:


// org.jboss.ejb.plugins.jrmp13.server.ContainerInvoker
public EJBHome getEJBHome() {
    return home;
}

// org.jboss.ejb.plugins.jrmp13.server.ContainerInvoker
// v13 invoker creates the proxy instance
public void init() {
    this.home = (EJBHome)Proxy.newProxyInstance(
    ((ContainerInvokerContainer)container).getHomeClass().getClassLoader(),
    new Class[]{((ContainerInvokerContainer)container).getHomeClass(), handleClass},
new HomeProxy(ci.getJndiName(), ci.getEJBMetaData(), ci, ci.isOptimized()) );
}

Oh, another long invocation. We must realize that "ci" is the plugin invoker (org.jboss.ejb.plugins.jrmp.server.ContainerInvoker). So the remote object is passed to the HomeProxy as a parameter.


// org.jboss.ejb.plugins.jrmp.interfaces.HomeProxy
// This class extends org.jboss.ejb.plugins.jrmp.interfaces.GenericProxy
public HomeProxy(String name, EJBMetaData ejbMetaData,
ContainerRemote container, boolean optimize)
{
    super(name, container, optimize);
}

// org.jboss.ejb.plugins.jrmp.interfaces.GenericProxy
protected GenericProxy(String name, ContainerRemote container, boolean optimize) {
    // the plugin invoker is at last be stored in the GenericProxy
this.container = container;
}

// org.jboss.ejb.plugins.jrmp.interfaces.GenericProxy
// GenericProxy implements java.io.Externalizable interface, so it is serializable.
public void writeExternal(java.io.ObjectOutput out) throws IOException {
    ...
    // show container serialization
    out.writeObject(isLocal() ? container : null);
    ...
}

// org.jboss.ejb.plugins.jrmp.interfaces.GenericProxy
public void readExternal(java.io.ObjectInput in)
    throws IOException, ClassNotFoundException
{
    ...
    // show container serialization
    container = (ContainerRemote)in.readObject();
    ...
}
 

Notice when the GenericProxy is serialized to the JNDI server, the writeExternal() method is called, and the
container (JRMPContainerInvoker) will be changed to JRMPContainerInvoker_Stub. So when the client find the GenericProxy instance, it is different from that in the server side:


client side: GenericProxy + JRMPContainerInvoker_Stub
server side: GenericProxy + JRMPContainerInvoker

So the dynamic proxy holds the stub object and delegates the remote call for client! Compared to Rickard's first version talked in RMI mailing list, this one is much simpler. His first version creates the stub by himself, and uses complicated classloader to create the stub object, but this version uses RMI's stub object directly. (I am really surprised. :-D)

4.3  OK, let's look at how HomeProxy realize the invoke() method required by the InvokerHandler interface:

// org.jboss.ejb.plugins.jrmp.interfaces.HomeProxy
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
{
    // Build a method invocation that carries the identity of the target object
    RemoteMethodInvocation rmi = new RemoteMethodInvocation(
                    new CacheKey(args[0]),
                    removeObject,
                    new Object[0]);

    // here is the critical part
    return container.invoke(new MarshalledObject(rmi)).get();
}

So the HomeProxy invokes the stub (container) method at last, and that invocation goes to server. Before that invocation the proxy can add many other features: authentication message, transaction, etc. Here is the proxy's strength.

4.4  At server side, the plugin invoker method is:

// org.jboss.ejb.plugins.jrmp.server.JRMPContainerInvoker
public MarshalledObject invoke(MarshalledObject mimo)
{
    RemoteMethodInvocation rmi = (RemoteMethodInvocation)mimo.get();
    ...
    return new MarshalledObject(container.invoke(
                new MethodInvocation(rmi.getId(), rmi.getMethod(),
                        rmi.getArguments(), rmi.getPrincipal(), rmi.getCredential(),
                        rmi.getTransactionPropagationContext())) );
    ...
}

// org.jboss.ejb.StatefulSessionContainer
public Object invoke(MethodInvocation mi)
{
    // enter interceptor stack!
    return getInterceptor().invoke(mi);
}

We see the invocation enters the interceptor stack at last. There, the application server makes log record, checks access permissions, propagates transaction context, and of course, it must cache the enterprise bean instance according to the object id hidden in the MethodInvocation instance, and calls the specified method on this specified instance. So let's see how JBoss loads the enterprise bean instance.

5. Loading Enterprise Bean Instance

 

5.1  The InstanceInterceptor (EntityInstanceInterceptor, StatefulSessionInstanceInterceptor, StatelessSessionInstanceInterceptor) response loading the specified enterprise bean instance into the context, and pass the call to the next interceptor.

5.2  For example, the invoke() method of StatefulSessionInstanceInterceptor is:

Object invoke(MethodInvocation mi) {
    EnterpriseContext ctx = container.getInstanceCache().get(mi.getId());
    mi.setEnterpriseContext(ctx);

    try {
        getNext().invoke(mi);
    } ...
}

The enterprise bean instance is hidden in the EnterpriseContext instance, and this object is loaded by the InstanceCache object, and then is put in the MethodInvocation instance. Notice the MethodInvocation instance is passed as the parameter of the interceptors.

5.3  Following is the skeleton of the EnterpriseContext class:

public abstract class EnterpriseContext {
    Object instance; // EJB instance
    Container con;   // The container using this context
    Object id;   // Only StatelessSession beans have no Id, stateful and entity do

    // EJBContext are implemented in the ...
    protected class EJBContextImpl implements EJBContext {}
}

Note the EJBContext is implemented as an inner class of EnterpriseContext.

5.4  The get() method of InstanceCache is implemented in the AbstractInstanceCache class:

// org.jboss.ejb.plugins.AbstractInstanceCache

public EnterpriseContext get(Object id) {
    EnterpriseContext ctx = cache_policy.get(id);
    if (ctx == null) {
        ctx = acquireContext();
        setKey(id, ctx);
        activate(ctx);
        insert(key, ctx);
    }
    return ctx;
}

protected abstract EnterpriseContext acquireContext() throws Exception;
protected abstract void activate(EnterpriseContext ctx) throws RemoteException;

public void insert(EnterpriseContext ctx) {
    CachePolicy cache = getCache();
    Object key = getKey(ctx);

    if (cache.peek(key) == null) {
        cache.insert(key, ctx);
    }
}

The InstanceCache first tries to get the context from the cache policy. If failed, it gets the context by the acquireContext() method and then call activate(), at last store it in the cache. Note the acquireContext() and activate() are abstract methods.

5.5  Here is the code of StatefulSessionInstanceCache which implements the above abstract methods:

// org.jboss.ejb.plugins.StatefulSessionInstanceCache
protected void activate(EnterpriseContext ctx) {
    m_container.getPersistenceManager().activateSession(ctx);
}

protected abstract EnterpriseContext acquireContext() {
    return m_container.getInstancePool().get();
}

The acquireContext() method get a blank context instance from the instance pool, then in the activate() method the persistence manager object loads the bean data into the context.

5.6  Notice from the CachePolicy to InstancePool, then to PersistenceManager, all method invocation happened in the interfaces. At last the context is loaded and cached! The interface boundary is very clear.

6. Automatic Object Persistence

 

6.1  The logic of automatic object persistence is hidden in the mist. It is finished by the WorkerQueue class. This class maintains an Executable object queue, and persists the Executable object in the queue by another thread.

// org.jboss.util.WorkerQueue

/* The thread that runs the Executable jobs */
protected Thread m_queueThread;

public WorkerQueue(String threadName) {
    m_queueThread = new Thread(new QueueLoop(), threadName);
}

// The QueueLoop is an inner class of WorkerQueue
protected class QueueLoop implements Runnable {
    public void run() {
        while (true) {
            getJob().execute();
        }
    }
}

6.2  For the LRUCachePolicy, when you insert a bean context into it, it may remove a recently less used context and try to create an Executable object with this context, and then insert that Executable object into the WorkerQueue.


// org.jboss.util.LRUCachePolicy

// the map holding the cached objects
protected HashMap m_map;

// the linked list used to implement the LRU algorithm
protected LRUList m_list;

public void insert(Object key, Object o) {
    Object obj = m_map.get(key);    // try to locate the object
    if (obj == null) {
        m_list.demote();
        LRUCacheEntry entry = createCacheEntry(key, o);
        m_list.promote(entry);
        m_map.put(key, entry);
    }
}

protected void ageOut(LRUCacheEntry entry) {
    remove(entry.m_key);
}

// inner class of LRUCachePolicy
public class LRUList {
    protected void demote() {
        ...
        if (m_count == m_maxCapacity) {
            LRUCacheEntry entry = m_tail;

            // the entry will be removed by ageOut
            ageOut(entry);
        }
    }
}

The LRU algorithm is not important here. We only need to know that the demote() method may remove an entry by ageOut method. But the ageOut() of LRUCachePolicy only removes the object.

6.3  But the LRUEnterpriseContextCachePolicy really does something in the ageOut() method:

//
// org.jboss.ejb.plugins.LRUEnterpriseContextCachePolicy
//
private AbstractInstanceCache m_cache;

protected void ageOut(LRUCacheEntry entry) {
    ...
    m_cache.release((EnterpriseContext(entry.m_object);
}

//
// org.jboss.ejb.plugins.AbstractInstanceCache
//
public void release(EnterpriseContext ctx) {
    Object id = getKey(ctx);
    if (getCache().peek(id) != null) {
        getCache().remove(id);
    }

    schedulePassivation(ctx);
}

/* Helper class that handles synchronization for the passivation thread */
private PassivationHelper m_passivationHelper;

protected void schedulePassivation(EnterpriseContext ctx) {
    m_passivationHelper.schedule(ctx);
}
 

6.4  The actual job is done in the PassivationHelper class. It generates the Executable object and pushes it into the WorkerQueue.

// AbstractInstanceCache
private WorkerQueue m_passivator;

//
// inner class of AbstractInstanceCache
//
protected class PassivationHelper {
    protected void schedule(EnterpriseContext bean) {
        PassivationJob job = new PassivationJob(bean);
        m_passivator.putJob(job);
    }
}

6.5  The PassivationJob implements the Executable interface:

// The PassivationJob is defined in the PassivationHelper class
// as an anonymous class
new PassivationJob(bean) {
    public void execute() {
        EnterpriseContext ctx = getEnterpriseContext();

        // complicated algorithm, but runs following methods:
        passivate(ctx);
        freeContext(ctx);
    }
}

6.6  Well the passivate() and freeContext() are defined as abstract methods in the AbstractInstanceCache class. The StatefulSessionInstanceCache class implements them.

// org.jboss.ejb.plugins.StatefulSessionInstanceCache
protected passivate(EnterpriseContext ctx) {
    m_container.getPersistenceManager().passivateSession(ctx);
}

protected void freeContext(EnterpriseContext ctx) {
    m_container.getInstancePool().free(ctx);
}

So at last the bean object is persistent and the context is returned to the InstancePool. The logic is perfect.

6.7  The TimeCachePolicy implements the Runnable interface. That means it checks the cached objects periodly and age out the timed-out objects.

7. Call bean method

The last step is simple, the ContainerInerceptor locates the method by the method name, and gets the bean instance from the context, and then kicks off the method on the bean!

          // StatefulSessionContainer.ContainerInterceptor

invoke(MethodInvocation mi) {
    Method m = (Method)beanMapping.get(mi.getMethod());
    return m.invoke(mi.getEnterpriseContext().getInstance(),
                    mi.getArguments());
}

8. Conclusion

The JBoss document said the critical technology of JBoss is Dynamic Proxy and JMX. Sure, the Dynamic Proxy is one key part of JBoss, but I don't think the application of JMX is beautiful. In my opinion, the logic of automatic bean persistence is so delicate and needs a programmer to contemplate carefully. :-)
0 0

相关博文

我的热门文章

img
取 消
img