Tomcat类加载机制有commonLoader、catalinaLoader、sharedLoader、webappclassLoader,关系如下:
commonLoader、catalinaLoader、sharedLoader是在tomcat初始化的时候就被定义了,也就是BootStrap.java main函数启动时,就会调用initClassLoaders()
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
createClassLoader(),这个方法是去读配置文件conf/catalina.properties
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
默认server.loader和shared.loader是空,根据以下的createClassLoader方法,默认commonLoader、catalinaLoader、sharedLoader指向同一个引用
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
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 {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(
new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
}
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));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
commonLoader : 主要负责加载tomcat所需要的class,以及被各个Webapp都能访问
catalinaLoader: 主要负责加载私有class,各个Webapp是不能访问
sharedLoader: 主要负责各个Webapp共享的类加载器
注意ClassLoaderFactory.createClassLoader(repositories, parent) 这个方法返回一个不用做权限检查的URLClassLoader;
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
AccessController.doPrivileged() 这个方法该怎么理解呢?
一个调用者在调用doPrivileged方法时,可被标识为 “特权”。在做访问控制决策时,如果checkPermission方法遇到一个通过doPrivileged调用而被表示为 “特权”的调用者,并且没有上下文自变量,checkPermission方法则将终止检查。如果那个调用者的域具有特定的许可,则不做进一步检查,checkPermission安静地返回,表示那个访问请求是被允许的;如果那个域没有特定的许可,则和通常一样,一个异常被抛出。
首先java默认是不进行权限检查,本地的程序将拥有所有权限,打开方式如下:
System.setSecurityManager(new SecurityManager());
或者在启动参数中加上-Djava.security.manager。
什么地方会用到AccessController.doPrivileged():
个人认为在某个jar包中有个class将去读取文件时,并且应用打开权限检查
如何使用(举一个例子):
public class TestFileUtil {
public static boolean exist(String fileName) {
File file = new File(fileName);
return file.exists();
}
public static boolean existWithDoPrivilege(final String fileName) {
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return exist(fileName);
}
});
}
}
先打一个jar为test.jar,再创建一个工程Client,把test.jar引入,写个main函数
public class ProvilegeTest {
public static void main(String[] args) {
//开启权限检查
System.setSecurityManager(new SecurityManager());
String fileName = "C:/t.java";
System.out.println(TestFileUtil.exist(fileName));
}
}
运行一下:报错
Exception in thread "main" java.security.AccessControlException: access denied ("java.io.FilePermission" "t.java" "read")
at java.security.AccessControlContext.checkPermission(Unknown Source)
at java.security.AccessController.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkRead(Unknown Source)
at java.io.File.exists(Unknown Source)
at com.cxy.test.TestFileUtil.exist(TestFileUtil.java:11)
at com.cxy.test.ProvilegeTest.main(ProvilegeTest.java:10)
要想让它有权限:
1. 使用TestFileUtil.existWithDoPrivilege(fileName)
public class ProvilegeTest {
public static void main(String[] args) {
//开启权限检查
System.setSecurityManager(new SecurityManager());
String fileName = "C:/t.java";
System.out.println(TestFileUtil.existWithDoPrivilege(fileName));
}
}
2.在client工程下新建一个client.policy文件
grant codebase "file:E:\\JavaProject\\ServletJsp\\svn\\Client\\test.jar"
{
permission java.io.FilePermission
"C:\\t.java", "read,write";
};
3.添加vm参数
-Djava.security.policy=E:/JavaProject/ServletJsp/svn/Client/client.policy
当run方法会抛异常的时候,就应该用PrivilegedExceptionAction
AccessController.doPrivileged(new PrivilegedExceptionAction<Boolean>() {
@Override
public Boolean run() throws Exception {
return null;
}
});
在conf/catalina.policy文件,用来指定权限
webappclassLoader:只加载自己webapp的class
是在启动StandardContext的时候会创建WebappLoader
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
然后执行loader.start()
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
就会直接执行org.apache.catalina.loader.WebappLoader.startInternal()
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 {
classLoader = createClassLoader();
classLoader.setJarOpenInterval(this.jarOpenInterval);
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);
if (container instanceof StandardContext) {
classLoader.setAntiJARLocking(
((StandardContext) container).getAntiJARLocking());
classLoader.setClearReferencesRmiTargets(
((StandardContext) container).getClearReferencesRmiTargets());
classLoader.setClearReferencesStatic(
((StandardContext) container).getClearReferencesStatic());
classLoader.setClearReferencesStopThreads(
((StandardContext) container).getClearReferencesStopThreads());
classLoader.setClearReferencesStopTimerThreads(
((StandardContext) container).getClearReferencesStopTimerThreads());
classLoader.setClearReferencesHttpClientKeepAliveThread(
((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
classLoader.setClearReferencesObjectStreamClassCaches(
((StandardContext) container).getClearReferencesObjectStreamClassCaches());
}
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);
}
以上只是我对tomcat7类加载器初步认识,后续会深入分析,有问题欢迎指正