|
楼主 |
发表于 2010-5-7 07:53:10
|
显示全部楼层
本帖最后由 gk2004busy 于 2010-5-7 07:56 编辑
先看看在装载什么资源,上面的调用明显是jax-ws,glassfish下跑的是metro的实现。在glassfish目录下找到webservice-rt.jar,打开看META-INF/MANIFEST.MF,找到Name: jaxws-rt.jar这段,发现有Implementation-Version: 2.1.3.1,上metro的官网,找metor 相应版本的源文件,最接近的是2.1.3。下载下来查看源码,恩,开源就是好啊。
首先找到ServiceFinder类,代码片段:com.sun.xml.ws.util.ServiceFinder$LazyIterator.hasNext(ServiceFinder.java: 357 )
private static final String prefix = " META-INF/services/ " ;
if (configs == null ) {
String fullName = prefix + service.getName();
if (loader == null )
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
}
非常通用的做法,META-INF/services/下查找资源看改用哪个实现。
逆推找到类TransportTubeFactory中对此的调用代码:
public static Tube create(@Nullable ClassLoader classLoader,
@NotNull ClientTubeAssemblerContext context) {
for (TransportTubeFactory factory : ServiceFinder.find(TransportTubeFactory. class ,classLoader)) {
Tube tube = factory.doCreate(context);
if (tube != null ) {
TransportTubeFactory.logger.fine(factory.getClass() + " successfully created " + tube);
return tube;
}
}
代码的意图很明显,create()方法在classpath下找到TransportTubeFactory的实现,然后逐个尝试创建Tube对象。
疑问就来了,为什么每次请求都要create, 这里的create明显是重量级的,需要到classpath下去查找一下某个Factory的实现。明显不合理,继续逆推代码,在类DeferredTransportPipe中找到对create的调用:
public NextAction processRequest(@NotNull Packet request) {
if (request.endpointAddress == address)
// cache hit
return transport.processRequest(request);
.....
address = request.endpointAddress;
transport = TransportTubeFactory.create(classLoader, newContext);
不出意料的,这里有cache: 如果endpoint address和上次相同,直接重用,否则就调用create方法。从前面的现象看,cache没有命中,至少没有全部命中。
由于我们的测试的案例是连续调用4个不同的webservice,当然每个webservice的endpoint address是不同的。因此第一反应是这里transport cache机制被四个webservice的client端公用,因此每次调用只有1/4的概率和上次相同,其他的3/4就只能重新创建。之后花费了大量时间和精力去查看openesb的代码,过程不提,结果就是无果。
再回头来看这个cache的地方,有点奇怪为什么不命中。好在可以做remote debug,debug进入,到if(request.endpointAddress==address ) 这行,发现果然没有命中,但是随即检查request.endpointAddress和 address的值,非常惊讶的发现里面的实际值是相同的!!
直接晕倒!if (request.endpointAddress == address )
值相同而==不成立,那么就是说这里的request.endpointAddress 和 address 并不是一般的enum或者类型安全枚举, ==的检测根本不成立。
这是sun的代码啊,sun的程序员也会犯这种低级错误?用 == 来比较普通对象而不是用equals()方法?
继续看EndpointAddress 这个类,无语了:
1. 这是个普通的类,根本不是enum或者类型安全枚举,有两个publish的构造函数,理论上,使用者可以随意创建任意数量的实例
2. 没有重载equals方法,因此即使改用equals方法来提到==的检查也是无意义的,默认的equals()还是检查对象引用
因此,再来看DeferredTransportPipe中的这段试图重用cache的代码
public NextAction processRequest(@NotNull Packet request) {
if (request.endpointAddress == address)
// cache hit
return transport.processRequest(request);
这里的"if(request.endpointAddress==address)"能否成立,完全取决于客户端的调用方法:如果调用方保证每次相同endpointAddress的请求,request.endpointAddress都会是同一个实例,则这里的cache可以命中。否则这个cache毫无意义,还是需要每次重新创建重量级的transport对象。我们的测试案例中,很明显,openESB的程序员,没有考虑到DeferredTransportPipe这里的"特殊"要求,每次调用传入的request.endpointAddress虽然里面的实际值相同,但是每次都是不同的实例。因此 == 不成立,cache不命中。
查找了一下相关的类和接口定义,对于方法public NextAction processRequest(Packet request) 和 Packet中的endpointAddress属性,没有任何javadoc说明要求Packet中的endpointAddress属性需要做到相同地址只使用一个对象实例。
看Packet中的endpointAddress的设值代码:
public void setEndPointAddressString(String s) {
if (s == null )
this .endpointAddress = null ;
else
this .endpointAddress = EndpointAddress.create(s);
}
public static EndpointAddress create(String url) {
try {
return new EndpointAddress(url);
} catch (URISyntaxException e) {
throw new WebServiceException( " Illegal endpoint address: " + url,e);
}
}
明显每次通过调用setEndPointAddressString()设置时都会产生一个新的EndpointAddress实例。看代码时还意外的发现,
public EndpointAddress endpointAddress;
这个endpointAddress属性居然是public的!! 看样子,DeferredTransportPipe类的开发者,是寄希望于调用者不要通过Packet.setEndPointAddressString(String s)来设置,而是希望直接使用public的属性,这样才有希望命中cache!这分明是在挖坑,而且明显现在openESB的开发者被坑进去了!
鄙视啊鄙视,这样的代码,居然是sun的程序员写出来的,还放在metro里面,而metro作为默认的jax-ws实现被放在jdk中...... 很是无语。
无奈之下,修改代码,将 == 去掉,自己简单的判断一下endpointAddress的实际值
if (address != null && address.getURI() != null
&& request.endpointAddress.getURI().equals(address.getURI())) {
// cache hit
return transport.processRequest(request);
}
将编译出来的class文件,替换glassfish/lib/webservice-rt.jar中的相同文件,重新测试。再次thread dump,发现问题解决了。
期间看了一下DeferredTransportPipe类的各个version的代码,这里的 == 一直都没有改,难道sun就一直没有发现这里有问题?有兴趣的可以通过下面的地址使用fisheye来查看这个类的代码:
http://fisheye5.cenqua.com/browse/jax-ws-sources/jaxws-ri/rt/src/com/sun/xml/ws/transport/DeferredTransportPipe.java?r1=1.3&r2=1.3.4.1&u=-1
总结:用 == 来比较非enum或者类型安全枚举的对象实例,这种错误一般只有初学者才犯,万万没有想到,在metro这样级别的代码中也能出现。无限感叹啊,再次援引同事的评语作为本文的结束语:
sun的程序员也是程序员啊! |
|