Tomcat(五)类加载器机制

当一个JVM启动时,至少有三个ClassLoader会被启动:

  • Bootstrap 类加载器:加载Java核心包,它们被置放在(<JAVA_HOME>/lib目录下,这部分是JVM的一部分,往往使用native code完成。
  • Extensions类加载器:加载Java扩展包,即位于<JAVA_HOME>/lib/ext下的包,这里要注意的是,有些JVM的这个加载器和Bootstrap类加载器是同一个加载器,而Sun是把二者分开的,其实现类为sun.misc.Launcher$ExtClassLoader
  • System类加载器:这个类加载器加载CLASSPATH下的类,Sun的默认实现是sun.misc.Launcher$ExtClassLoader

这三者之间的父子关系是:Bootstrap类加载器是Extensions类加载器的父类加载器,而Extensions类加载器是System类加载器的父类加载器。
Tomcat的Classloader机制,从Bootstrap开始。在BootStrap初始化的时候,调用org.apache.catalina.startup.Bootstrap#initClassLoaders(),这个方法里面创建3个ClassLoader,它们分别是commonLoader,catalinaLoader,sharedLoader,其中catalinaLoader,sharedLoader的父亲加载器是commonLoader,initClassLoaders执行的过程中会执行createClassLoader,而createClassLoader是根据conf/catalina.properties文件中common.loaderserver.loadershared.loader的值来初始化,它的代码如下。
org.apache.catalina.startup.Bootstrap#createClassLoader()

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
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {

String value = CatalinaProperties.getProperty(name + ".loader");
// 1
if ((value == null) || (value.equals("")))
return parent;
// 2
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;
}

// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(
new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}

// Local repository
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));
}
}
// 3
ClassLoader classLoader = ClassLoaderFactory.createClassLoader
(repositories, parent);

// Retrieving MBean server
MBeanServer mBeanServer = null;
if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
} else {
mBeanServer = ManagementFactory.getPlatformMBeanServer();
}

// Register the server classloader
ObjectName objectName =
new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
mBeanServer.registerMBean(classLoader, objectName);

return classLoader;

}

  • 标注1的代码:判断如果catalina.properties中没有配置对应的loader属性的话,直接返回父加载器,而默认情况下,server.loadershared.loader为空,那么此时的catalinaLoader,sharedLoader其实是同一个ClassLoader。
  • 标注2的代码:根据环境变量的配置替换字符串中的值,默认情况下,common.loader的值为common.loader=${catalina.base}/lib${catalina.base}/lib/.jar${catalina.home}/lib${catalina.home}/lib/.jar,这里会将catalina.basecatalina.home用环境变量的值替换。
  • 标注3的代码:最终调用org.apache.catalina.startup.ClassLoaderFactory#createClassLoader()静态工厂方法创建URLClassloader的实例,而具体的URL其实就是*.loader属性配置的内容,此外如果parent = null的话,则直接用系统类加载器。 上面分析Tomcat在启动的时候,初始化的几个ClassLoader,接下再来继续看看,这些ClassLoader具体都用在什么地方。
    接着来看org.apache.catalina.startup.Bootstrap#init(),在初始化完3个classLoader以后,接下来首先通过catalinaLoader加载org.apache.catalina.startup.Catalinal类,然后通过反射调用org.apache.catalina.startup.Catalina#setParentClassLoader(),具体代码片段如下。
    org.apache.catalina.startup.Bootstrap#init()
    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
    /**
    * Initialize daemon.
    */
    public void init()
    throws Exception
    {

    // Set Catalina path
    setCatalinaHome();
    setCatalinaBase();

    initClassLoaders();

    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // Load our startup class and call its process() method
    if (log.isDebugEnabled())
    log.debug("Loading startup class");
    Class<?> startupClass =
    catalinaLoader.loadClass
    ("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.newInstance();

    // Set the shared extensions class loader
    if (log.isDebugEnabled())
    log.debug("Setting startup class properties");
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    Method method =
    startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues);
    catalinaDaemon = startupInstance;
    }

