gpt4 book ai didi

从源码级深入剖析Tomcat类加载原理

转载 作者:我是一只小鸟 更新时间:2023-06-17 22:31:23 26 4
gpt4 key购买 nike

众所周知,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 URLClassLoader implements 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类加载器打破了双亲委派机制,自定义类加载类的顺序:

  1. 扩展类加载器(ExtClassLoader)加载
  2. Web应用程序类加载器(WebappClassLoader)
  3. 系统类加载器类(AppClassLoader)
  4. 公共类加载器类(CommonClassLoader)

如果Web应用程序类加载器配置为,,也就是WebappClassLoaderBase类的变量delegate=true时,则类加载顺序变为:

  1. 扩展类加载器(ExtClassLoader)加载
  2. 系统类加载器类(AppClassLoader)
  3. 公共类加载器类(CommonClassLoader)
  4. 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类加载原理的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com