关注分享主机优惠活动
国内外VPS云服务器

Tomcat Catalina不出新的原理分析

1.为什么卡特琳娜不重新出柜?掌握了Java的类加载器和父母委托机制,现在我们可以回答这个问题了。Tomcat的类加载器是如何设计的?

1.Web容器的特点Web容器有其自身的特殊性,所以JVM的类加载器的父委托机制不能完全用在类加载器中。在Web容器中,我们应该满足以下特征:

隔离:

部署在同一Web容器上的两个Web应用程序所使用的Java类库可以相互隔离。想象两个Web应用程序,一个使用Spring3.0,另一个使用新的5.0,应用服务器使用类加载器。由于jar包覆盖,Web应用程序将无法启动。

Tomcat Catalina不出新的原理分析-主机频道

灵活性:

如果Web应用程序之间的类加载器相互独立,那么可以重新部署一个Web应用程序,并且该Web应用程序的类加载器将被重新构建,而不会影响其他Web应用程序。如果采用类加载器,那么类之间的依赖关系是杂乱而复杂的,不可能完全搬出一个应用类。

性能:

性能也是很重要的一点。部署在同一Web容器上的两个Web应用程序所使用的Java类库可以相互共享。这个要求也很普遍。例如,用户可能在同一个服务器上部署了10个使用Spring framework的应用程序。如果在每个应用的隔离目录下存放10份Spring,那将是极大的资源浪费& mdash& mdash这不是浪费磁盘空间的问题,而是类库在使用的时候必须加载到Web容器的内存中。如果类库不能共享,虚拟机的方法区就容易出现过度扩展的风险。

2.tomcat类加载器结构我了解到Web容器的类加载器有很多。让我们看看Tomcat类加载器的结构。

首先在上图中整体看一下tomcat的类加载器:

Tomcat Catalina不出新的原理分析-主机频道

可以看出,在原有java类加载器的基础上,tomcat增加了几个类加载器,包括三个基本类加载器和每个Web应用的类加载器,其中三个基本类加载器可以在conf/catalina.properties中配置,具体如下:

常见:

应用程序类加载器是父类,它是tomcat顶层的公共类加载器。它的路径由conf/catalina.properties中的common.loader指定,默认指向${catalina.base}/lib下的包。

Tomcat Catalina不出新的原理分析-主机频道

卡特琳娜:

Common classloader是父类,用于加载tomcat应用服务器。其路径由server.loader指定,默认情况下为空。此时,Tomcat使用公共类加载器来加载应用服务器。

Tomcat Catalina不出新的原理分析-主机频道

共享:

通用类加载器是所有Web应用的父类,其路径由shared.loader指定,默认为空。此时,tomcat使用公共类加载器作为Web应用程序的父加载器。

Web应用程序:

以共享类加载器为父类,加载/WEB-INF/classes目录下未压缩的类和资源文件和/WEB-INF/lib目录下的jar包。该类加载器仅对当前Web应用程序可见,对其他Web应用程序不可见。

默认情况下,Common、Catalina和Shared类装入器是相同的,但是可以配置三个不同的类装入器来执行它们各自的任务。

首先,通用类加载器加载Tomcat应用服务器和Web应用中可见的类,比如Servlet规范相关的包和一些通用的工具包。

其次,Catalina class loader负责只在Tomcat应用服务器内部可见的类,这些类对于Web应用是不可见的。例如,如果您希望实现自己的会话存储方案,并且该方案依赖于一些第三方包,但您肯定不希望这些包对Web应用程序可见,您可以配置server.load并创建一个独立的Catalina类加载器。

第三,Shared类加载Web应用程序共享类,tomcat服务器不会依赖这些类。

3.Tomcat源代码分析3.1 CatalinClassLoader首先看一下Tomcat的类加载器的继承结构。

Tomcat Catalina不出新的原理分析-主机频道

你可以看到继承的结构不同于我们上面写的类装入器。