通过上面的代码,可以清楚的看到调用Catalina#setParentClassLoader(),设置parentClassLoader以后,sharedLoader又是在哪里使用的呢?这就需要接着来分析容器启动的代码。通过查看org.apache.catalina.startup.Catalina#getParentClassLoader()调用栈,看到在StandardContext#startInternal()中调用它,那么查看一下它的代码,包含如下代码片段。
org.apache.catalina.core.StandardContext#startInternal()

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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {

if(log.isDebugEnabled())
log.debug("Starting " + getBaseName());

// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}

setConfigured(false);
boolean ok = true;

// Currently this is effectively a NO-OP but needs to be called to
// ensure the NamingResources follows the correct lifecycle
if (namingResources != null) {
namingResources.start();
}

// Add missing components as necessary
if (webappResources == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources");
try {
if ((getDocBase() != null) && (getDocBase().endsWith(".war")) &&
(!(new File(getBasePath())).isDirectory()))
setResources(new WARDirContext());
else
setResources(new FileDirContext());
} catch (IllegalArgumentException e) {
log.error("Error initializing resources: " + e.getMessage());
ok = false;
}
}
if (ok) {
if (!resourcesStart()) {
log.error( "Error in resourceStart()");
ok = false;
}
}
// 1
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}

// Initialize character set mapper
getCharsetMapper();

// Post work directory
postWorkDirectory();

// Validate required extensions
boolean dependencyCheck = true;
try {
dependencyCheck = ExtensionValidator.validateApplication
(getResources(), this);
} catch (IOException ioe) {
log.error("Error in dependencyCheck", ioe);
dependencyCheck = false;
}

if (!dependencyCheck) {
// do not make application available if depency check fails
ok = false;
}

// Reading the "catalina.useNaming" environment variable
String useNamingProperty = System.getProperty("catalina.useNaming");
if ((useNamingProperty != null)
&& (useNamingProperty.equals("false"))) {
useNaming = false;
}

if (ok && isUseNaming()) {
if (getNamingContextListener() == null) {
NamingContextListener ncl = new NamingContextListener();
ncl.setName(getNamingContextName());
ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
addLifecycleListener(ncl);
setNamingContextListener(ncl);
}
}

// Standard container startup
if (log.isDebugEnabled())
log.debug("Processing standard container startup");


// Binding thread
ClassLoader oldCCL = bindThread();

try {

if (ok) {
// 2
// Start our subordinate components, if any
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();

// since the loader just started, the webapp classloader is now
// created.
// By calling unbindThread and bindThread in a row, we setup the
// current Thread CCL to be the webapp classloader
unbindThread(oldCCL);
oldCCL = bindThread();

// Initialize logger again. Other components might have used it
// too early, so it should be reset.
logger = null;
getLogger();

if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start();
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start();
if ((resources != null) && (resources instanceof Lifecycle))
((Lifecycle) resources).start();

// Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

// Start our child containers, if not already started
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}

// Start the Valves in our pipeline (including the basic),
// if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}

// Acquire clustered manager
Manager contextManager = null;
if (manager == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.cluster.noManager",
Boolean.valueOf((getCluster() != null)),
Boolean.valueOf(distributable)));
}
if ( (getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error("standardContext.clusterFail", ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}

// Configure default manager if none was specified
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager",
contextManager.getClass().getName()));
}
setManager(contextManager);
}

if (manager!=null && (getCluster() != null) && distributable) {
//let the cluster know that there is a context that is distributable
//and that it has its own manager
getCluster().registerManager(manager);
}
}

} finally {
// Unbinding thread
unbindThread(oldCCL);
}

if (!getConfigured()) {
log.error( "Error getConfigured");
ok = false;
}

// We put the resources into the servlet context
if (ok)
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources());

// Initialize associated mapper
mapper.setContext(getPath(), welcomeFiles, resources);

// Binding thread
oldCCL = bindThread();

if (ok ) {
if (getInstanceManager() == null) {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map<String, Map<String, String>> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResources(): getNamingResources());
setInstanceManager(new DefaultInstanceManager(context,
injectionMap, this, this.getClass().getClassLoader()));
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
}
}

try {
// Create context attributes that will be required
if (ok) {
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
}

// Set up the context init params
mergeParameters();

// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}

// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error( "Error listenerStart");
ok = false;
}
}

try {
// Start manager
if ((manager != null) && (manager instanceof Lifecycle)) {
((Lifecycle) getManager()).start();
}
} catch(Exception e) {
log.error("Error manager.start()", e);
ok = false;
}

// Configure and call application filters
if (ok) {
if (!filterStart()) {
log.error("Error filterStart");
ok = false;
}
}

