源码剖析Tomcat类的加载原理

众所周知,Java中默认的类加载器是以父子关系存在的,实现了双亲委派机制进行类的加载,在前文中,我们提到了,双亲委派机制的设计是为了保证类的唯一性,这意味着在同一个JVM中是不能加载相同类库的不同版本的类。

然而与许多服务器应用程序一样,Tomcat 允许容器的不同部分以及在容器上运行的不同Web应用程序可以访问的各种不同版本的类库,这就要求Tomcat必须打破这种双亲委派机制,通过实现自定义的类加载器(即实现了java.lang.ClassLoader)进行类的加载。下面,就让我们来看看Tomcat类加载原理是怎样的。

Tomcat中有两个最重要的类加载器,第一个便是负责Web应用程序类加载的WebappClassLoader,另一个便是JSP Servlet类加载器`JasperLoader。

Web应用程序类加载器(WebappClassLoader)

上代码:

1
2
3
4
5
6
7
8
9
public class WebappClassLoader extends WebappClassLoaderBase {
    public WebappClassLoader() {
        super();
    }
    public WebappClassLoader(ClassLoader parent) {
        super(parent);
    }
   ...
}

我们来看看WebappClassLoader继承的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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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 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应用程序类加载器配置为, 或者满足下列条件的类:
             *  当前类属于以下这些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)

上代码:

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
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方法,具体实现:

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
protected Class> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction>() {
                    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类加载的资料请关注IT俱乐部其它相关文章!

本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/server/tomcat/9196.html
上一篇
下一篇
联系我们

联系我们

在线咨询: QQ交谈

邮箱: 1120393934@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部