分享
 
 
 

Understanding JBoss

王朝java/jsp·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

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:

RemoteObject->ContainerInvoker

StatefulSessionInstanceInterceptor

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. :-)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有