广告位联系
返回顶部
分享到

Tomcat Catalina为什么不new出来原理解析

Tomcat 来源:互联网 作者:佚名 发布时间:2022-08-20 21:17:08 人浏览
摘要

一、Catalina为什么不new出来? 掌握了Java的类加载器和双亲委派机制,现在我们就可以回答正题上来了,Tomcat的类加载器是怎么设计的? 1.Web容器的特性 Web容器有其自身的特殊性,所以在

一、Catalina为什么不new出来?

掌握了Java的类加载器和双亲委派机制,现在我们就可以回答正题上来了,Tomcat的类加载器是怎么设计的?

1.Web容器的特性

Web容器有其自身的特殊性,所以在类加载器这块是不能完全使用JVM的类加载器的双亲委派机制的。在Web容器中我们应该要满足如下的特性:

隔离性:

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

灵活性:

Web应用之间的类加载器相互独立,那么就能针对一个Web应用进行重新部署,此时Web应用的类加载器会被重建,而且不会影响其他的Web应用。如果采用一个类加载器,类之间的依赖是杂乱复杂的,无法完全移出某个应用的类。

性能:

性能也是一个比较重要的点。部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以互相共享。这个需求也很常见,例如,用户可能有10个使用Spring框架的应用程序部署在同一台服务器上,如果把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费——这主要倒不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到Web容器的内存,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险。

2.Tomcat类加载器结构

明白了Web容器的类加载器有多个,再来看tomcat的类加载器结构。

首先上张图,整体看下tomcat的类加载器:

可以看到在原先的java类加载器基础上,tomcat新增了几个类加载器,包括3个基础类加载器和每个Web应用的类加载器,其中3个基础类加载器可在conf/catalina.properties中配置,具体介绍下:

Common:

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

Catalina:

以Common类加载器为父类,是用于加载Tomcat应用服务器的类加载器,其路径由server.loader指定,默认为空,此时tomcat使用Common类加载器加载应用服务器。

Shared:

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

Web应用:

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

默认情况下,Common、Catalina、Shared类加载器是同一个,但可以配置3个不同的类加载器,使他们各司其职。

首先,Common类加载器复杂加载Tomcat应用服务器内部和Web应用均可见的类,如Servlet规范相关包和一些通用工具包。

其次,Catalina类加载器负责只有Tomcat应用服务器内部可见的类,这些类对Web应用不可见。比如,想实现自己的会话存储方案,而且该方案依赖了一些第三方包,当然是不希望这些包对Web应用可见,这时可以配置server.load,创建独立的Catalina类加载器。

再次,Shared类复杂加载Web应用共享类,这些类tomcat服务器不会依赖

3.Tomcat源码分析

3.1 CatalinClassLoader

首先来看看Tomcat的类加载器的继承结构

可以看到继承的结构和我们上面所写的类加载器的结构不同。

大家需要注意双亲委派机制并不是通过继承来实现的,而是相互之间组合而形成的。

所以AppClassLoader没有继承自 ExtClassLoader,WebappClassLoader也没有继承自AppClassLoader。

至于Common ClassLoader ,Shared ClassLoader,Catalina ClassLoader则是在启动时初始化的三个不同名字的URLClassLoader。

先来看看Bootstrap#init()方法。init方法会调用initClassLoaders,同样也会将Catalina ClassLoader设置到当前线程设置到当前线程,进入initClassLoaders来看看。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

private void initClassLoaders() {

    try {

        // 创建 commonLoader  catalinaLoader sharedLoader

        commonLoader = createClassLoader("common", null);

        if (commonLoader == null) {

            // no config file, default to this loader - we might be in a 'single' env.

            commonLoader = this.getClass().getClassLoader();

        }

        // 默认情况下 server.loader 和 shared.loader 都为空则会返回 commonLoader 类加载器

        catalinaLoader = createClassLoader("server", commonLoader);

        sharedLoader = createClassLoader("shared", commonLoader);

    } catch (Throwable t) {

        handleThrowable(t);

        log.error("Class loader creation threw exception", t);

        System.exit(1);

    }

}