// Load and initialize all "load on startup" servlets
if (ok) {
loadOnStartup(findChildren());
}

// Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}

// Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
}

startTime=System.currentTimeMillis();

// Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}

// Close all JARs right away to avoid always opening a peak number
// of files on startup
if (getLoader() instanceof WebappLoader) {
((WebappLoader) getLoader()).closeJARs(true);
}

// Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
} else {
setState(LifecycleState.STARTING);
}
}

  • 标注1的代码:在StandardContext启动的时候,会创建webapploader,创建webapploader的时候会将getParentClassLoader()返回的结果(这里返回的其实就是sharedLoader)赋值给自己的parentClassLoader变量。
  • 标注2的代码:调用到Webapploader#start(),因为WebappLoader符合Tomcat组件生命周期管理的模板方法模式,因此会调用到它的startInternal()

接下来就来看看WebappLoader#startInternal()
org.apache.catalina.loader.WebappLoader#startInternal()

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
/**
* Start associated {@link ClassLoader} and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected void startInternal() throws LifecycleException {

if (log.isDebugEnabled())
log.debug(sm.getString("webappLoader.starting"));

if (container.getResources() == null) {
log.info("No resources for " + container);
setState(LifecycleState.STARTING);
return;
}

// Register a stream handler factory for the JNDI protocol
URLStreamHandlerFactory streamHandlerFactory =
DirContextURLStreamHandlerFactory.getInstance();
if (first) {
first = false;
try {
URL.setURLStreamHandlerFactory(streamHandlerFactory);
} catch (Exception e) {
// Log and continue anyway, this is not critical
log.error("Error registering jndi stream handler", e);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This is likely a dual registration
log.info("Dual registration of jndi stream handler: "
+ t.getMessage());
}
}

// Construct a class loader based on our current repositories list
try {
// 1
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());
}

for (int i = 0; i < repositories.length; i++) {
classLoader.addRepository(repositories[i]);
}

// Configure our repositories
setRepositories();
setClassPath();

setPermissions();

((Lifecycle) classLoader).start();

// Binding the Webapp class loader to the directory context
DirContextURLStreamHandler.bind(classLoader,
this.container.getResources());

StandardContext ctx=(StandardContext)container;
String contextName = ctx.getName();
if (!contextName.startsWith("/")) {
contextName = "/" + contextName;
}
ObjectName cloname = new ObjectName
(MBeanUtils.getDomain(ctx) + ":type=WebappClassLoader,context="
+ contextName + ",host=" + ctx.getParent().getName());
Registry.getRegistry(null, null)
.registerComponent(classLoader, cloname, null);

} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
log.error( "LifecycleException ", t );
throw new LifecycleException("start: ", t);
}

setState(LifecycleState.STARTING);
}

  • 标注1的代码:调用createClassLoader()创建一个classLoader。

org.apache.catalina.loader.WebappLoader#createClassLoader()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Create associated classLoader.
*/
private WebappClassLoader createClassLoader()
throws Exception {

Class<?> clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;

if (parentClassLoader == null) {
parentClassLoader = container.getParentClassLoader();
}
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);

return classLoader;
}

在上面的代码里面,classLoader是WebappLoader的实例变量,其值为org.apache.catalina.loader.WebappClassLoader,那么上面的代码其实就是通过反射调用WebappClassLoader的构造函数,然后传递sharedLoader作为其父亲加载器。
代码阅读到这里,已经基本清楚Tomcat中ClassLoader的总体结构,总结如下: 在Tomcat存在common,cataina,shared三个公共的classloader,默认情况下,这三个classloader其实是同一个,都是common classloader,而针对每个webapp,也就是context(对应代码中的StandardContext类),WebappClassLoader为每个应用创建类加载器实例。
上面的描述,我们可以通过下图形象化的描述。

