Tomcat源码解析——类加载机制

server/2024/9/24 23:27:06/

        一、类加载器的创建

        在之前的Tomcat启动源码中,简单的介绍了Tomcat的四种类加载器,再复习一遍。

类加载器

作用父加载器
commonLoader(共同类加载器)加载$CATALINA_HOME/lib下的类加载器应用类加载器
catalinaLoader(容器类加载器)加载Tomcat应用服务器的类加载器,可以理解为加载Tomcat源码中的类共同类加载器
sharedLoader(共享类加载器)加载应用类加载器的共享的类加载器,例如相同版本的mysql驱动等共同类加载器
webappLoader(Web应用类加载)加载Web应用下的类类加载,每个web应用之间是相互隔离的共享类加载器

        类加载器的结构层次:

                

         commonLoader、catalinaLoader、sharedLoader可以在tomcat下的conf/catalina.properties文件中修改。

        在Tomcat的启动中,一开始就创建了commonLoader、catalinaLoader、sharedLoader类加载器并且加载对应设置的资源。

Bootstrap:private void initClassLoaders() {try {//读取catalina.properties下的common.loader资源加载//commonLoader的父类加载器是应用类加载器(不设置都默认为应用类加载器)commonLoader = createClassLoader("common", null);if( commonLoader == null ) {commonLoader=this.getClass().getClassLoader();}//读取catalina.properties下的server.loader资源加载//catalinaLoader的父类加载器是commonLoadercatalinaLoader = createClassLoader("server", commonLoader);//读取catalina.properties下的shard.loader资源加载//sharedLoader的父类加载器是commonLoadersharedLoader = createClassLoader("shared", commonLoader);} catch (Throwable t) {handleThrowable(t);log.error("Class loader creation threw exception", t);System.exit(1);}}private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {//读取catalina.properties中对应的key值String value = CatalinaProperties.getProperty(name + ".loader");//如果没有设值对应的属性值,则返回父类加载器if ((value == null) || (value.equals("")))return parent;//替换掉Tomact的表达式变量值,如catalina.home、catalina.base等为具体的路径value = replace(value);List<Repository> repositories = new ArrayList<Repository>();StringTokenizer tokenizer = new StringTokenizer(value, ",");while (tokenizer.hasMoreElements()) {String repository = tokenizer.nextToken().trim();if (repository.length() == 0) {continue;}try {//创建一个URL@SuppressWarnings("unused")URL url = new URL(repository);repositories.add(new Repository(repository, RepositoryType.URL));continue;} catch (MalformedURLException e) {// Ignore}if (repository.endsWith("*.jar")) {repository = repository.substring(0, repository.length() - "*.jar".length());repositories.add(new Repository(repository, RepositoryType.GLOB));} else if (repository.endsWith(".jar")) {repositories.add(new Repository(repository, RepositoryType.JAR));} else {repositories.add(new Repository(repository, RepositoryType.DIR));}}//创建一个URL的类加载器,并把要加载的路径传递过去return ClassLoaderFactory.createClassLoader(repositories, parent);}

        严格来说,commonLoader和sharedLoader都算是可以共享的类加载器,只是作用域不同,所以在catalina.properties中设置了common.loader和shared.loader加载路径时,则在应用的maven中需要排除掉这些共享的依赖(不排除的话,会在web类加载器中重复加载,common.loader和server.loader设置就无意义)。

        commonLoader是catalinaLoader和sharedLoader的父类加载器,如果server.loader和shared.loader没有设值时,那么这3个类加载器的值都是一样的。

        Web应用类加载器的创建是在Host容器启动之后,Host容器会发送事件到HostConfig中,然后启动Host下的应用。

HostConfig:public void lifecycleEvent(LifecycleEvent event) {try {//Host的生命周期事件host = (Host) event.getLifecycle();if (host instanceof StandardHost) {setCopyXML(((StandardHost) host).isCopyXML());setDeployXML(((StandardHost) host).isDeployXML());setUnpackWARs(((StandardHost) host).isUnpackWARs());setContextClass(((StandardHost) host).getContextClass());}} catch (ClassCastException e) {log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);return;}if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {check();} else if (event.getType().equals(Lifecycle.START_EVENT)) {//Host启动之后,部署启动Host下的应用start();} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {stop();}}public void start() {//...省略if (host.getDeployOnStartup())//部署应用,即启动应用deployApps();}protected void deployApps() {File appBase = appBase();File configBase = configBase();String[] filteredAppPaths = filterAppPaths(appBase.list());deployDescriptors(configBase, configBase.list());//热部署War包deployWARs(appBase, filteredAppPaths);//部署扩展文件夹,即War包的解压deployDirectories(appBase, filteredAppPaths);}

        不管是如何部署,最终都会调用Context的start方法启动应用,在Context启动时,会创建对应的Web应用类加载器进行绑定,即一个Context对应一个Web应用类加载器,从而实现应用之间Jar包的隔离。

        

WebappLoader:private String loaderClass ="org.apache.catalina.loader.WebappClassLoader";protected void startInternal() throws LifecycleException {//...省略try {//创建应用对应的Web应用类加载器classLoader = createClassLoader();classLoader.setResources(container.getResources());classLoader.setDelegate(this.delegate);classLoader.setSearchExternalFirst(searchExternalFirst);if (container instanceof StandardContext) {classLoader.setAntiJARLocking(((StandardContext) container).getAntiJARLocking());classLoader.setClearReferencesStatic(((StandardContext) container).getClearReferencesStatic());classLoader.setClearReferencesStopThreads(((StandardContext) container).getClearReferencesStopThreads());classLoader.setClearReferencesStopTimerThreads(((StandardContext) container).getClearReferencesStopTimerThreads());classLoader.setClearReferencesHttpClientKeepAliveThread(((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());}//...省略}private WebappClassLoaderBase createClassLoader()throws Exception {Class<?> clazz = Class.forName(loaderClass);WebappClassLoaderBase classLoader = null;if (parentClassLoader == null) {parentClassLoader = container.getParentClassLoader();}Class<?>[] argTypes = { ClassLoader.class };Object[] args = { parentClassLoader };Constructor<?> constr = clazz.getConstructor(argTypes);classLoader = (WebappClassLoaderBase) constr.newInstance(args);return classLoader;}

        可以看到,默认创建的Web应用类加载器是WebappClassLoader,同时会设置父类加载。

        通过打断点的方式,可以看到Web应用类加载器的父类加载器是共享类加载器。

        二、类加载器的使用

        在上文中,Tomcat的四大类加载器已经创建完毕,那么Tomcat是如何实现应用之间的隔离?

        在上一篇Tomcat的请求流程中,我们知道当一个请求到达时,会经过容器通过Valve传递。

StandardHostValve:public final void invoke(Request request, Response response)throws IOException, ServletException {//...省略//为当前线程设置应用的类加载器,从而实现应用之间隔离Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());//...省略//传递到下一个容器的Valve中context.getPipeline().getFirst().invoke(request, response)}

        当请求到达HostValve中,会为当前线程设置类加载器为对应的Web应用类加载器,那么JVM在后面加载类时,会使用该类加载器。(JVM的机制)

        WebappClassLoader是默认的Web应用类加载器,重写了loadClass方法。

WebappClassLoader:public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLockInternal(name)) {Class<?> clazz = null;//先查看本地缓存是否加载了该类clazz = findLoadedClass0(name);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}//查询当前的类加载器是否已经加载了clazz = findLoadedClass(name);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}//尝试用扩展类加载器加载该类(依旧遵循双亲委派机制),防止Java的核心类被破坏try {clazz = j2seClassLoader.loadClass(name);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {}//检查访问权限if (securityManager != null) {int i = name.lastIndexOf('.');if (i >= 0) {try {securityManager.checkPackageAccess(name.substring(0,i));} catch (SecurityException se) {String error = "Security Violation, attempt to use " +"Restricted Class: " + name;throw new ClassNotFoundException(error, se);}}}boolean delegateLoad = delegate || filter(name);//当delegate为true时,会继续使用双亲委派加载方式,不过默认都是falseif (delegateLoad) {if (log.isDebugEnabled())log.debug("  Delegating to parent classloader1 " + parent);try {clazz = Class.forName(name, false, parent);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {}}try {//查找加载应用目录的class(/WEB-INF/classes和/WEB-INF/lib,既war包中的所有类)clazz = findClass(name);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {}//如果本地的class还没加载没有找到,则使用父加载器加载继续尝试(此处会走双亲委派)if (!delegateLoad) {try {//使用父加载器加载,此处会走双亲委派的机制clazz = Class.forName(name, false, parent);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {}}}throw new ClassNotFoundException(name);}

        应用类加载器的加载流程:

                1.查询缓存是否已经加载,加载不成功则进行下一步

                2.使用扩展类加载器加载(防止Java核心类被破坏),加载不成功则进行下一步

                3.使用Web应用类加载器加载(加载/WEB-INF/classes和/WEB-INF/lib),加载不成功则进行下一步

                4.交给父类加载器走双亲委派加载,加载路径则为:共享类加载器——>共同类加载器——>应用类加载器——>扩展类加载器——>系统类加载器      

        每个应用都使用自己的Web应用类加载器加载/WEB-INF/classes和/WEB-INF/lib,从而实现了Tomcat之间的应用隔离。

三、为什么要打破双亲委派机制?

        如果不打破双亲委派机制,能实现应用之间的隔离吗?

        答案是可以的,如果每个应用都创建一个自己的类加载器,走双亲委派加载时,最终还是在该类加载器实现最终的加载。

        既然如此,为什么Tomcat要打破双亲委派机制呢?

        因为Tomcat需要节约资源,如果走了双亲委派机制,那么一些共同的类库将无法实现共享,每个应用的类加载器都需要把所有的类库全部加载到自己的类加载器中,会浪费很多的内存资源,打破双亲委派机制,不仅可以让共同使用的类库实现共享,还能实现应用之间的隔离,不造成内存资源的浪费。

        


http://www.ppmy.cn/server/6283.html

相关文章

陇剑杯 省赛 攻击者3 CTF wireshark 流量分析

陇剑杯 省赛 攻击者3 CTF wireshark 流量分析 题目 链接&#xff1a;https://pan.baidu.com/s/1KSSXOVNPC5hu_Mf60uKM2A?pwdhaek 提取码&#xff1a;haek ├───LogAnalize │ ├───linux简单日志分析 │ │ linux-log_2.zip │ │ │ ├───misc日志…

4.3雷达建图与导航(冰达机器人实现)

4.3雷达建图与导航 4.3.1启动激光雷达和数据查看 本节内容介绍激光雷达的启动和雷达数据的查看 机器人端启动激光雷达roslaunch robot_navigation lidar.launch 此时查看话题列表就会发现多了一个scan话题&#xff0c;这个就是雷达发布的。看到这个话题说明雷达已经正常启动。…

《MATLAB科研绘图与学术图表绘制从入门到精通》示例:绘制伊甸火山3D网格曲面图

11.4.2小节我们使用3D曲面图可视化分析伊甸火山数据&#xff0c;本小节我们采用3D网格曲面图可视化分析伊甸火山数据&#xff0c;以展示其地形&#xff0c;具体示例代码如下。 购书地址&#xff1a;https://item.jd.com/14102657.html

Java中的四种引用类型

6.Java中的引用类型 1.强引用 一个对象A被局部变量、静态变量引用了就产生了强引用。因为局部变量、静态变量都是被GC Root对象关联上的&#xff0c;所以被引用的对象A&#xff0c;就在GC Root的引用链上了。只要这一层关系存在&#xff0c;对象A就不会被垃圾回收器回收。所以只…

HarmonyOS开发实例:【分布式数据服务】

介绍 分布式数据服务(Distributed Data Service&#xff0c;DDS)为应用程序提供不同设备间数据分布式的能力。通过调用分布式数据接口&#xff0c;应用程序将数据保存到分布式数据库中。通过结合帐号、应用和分布式数据服务对属于不同的应用的数据进行隔离&#xff0c;保证不同…

DNS是TCP还是UDP

既使用TCP也使用UDP 1. 域名解析时用UDP 在大多数情况下&#xff0c;DNS请求使用UDP协议&#xff0c;因为UDP协议可以提供较高的效率和安全性&#xff0c;尤其是在查询的响应大小较小&#xff08;通常不超过512字节&#xff09;时。非可靠连接&#xff0c;因为传输的数据量小…

Matlab|【复现】主动配电网故障定位方法研究

目录 1 主要内容 算例模型 期望故障电流状态函数 评价函数&#xff08;膨胀率函数&#xff09; 算例验证方法 详实的文档说明 2 部分程序 3 程序结果 4 下载链接 1 主要内容 该程序方法复现了《基于改进多元宇宙算法的主动配电网故障定位方法研究》_郑聪&#xff0c;建…

数据结构习题--杨辉三角形(返回某一行)

数据结构习题–杨辉三角形(返回某一行) 输入需要第几行&#xff0c;返回杨辉三角形中的这一行 注意&#xff1a;这里的行数是从0开始 方法&#xff1a;递推(复杂度行数的平方) 分析&#xff1a; 当处于每行的第一个和最后一个时&#xff0c;添加的数为1 除此之外&#xff0…