我们可以看到在initClassLoaders()方法中完成了CommonClassLoader, CatalinaClassLoader,和SharedClassLoader的创建,而且进入到createClassLoader方法中。

可以看到这三个基础类加载器所加载的资源刚好对应conf/catalina.properties中的common.loader,server.loader,shared.loader

3.2 层次结构

Common/Catalina/Shared ClassLoader的创建好了之后就会维护相互之间的组合关系

其实也就是设置了父加载器

3.3 具体的加载过程

源码比较长,直接进入到 WebappClassLoaderBase中的 LoadClass方法

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

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

@Override

    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (getClassLoadingLock(name)) {

            if (log.isDebugEnabled()) {

                log.debug("loadClass(" + name + ", " + resolve + ")");

            }

            Class<?> clazz = null;

            // Log access to stopped class loader

            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache

            // 检查WebappClassLoader中是否加载过此类

            clazz = findLoadedClass0(name);

            if (clazz != null) {

                if (log.isDebugEnabled()) {

                    log.debug("  Returning class from cache");

                }

                if (resolve) {

                    resolveClass(clazz);

                }

                return clazz;

            }

            // (0.1) Check our previously loaded class cache

            // 如果第一步没有找到,则继续检查JVM虚拟机中是否加载过该类

            clazz = findLoadedClass(name);

            if (clazz != null) {

                if (log.isDebugEnabled()) {

                    log.debug("  Returning class from cache");

                }

                if (resolve) {

                    resolveClass(clazz);

                }

                return clazz;

            }

            // (0.2) Try loading the class with the bootstrap class loader, to prevent

            //       the webapp from overriding Java SE classes. This implements

            //       SRV.10.7.2

            // 如果前两步都没有找到,则使用系统类加载该类(也就是当前JVM的ClassPath)。

            // 为了防止覆盖基础类实现,这里会判断class是不是JVMSE中的基础类库中类。

            String resourceName = binaryNameToPath(name, false);

            ClassLoader javaseLoader = getJavaseClassLoader();

            boolean tryLoadingFromJavaseLoader;

            try {

                // Use getResource as it won't trigger an expensive

                // ClassNotFoundException if the resource is not available from

                // the Java SE class loader. However (see

                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for

                // details) when running under a security manager in rare cases

                // this call may trigger a ClassCircularityError.

                // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for

                // details of how this may trigger a StackOverflowError

                // Given these reported errors, catch Throwable to ensure any

                // other edge cases are also caught

                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) {

                // Swallow all exceptions apart from those that must be re-thrown

                ExceptionUtils.handleThrowable(t);

                // The getResource() trick won't work for this class. We have to

                // try loading it directly and accept that we might get a

                // ClassNotFoundException.

                tryLoadingFromJavaseLoader = true;

            }

            if (tryLoadingFromJavaseLoader) {

                try {

                    clazz = javaseLoader.loadClass(name);

                    if (clazz != null) {

                        if (resolve) {

                            resolveClass(clazz);

                        }

                        return clazz;

                    }

                } catch (ClassNotFoundException e) {

                    // Ignore

                }

            }

            // (0.5) Permission to access this class when using a SecurityManager

            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);

                    }

                }

            }

            // 检查是否 设置了delegate属性,设置为true,那么就会完全按照JVM的"双亲委托"机制流程加载类。

            boolean delegateLoad = delegate || filter(name, true);

            // (1) Delegate to our parent if requested

            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

                }

            }

            // (2) Search local repositories

            // 若是没有委托,则默认会首次使用WebappClassLoader来加载类。通过自定义findClass定义处理类加载规则。

            // findClass()会去Web-INF/classes 目录下查找类。

            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

            }

            // (3) Delegate to parent unconditionally

            // 若是WebappClassLoader在/WEB-INF/classes、/WEB-INF/lib下还是查找不到class,

            // 那么无条件强制委托给System、Common类加载器去查找该类。

            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);

    }

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的顺序)

版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://juejin.cn/post/7133459745515995173
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计