大家要注意父母委托机制,这种机制不是通过继承来实现的,而是通过相互结合来形成的。

因此,AppClassLoader不从ExtClassLoader继承,WebappClassLoader也不从appClassLoader继承。

Common ClassLoader、Shared ClassLoader和Catalina ClassLoader是在启动时初始化的具有三个不同名称的URLClassLoader。

我们先来看看Bootstrap#init()方法。init方法将调用initClassLoaders,它还会将Catalina类加载器设置为当前线程,并输入initClassLoaders进行查看。

private init class loaders(){ try {//create common loader catalina loader shared loader common loader = create class loader(" common ",null);if (commonLoader == null) { //没有配置文件,默认为这个loader -我们可能在一个‘单个’env中。commonLoader = this.getClass()。get class loader();}//默认情况下,当server.loader和shared.loader都为空时,将返回commonLoader类加载器catalina loader = create class loader(" server ",common loader);shared loader = create class loader(" shared ",common loader);} catch(Throwable t){ handleThrowable(t);log.error("类加载器创建抛出异常",t);system . exit(1);}}我们可以看到,在initClassLoaders()方法中已经创建了CommonClassLoader、CatalinaClassLoader、SharedClassLoader,它们进入了createClassLoader方法。

Tomcat Catalina不出新的原理分析-主机频道

可以看到这三个基本类加载器加载的资源正好对应conf/catalina.properties中的common.loader、server.loader和shared.loader

3.2层级

在创建了common/catalina/shared class loader之后,它们之间的组合关系将被维护。

Tomcat Catalina不出新的原理分析-主机频道

实际上,父加载程序已经设置好了。

3.3具体加载过程有很长的源代码,直接进入WebappClassLoaderBase中的LoadClass方法。

@ Override public Class & lt?& gtloadClass(String name,boolean resolve)抛出ClassNotFoundException { synchronized(getClassLoadingLock(name)){ if(log . isdebugenabled()){ log . debug(" load class("+name+","+resolve+");} Class & lt?& gtclazz = null//记录对已停止的类加载器checkStateForClassLoading(name)的访问;//(0)检查我们以前加载的本地类缓存//检查这个类clazz = findLoadedClass0(name)是否已经加载到WebappClassLoader中;如果(clazz!= null){ if(log . isdebugenabled()){ log . debug("从缓存返回类");} if(resolve){ resolve class(clazz);}返回clazz}//(0.1)检查我们之前加载的类缓存//如果第一步没有找到,继续检查类clazz = findLoadedClass(name)是否已经加载到JVM虚拟机中;如果(clazz!= null){ if(log . isdebugenabled()){ log . debug("从缓存返回类");} if(resolve){ resolve class(clazz);}返回clazz} // (0.2)尝试使用引导类加载器加载类,以防止web应用程序覆盖Java se类。这实现了//SRV . 10 . 7 . 2//如果前两步没有找到,使用system类加载类(即当前JVM的类路径)。//为了防止重写基本类的实现,这里会判断该类是否是JVMSE中基本类库中的类。string resourceName = binaryNameToPath(name,false);class loader javase loader = getJavaseClassLoader();布尔型tryLoadingFromJavaseLoader尝试{ //使用getResource,因为如果资源无法从Java SE类加载器获得,它不会触发代价昂贵的// ClassNotFoundException。但是(详细信息请参见//https://bz.apache.org/bugzilla/show_bug.cgi? id = 58125//在安全管理器下运行时,在极少数情况下//此调用可能会触发ClassCircularityError。//请参见https://bz.apache.org/bugzilla/show_bug.cgi? id = 61424,了解//这可能如何触发StackOverflowError的详细信息//给定这些报告的错误,catch Throwable可确保任何//其他边缘情况也被捕获URL urlif (securityManager!= null){ privileged action & lt;URL & gtDP = new PrivilegedJavaseGetResource(resourceName);URL = access controller . doprivileged(DP);} else { URL = javase loader . get resource(resourceName);} tryLoadingFromJavaseLoader =(URL!= null);} catch (Throwable t) { //除了必须是re-throwed exception utils . handlethrowable(t)的异常之外,吞掉所有异常;getResource()技巧对这个类不起作用。我们必须//尝试直接加载它,并接受我们可能会得到// ClassNotFoundException。tryLoadingFromJavaseLoader = true;} if(tryloadingfromjavase loader){ try { clazz = javase loader . load class(name);如果(clazz!= null){ if(resolve){ resolve class(clazz);}返回clazz} } catch(ClassNotFoundException e){//Ignore } }//(0.5)使用security manager if(security manager!= null) { int i = name.lastIndexOf(' . ');如果(i & gt= 0){ try { security manager . checkpackageaccess(name . substring(0,I));} catch(security exception se){ String error = sm . getstring(" web app class loader . restricted package ",name);log.info(错误,se);抛出新的ClassNotFoundException(错误,se);} } }//检查是否设置了委托属性。如果设置为true,该类将完全根据JVM的“父委托”机制进程加载。boolean delegate load = delegate | | filter(name,true);// (1)如果请求则委托给我们的父类if(Delegate load){ if(log . is debugenabled()){ log . debug(" Delegating to parent class loader 1 "+parent);} try { clazz = Class.forName(name,false,parent);如果(clazz!= null){ if(log . isdebugenabled()){ log . debug("从父级加载类");} if(resolve){ resolve class(clazz);}返回clazz} } catch(ClassNotFoundException e){//Ignore } }//(2)搜索本地存储库//如果没有委托,默认情况下将使用WebappClassLoader首次加载类。通过自定义findClass来定义处理类加载规则。// findClass()将转到Web-INF/classes目录来查找该类。if(log . is debugenabled()){ log . debug("搜索本地存储库");} try { clazz = find class(name);如果(clazz!= null){ if(log . isdebugenabled()){ log . debug("从本地存储库加载类");} if(resolve){ resolve class(clazz);}返回clazz} } catch(ClassNotFoundException e){//Ignore }//(3)无条件委托给Parent如果WebappclassLoader仍然找不到/WEB-INF/classes、/WEB-INF/lib下的类,//则无条件强制系统和常用类加载器查找该类。如果(!delegate load){ if(log . is debugenabled()){ log . debug("委托给父类加载器结尾:"+parent));} try { clazz = Class.forName(name,false,parent);如果(clazz!= null){ if(log . isdebugenabled()){ log . debug("从父级加载类");} if(resolve){ resolve class(clazz);}返回clazz} } catch(ClassNotFoundException e){//Ignore } } } throw new ClassNotFoundException(name);}Web应用程序类加载器的默认加载顺序是:

(1).第一次从缓存加载;(2).如果没有,从JVM的Bootstrap类加载器加载;(3).如果没有,从当前类加载器加载(顺序为WEB-INF/classes,web -INF/lib);(4).如果没有,则从父类装入器装入。由于父类加载器采用默认的委托模式,所以加载顺序为AppClassLoader、Common、Shared。Tomcat提供delegate属性来控制是否启用java委托模式。默认值为false(未启用)。当设置为true时,tomcat将使用java的默认委托模式。此时,加载顺序如下:

(1).第一次从缓存加载;(2).如果没有,从JVM的Bootstrap类加载器加载;(3).如果没有,从父类加载器加载,加载顺序为AppClassLoader,Common,Shared。(4).如果没有,从当前的类加载器加载(顺序为WEB-INF/classes,WEB-INF/lib)。以上是为什么Tomcat Catalina不出新的详细内容。有关Tomcat Catalina原理的更多信息,请关注主机频道zhujipindao中的其他相关文章。com!

未经允许不得转载:主机频道 » Tomcat Catalina不出新的原理分析

评论 抢沙发

评论前必须登录!