[TOC]
Tomcat:正统的类加载器架构
主流的Java Web服务器,如Tomcat、Jetty、WebLogic、WebSphere或其他笔者没有列举 的服务器,都实现了自己定义的类加载器(一般都不止一个)。因为一个功能健全的Web服 务器,要解决如下几个问题:
-
部署在同一个服务器上的两个Web应用程序所使用的Java类库可以实现相互隔离。
这是 最基本的需求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一 个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以互相独立使用。
-
部署在同一个服务器上的两个Web应用程序所使用的Java类库可以互相共享。
这个需求 也很常见,例如,用户可能有10个使用Spring组织的应用程序部署在同一台服务器上,如果 把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费——这主要倒 不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到服务器内存,如果类库不能共 享,虚拟机的方法区就会很容易出现过度膨胀的风险。
-
服务器需要尽可能地保证自身的安全不受部署的Web应用程序影响。
目前,有许多主流 的Java Web服务器自身也是使用Java语言来实现的。因此,服务器本身也有类库依赖的问 题,一般来说,基于安全考虑,服务器所使用的类库应该与应用程序的类库互相独立。
-
支持JSP应用的Web服务器,大多数都需要支持HotSwap功能。
JSP文件最终 要编译成Java Class才能由虚拟机执行,但JSP文件由于其纯文本存储的特性,运行时修改的 概率远远大于第三方类库或程序自身的Class文件。而且ASP、PHP和JSP这些网页应用也把 修改后无须重启作为一个很大的“优势”来看待,因此“主流”的Web服务器都会支持JSP生成类 的热替换,当然也有“非主流”的,如运行在生产模式(Production Mode)下的WebLogic服务 器默认就不会处理JSP文件的变化。
由于存在上述问题,在部署Web应用时,单独的一个ClassPath就无法满足需求了,所以 各种Web服务器都“不约而同”地提供了好几个ClassPath路径供用户存放第三方类库,这些路 径一般都以“lib”或“classes”命名。被放置到不同路径中的类库,具备不同的访问范围和服务 对象,通常,每一个目录都会有一个相应的自定义类加载器去加载放置在里面的Java类库。
在Tomcat目录结构中,有3组目录(“/common/”、“/server/”和“/shared/”)可以存放 Java类库,另外还可以加上Web应用程序自身的目录“/WEB-INF/”,一共4组

- /common:类库可以被Tomcat和所有的Web应用使用。->CommonClassLoader
- /server:类库可以被Tomcat使用,对所有Web应用程序都不可见。->CatalinaClassLoader
- /shared:类库对所有Web应用程序可见,但是对Tomcat自己不可见。->SharedClassLoader
- /WebApp/WEB-INF:仅对此Web应用程序可见,对其他Web应用程序和Tomcat都不可见。->WebAppClassLoader
OSGi:灵活的类加载器架构
字节码生成技术与动态代理的实现
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class DynamicProxyTest {
interface IHello { void sayHello(); }
static class Hello implements IHello { @Override public void sayHello() { System.out.println("hello world"); } }
static class DynamicProxy implements InvocationHandler { Object originalObj;
Object bind(Object originalObj) { this.originalObj = originalObj; return Proxy.newProxyInstance( originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this); }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("welcome"); return method.invoke(originalObj, args); }
}
public static void main(String[] args) { IHello ihello = (IHello) new DynamicProxy().bind(new Hello()); ihello.sayHello(); }
}
|
Proxy.newProxyInstance()方法返回一个实现了IHello的接口,并且代理了new Hello()实例行为的 对象。最后它调用了 sun.misc.ProxyGenerator.generateProxyClass()方法来完成生成字节码的动作,这个方法可以 在运行时产生一个描述代理类的字节码byte[]数组。
如果想看一看这个在运行时产生的代理 类中写了些什么,可以在main()方法中加入
1
| System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
|
磁盘中将会产生一个名为“$Proxy0.class”的代理类Class 文件,反编译后可以看见
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package org.fenixsoft.bytecode; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements DynamicProxyTest.IHello { private static Method m3; private static Method m1; private static Method m0; private static Method m2; public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final void sayHello() throws { try { this.h.invoke(this,m3,null); return; } catch(RuntimeException localRuntimeException) { throw localRuntimeException; } catch(Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m3=Class.forName("org.fenixsoft.bytecode.DynamicProxyTest $IHello").getMethod("sayHello",new Class[0]); m1=Class.forName("java.lang.Object").getMethod("equals",new Class[]{Class.forName("java.lang.Object")}); m0=Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]); m2=Class.forName("java.lang.Object").getMethod("toString",new Class[0]); return; } catch(NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch(ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
|
这个代理类的实现代码也很简单,它为传入接口中的每一个方法,以及从 java.lang.Object中继承来的equals()、hashCode()、toString()方法都生成了对应的实 现,并且统一调用了InvocationHandler对象的invoke()方法(代码中的“this.h”就是父类 Proxy中保存的InvocationHandler实例变量)来实现这些方法的内容,各个方法的区别不过是 传入的参数和Method对象有所不同而已,所以无论调用动态代理的哪一个方法,实际上都是 在执行InvocationHandler.invoke()中的代理逻辑。