清楚Tomcat总体的ClassLoader结构以后,进一步来分析一下WebAppClassLoader的代码,Java的ClassLoader机制有parent-first的机制,而这种机制是在loadClass()保证的,一般情况下,只需要重写findClass()就好,而对于WebAppClassLoader,通过查看源代码,发现loadClass()findClass()都进行重写,那么首先就来看看它的loadClass(),它的代码如下。
org.apache.catalina.loader.WebappClassLoader#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
/**
* Load the class with the specified name, searching using the following
* algorithm until it finds and returns the class. If the class cannot
* be found, returns <code>ClassNotFoundException</code>.
* <ul>
* <li>Call <code>findLoadedClass(String)</code> to check if the
* class has already been loaded. If it has, the same
* <code>Class</code> object is returned.</li>
* <li>If the <code>delegate</code> property is set to <code>true</code>,
* call the <code>loadClass()</code> method of the parent class
* loader, if any.</li>
* <li>Call <code>findClass()</code> to find this class in our locally
* defined repositories.</li>
* <li>Call the <code>loadClass()</code> method of our parent
* class loader, if any.</li>
* </ul>
* If the class was found using the above steps, and the
* <code>resolve</code> flag is <code>true</code>, this method will then
* call <code>resolveClass(Class)</code> on the resulting Class object.
*
* @param name Name of the class to be loaded
* @param resolve If <code>true</code> then resolve the class
*
* @exception ClassNotFoundException if the class was not found
*/
@Override
public synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {

if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null;

// Log access to stopped classloader
if (!started) {
try {
throw new IllegalStateException();
} catch (IllegalStateException e) {
log.info(sm.getString("webappClassLoader.stopped", name), e);
}
}

// (0) Check our previously loaded local class cache
// 1
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
// 2
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 system class loader, to prevent
// the webapp from overriding J2SE classes
try {
// 3
clazz = system.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 = "Security Violation, attempt to use " +
"Restricted Class: " + name;
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
// 4
boolean delegateLoad = delegate || filter(name);
// 5
// (1) Delegate to our parent if requested
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = Class.forName(name, false, loader);
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
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
// 6
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
// 7
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = Class.forName(name, false, loader);
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);
}

  • 标注1代码:首先从当前ClassLoader的本地缓存中加载类,如果找到则返回。
  • 标注2代码:在本地缓存没有的情况下,调用ClassLoader#findLoadedClass()查看JVM是否已经加载过此类,如果已经加载则直接返回。
  • 标注3代码:通过系统的来加载器加载此类,这里防止应用写的类覆盖J2SE的类,这句代码非常关键,如果不写的话,就会造成你自己写的类有可能会把J2SE的类给替换调,另外假如你写一个javax.servlet.Servlet类,放在当前应用的WEB-INF/class中,如果没有此句代码的保证,那么你自己写的类就会替换到Tomcat容器Lib中包含的类。
  • 标注4代码:判断是否需要委托给父类加载器进行加载,delegate属性默认为false,那么delegatedLoad的值就取决于filter的返回值,filter()中根据包名来判断是否需要进行委托加载,默认情况下会返回false。因此delegatedLoad = false
  • 标注5代码:因为delegatedLoad = false,那么此时不会委托父加载器去加载,这里其实是没有遵循parent-first的加载机制。
  • 标注6代码:调用findClass()在webapp级别进行加载。
  • 标注7代码:如果还是没有加载到类,并且不采用委托机制的话,则通过父类加载器去加载。

通过上面的描述,可以知道Tomcat在加载webapp级别的类的时候,默认是不遵守parent-first的,这样做的好处是更好的实现应用的隔离,但是坏处就是加大内存浪费,同样的类库要在不同的app中都要加载一份。
上面分析完loadClass,接着在来分析一下findClass。
org.apache.catalina.loader.WebappClassLoader#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
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
/**
* Find the specified class in our local repositories, if possible. If
* not found, throw <code>ClassNotFoundException</code>.
*
* @param name Name of the class to be loaded
*
* @exception ClassNotFoundException if the class was not found
*/
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {

if (log.isDebugEnabled())
log.debug(" findClass(" + name + ")");

// Cannot load anything from local repositories if class loader is stopped
if (!started) {
throw new ClassNotFoundException(name);
}

// (1) Permission to define this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
if (log.isTraceEnabled())
log.trace(" securityManager.checkPackageDefinition");
securityManager.checkPackageDefinition(name.substring(0,i));
} catch (Exception se) {
if (log.isTraceEnabled())
log.trace(" -->Exception-->ClassNotFoundException", se);
throw new ClassNotFoundException(name, se);
}
}
}

