源码剖析Tomcat类的加载原理
众所周知,Java中默认的类加载器是以父子关系存在的,实现了双亲委派机制进行类的加载,在前文中,我们提到了,双亲委派机制的设计是为了保证类的唯一性,这意味着在同一个JVM中是不能加载相同类库的不同版本的类。
然而与许多服务器应用程序一样,Tomcat 允许容器的不同部分以及在容器上运行的不同Web应用程序可以访问的各种不同版本的类库,这就要求Tomcat必须打破这种双亲委派机制,通过实现自定义的类加载器(即实现了java.lang.ClassLoader)进行类的加载。下面,就让我们来看看Tomcat类加载原理是怎样的。
Tomcat中有两个最重要的类加载器,第一个便是负责Web应用程序类加载的WebappClassLoader,另一个便是JSP Servlet类加载器`JasperLoader。
Web应用程序类加载器(WebappClassLoader)上代码:
public class WebappClassLoader extends WebappClassLoaderBase { public WebappClassLoader() {super(); } public WebappClassLoader(ClassLoader parent) {super(parent); } ...}我们来看看WebappClassLoader继承的WebappClassLoaderBase中实现的类加载方法loadClass
public abstract class WebappClassLoaderBase extends URLClassLoaderimplements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {//...省略不需要关注的代码 protected WebappClassLoaderBase() {super(new URL[0]);// 获取当前WebappClassLoader的父加载器系统类加载器ClassLoader p = getParent();if (p == null) { p = getSystemClassLoader();}this.parent = p;// javaseClassLoader变量经过以下代码的执行,// 得到的是扩展类加载器(ExtClassLoader)ClassLoader j = String.class.getClassLoader();if (j == null) { j = getSystemClassLoader(); while (j.getParent() != null) {j = j.getParent(); }}this.javaseClassLoader = j;securityManager = System.getSecurityManager();if (securityManager != null) { refreshPolicy();} } //...省略不需要关注的代码 @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) { if (log.isDebugEnabled()) {log.debug('loadClass(' + name + ', ' + resolve + ')'); } Class<?> clazz = null; // Web应用程序停止状态时,不允许加载新的类 checkStateForClassLoading(name); // 如果之前加载过该类,就可以从Web应用程序类加载器本地类缓存中查找,// 如果找到说明WebappClassLoader之前已经加载过这个类 clazz = findLoadedClass0(name); if (clazz != null) {if (log.isDebugEnabled()) { log.debug(' Returning class from cache');}if (resolve) { resolveClass(clazz);}return clazz; } // Web应用程序本地类缓存中没有,可以从系统类加载器缓存中查找,// 如果找到说明AppClassLoader之前已经加载过这个类 clazz = findLoadedClass(name); if (clazz != null) {if (log.isDebugEnabled()) { log.debug(' Returning class from cache');}if (resolve) { resolveClass(clazz);}return clazz; }// 将类似java.lang.String这样的类名这样转换成java/lang/String// 这样的资源文件名 String resourceName = binaryNameToPath(name, false);// 获取引导类加载器(BootstrapClassLoader) ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; try { // 引导类加载器根据转换后的类名获取资源url,如果url不为空,就说明找到要加载的类URL url;if (securityManager != null) { PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName); url = AccessController.doPrivileged(dp);} else { url = javaseLoader.getResource(resourceName);}tryLoadingFromJavaseLoader = (url != null); } catch (Throwable t) {ExceptionUtils.handleThrowable(t);tryLoadingFromJavaseLoader = true; } // 首先,从扩展类加载器(ExtClassLoader)加载,防止Java核心API库被Web应用程序类随意篡改 if (tryLoadingFromJavaseLoader) {try { clazz = javaseLoader.loadClass(name); if (clazz != null) {if (resolve) { resolveClass(clazz);}return clazz; }} catch (ClassNotFoundException e) { // Ignore} } // 当使用安全管理器时,允许访问这个类 if (securityManager != null) {int i = name.lastIndexOf('.');if (i >= 0) { try {securityManager.checkPackageAccess(name.substring(0,i)); } catch (SecurityException se) {String error = sm.getString('webappClassLoader.restrictedPackage', name);log.info(error, se);throw new ClassNotFoundException(error, se); }} } /* * 如果Web应用程序类加载器配置为,<Loader delegate='true'/> 或者满足下列条件的类: * 当前类属于以下这些jar包中: * annotations-api.jar — Common Annotations 1.2 类。 * catalina.jar — Tomcat 的 Catalina servlet 容器部分的实现。 * catalina-ant.jar — 可选。用于使用 Manager Web 应用程序的 Tomcat Catalina Ant 任务。 * catalina-ha.jar — 可选。提供基于 Tribes 构建的会话集群功能的高可用性包。 * catalina-storeconfig.jar — 可选。从当前状态生成 XML 配置文件。 * catalina-tribes.jar — 可选。高可用性包使用的组通信包。 * ecj-*.jar — 可选。Eclipse JDT Java 编译器用于将 JSP 编译为 Servlet。 * el-api.jar — 可选。EL 3.0 API。 * jasper.jar — 可选。Tomcat Jasper JSP 编译器和运行时。 * jasper-el.jar — 可选。Tomcat EL 实现。 * jaspic-api.jar — JASPIC 1.1 API。 * jsp-api.jar — 可选。JSP 2.3 API。 * servlet-api.jar — Java Servlet 3.1 API。 * tomcat-api.jar — Tomcat 定义的几个接口。 * tomcat-coyote.jar — Tomcat 连接器和实用程序类。 * tomcat-dbcp.jar — 可选。基于 Apache Commons Pool 2 和 Apache Commons DBCP 2 的 * 包重命名副本的数据库连接池实现。 * tomcat-i18n-**.jar — 包含其他语言资源包的可选 JAR。由于默认包也包含在每个单独的JAR * 中,如果不需要消息国际化,可以安全地删除它们。 * tomcat-jdbc.jar — 可选。另一种数据库连接池实现,称为 Tomcat JDBC 池。有关详细信息,请参阅 文档。 * tomcat-jni.jar — 提供与 Tomcat Native 库的集成。 * tomcat-util.jar — Apache Tomcat 的各种组件使用的通用类。 * tomcat-util-scan.jar — 提供 Tomcat 使用的类扫描功能。 * tomcat-websocket.jar — 可选。Java WebSocket 1.1 实现 * websocket-api.jar — 可选。Java WebSocket 1.1 API * * 此处的filter方法,实际上tomcat官方将filter类加载过滤条件,看作是一种类加载器, *将其取名为CommonClassLoader */ boolean delegateLoad = delegate || filter(name, true); // 如果ExtClassLoader没有获取到,说明是非JRE核心类,那么就从系统类加载器(也称AppClassLoader// 应用程序类加载器)加载 if (delegateLoad) {if (log.isDebugEnabled()) { log.debug(' Delegating to parent classloader1 ' + parent);}try { clazz = Class.forName(name, false, parent); if (clazz != null) {if (log.isDebugEnabled()) { log.debug(' Loading class from parent');}if (resolve) { resolveClass(clazz);}return clazz; }} catch (ClassNotFoundException e) { // Ignore} } // 从Web应用程序的类加载器(也就是WebappClassLoader)中加载类。Web应用程序的类加载器是// 一个特殊的类加载器,它负责从Web应用程序的本地库中加载类 if (log.isDebugEnabled()) {log.debug(' Searching local repositories'); } try {clazz = findClass(name);if (clazz != null) { if (log.isDebugEnabled()) {log.debug(' Loading class from local repository'); } if (resolve) {resolveClass(clazz); } return clazz;} } catch (ClassNotFoundException e) {// Ignore } // 经过上面几个步骤还未加载到类,则采用系统类加载器(也称应用程序类加载器)进行加载 if (!delegateLoad) {if (log.isDebugEnabled()) { log.debug(' Delegating to parent classloader at end: ' + parent);}try { clazz = Class.forName(name, false, parent); if (clazz != null) {if (log.isDebugEnabled()) { log.debug(' Loading class from parent');}if (resolve) { resolveClass(clazz);}return clazz; }} catch (ClassNotFoundException e) { // Ignore} }}// 最终,还未加载到类,报类未找到的异常throw new ClassNotFoundException(name); }//...省略不需要关注的代码}综上所述,我们得出WebappClassLoader类加载器打破了双亲委派机制,自定义类加载类的顺序:
扩展类加载器(ExtClassLoader)加载Web应用程序类加载器(WebappClassLoader)系统类加载器类(AppClassLoader)公共类加载器类(CommonClassLoader)如果Web应用程序类加载器配置为,,也就是WebappClassLoaderBase类的变量delegate=true时,则类加载顺序变为:
扩展类加载器(ExtClassLoader)加载系统类加载器类(AppClassLoader)公共类加载器类(CommonClassLoader)Web应用程序类加载器(WebappClassLoader)JSP类加载器(JasperLoader)上代码:
public class JasperLoader extends URLClassLoader { private final PermissionCollection permissionCollection; private final SecurityManager securityManager; // JSP类加载器的父加载器是Web应用程序类加载器(WebappClassLoader) public JasperLoader(URL[] urls, ClassLoader parent,PermissionCollection permissionCollection) {super(urls, parent);this.permissionCollection = permissionCollection;this.securityManager = System.getSecurityManager(); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false); } @Override public synchronized Class<?> loadClass(final String name, boolean resolve)throws ClassNotFoundException {Class<?> clazz = null;// 从JVM的类缓存中查找clazz = findLoadedClass(name);if (clazz != null) { if (resolve) {resolveClass(clazz); } return clazz;}// 当使用SecurityManager安全管理器时,允许访问访类if (securityManager != null) { int dot = name.lastIndexOf('.'); if (dot >= 0) {try { // Do not call the security manager since by default, we grant that package. if (!'org.apache.jasper.runtime'.equalsIgnoreCase(name.substring(0,dot))){securityManager.checkPackageAccess(name.substring(0,dot)); }} catch (SecurityException se) { String error = 'Security Violation, attempt to use ' +'Restricted Class: ' + name; se.printStackTrace(); throw new ClassNotFoundException(error);} }} // 如果类名不是以org.apache.jsp包名开头的,则采用WebappClassLoader加载if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) { // Class is not in org.apache.jsp, therefore, have our // parent load it clazz = getParent().loadClass(name); if( resolve ) {resolveClass(clazz); } return clazz;}// 如果是org.apache.jsp包名开头JSP类,就调用父类URLClassLoader的findClass方法// 动态加载类文件,解析成Class类,返回给调用方return findClass(name); }}下面是URLClassLoader的findClass方法,具体实现:
protected Class<?> findClass(final String name)throws ClassNotFoundException {final Class<?> result;try { result = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() { public Class<?> run() throws ClassNotFoundException {String path = name.replace('.', '/').concat('.class');Resource res = ucp.getResource(path, false);if (res != null) { try { // 解析类的字节码文件生成Class类对象return defineClass(name, res); } catch (IOException e) {throw new ClassNotFoundException(name, e); }} else { return null;} }}, acc);} catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException();}if (result == null) { throw new ClassNotFoundException(name);}return result; }从源码中我们可以看到,JSP类加载原理是,先从JVM类缓存中(也就是Bootstrap类加载器加载的类)加载,如果不是核心类库的类,就从Web应用程序类加载器WebappClassLoader中加载,如果还未找到,就说明是jsp类,则通过动态解析jsp类文件获得要加载的类。
经过上面两个Tomcat核心类加载器的剖析,我们也就知道了Tomcat类的加载原理了。
下面我们来总结一下:Tomcat会为每个Web应用程序创建一个WebappClassLoader类加载器进行类的加载,不同的类加载器实例加载的类是会被认为是不同的类,即使它们的类名相同,这样的话就可以实现在同一个JVM下,允许Tomcat容器的不同部分以及在容器上运行的不同Web应用程序可以访问的各种不同版本的类库。
针对JSP类,会由专门的JSP类加载器(JasperLoader)进行加载,该加载器会针对JSP类在每次加载时都会解析类文件,Tomcat容器会启动一个后台线程,定时检测JSP类文件的变化,及时更新类文件,这样就实现JSP文件的热加载功能。
以上就是源码剖析Tomcat类的加载原理的详细内容,更多关于Tomcat类加载的资料请关注好吧啦网其它相关文章!
![Linux命令行处理图片方式(图片格式转换、缩放、旋转等)](http://www.haobala.com/attached/image/news/202309/18124551e2.jpg)