// Ask our superclass to locate this class, if possible
// (throws ClassNotFoundException if it is not found)
Class<?> clazz = null;
try {
if (log.isTraceEnabled())
log.trace(" findClassInternal(" + name + ")");
if (hasExternalRepositories && searchExternalFirst) {
try {
clazz = super.findClass(name);
} catch(ClassNotFoundException cnfe) {
// Ignore - will search internal repositories next
} catch(AccessControlException ace) {
log.warn("WebappClassLoader.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if ((clazz == null)) {
try {
clazz = findClassInternal(name);
} catch(ClassNotFoundException cnfe) {
if (!hasExternalRepositories || searchExternalFirst) {
throw cnfe;
}
} catch(AccessControlException ace) {
log.warn("WebappClassLoader.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if ((clazz == null) && hasExternalRepositories && !searchExternalFirst) {
try {
clazz = super.findClass(name);
} catch(AccessControlException ace) {
log.warn("WebappClassLoader.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if (clazz == null) {
if (log.isDebugEnabled())
log.debug(" --> Returning ClassNotFoundException");
throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {
if (log.isTraceEnabled())
log.trace(" --> Passing on ClassNotFoundException");
throw e;
}

// Return the class we have located
if (log.isTraceEnabled())
log.debug(" Returning class " + clazz);

if (log.isTraceEnabled()) {
ClassLoader cl;
if (Globals.IS_SECURITY_ENABLED){
cl = AccessController.doPrivileged(
new PrivilegedGetClassLoader(clazz));
} else {
cl = clazz.getClassLoader();
}
log.debug(" Loaded by " + cl.toString());
}
return (clazz);
}

通过分析findClass的代码,最终会调用org.apache.catalina.loader.WebappClassLoader#findClassInternal(),那我们就来分析一下它的代码。
org.apache.catalina.loader.WebappClassLoader#findClassInternal()

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
/**
* Find specified class in local repositories.
*
* @return the loaded class, or null if the class isn't found
*/
protected Class<?> findClassInternal(String name)
throws ClassNotFoundException {

if (!validate(name))
throw new ClassNotFoundException(name);

String tempPath = name.replace('.', '/');
String classPath = tempPath + ".class";

ResourceEntry entry = null;

if (securityManager != null) {
PrivilegedAction<ResourceEntry> dp =
new PrivilegedFindResourceByName(name, classPath);
entry = AccessController.doPrivileged(dp);
} else {
// 1
entry = findResourceInternal(name, classPath);
}

if (entry == null)
throw new ClassNotFoundException(name);

Class<?> clazz = entry.loadedClass;
if (clazz != null)
return clazz;

synchronized (this) {
clazz = entry.loadedClass;
if (clazz != null)
return clazz;

if (entry.binaryContent == null)
throw new ClassNotFoundException(name);

// Looking up the package
String packageName = null;
int pos = name.lastIndexOf('.');
if (pos != -1)
packageName = name.substring(0, pos);

Package pkg = null;

if (packageName != null) {
pkg = getPackage(packageName);
// Define the package (if null)
if (pkg == null) {
try {
if (entry.manifest == null) {
definePackage(packageName, null, null, null, null,
null, null, null);
} else {
definePackage(packageName, entry.manifest,
entry.codeBase);
}
} catch (IllegalArgumentException e) {
// Ignore: normal error due to dual definition of package
}
pkg = getPackage(packageName);
}
}

if (securityManager != null) {

// Checking sealing
if (pkg != null) {
boolean sealCheck = true;
if (pkg.isSealed()) {
sealCheck = pkg.isSealed(entry.codeBase);
} else {
sealCheck = (entry.manifest == null)
|| !isPackageSealed(packageName, entry.manifest);
}
if (!sealCheck)
throw new SecurityException
("Sealing violation loading " + name + " : Package "
+ packageName + " is sealed.");
}

}

try {
// 2
clazz = defineClass(name, entry.binaryContent, 0,
entry.binaryContent.length,
new CodeSource(entry.codeBase, entry.certificates));
} catch (UnsupportedClassVersionError ucve) {
throw new UnsupportedClassVersionError(
ucve.getLocalizedMessage() + " " +
sm.getString("webappClassLoader.wrongVersion",
name));
}
entry.loadedClass = clazz;
entry.binaryContent = null;
entry.source = null;
entry.codeBase = null;
entry.manifest = null;
entry.certificates = null;
}
return clazz;
}

  • 标注1代码:通过名称去当前webappClassLoader的仓库中查找对应的类文件。
  • 标注2代码:将找到的类文件通过defineClass转变为JVM可以识别的Class对象返回。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×