小编给大家分享一下Tomcat9中类加载体系是怎么样的,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
Tomcat作为Web容器,支持多个应用程序的部署运行,所以会涉及应用程序间类库的隔离和共享,例如:不同应用程序既可以使用某个工具类库的不同版本但互不影响,亦可以共享使用某个类库。
在JVM中有3种系统提供的类加载器:
启动类加载器Bootstrap ClassLoader:这负载加载存放在
扩展类加载器Extension ClassLoader:负载加载
应用程序类加载器 Application ClassLoader:负载加载用户类路径CLASSPATH上所指定的类库,ClassLoader.getSysteClassLoader()方法的返回值就是此加载器,一般情况下这个就是程序中默认的类加载器
我们的应用程序都是由这3种类加载器互相配合进行加载的,还可以加入自己定义的类加载器,他们之间的关系如图所示:
图中展示的类加载器之间的层次关系,成为类加载器的双亲委派模型(Parents Delegation Model),双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应该有父类加载器。这里类加载器之间的父子关系一般不会继承(Inheritance)的强耦合关系来实现,而是使用组合(Composition)的弱耦合关系来复用父加载器的代码。
类加载器的双亲委派模型在JDK1.2期间被引入并被广泛应用于之后的所有Java程序中,但它并不是一个强制的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式,在之后章节中会看到,在Apache Tomcat中就打破了此模型。
双亲委派模型的工作过程是:如果一个类加载器收到加载类的请求,他首先不会自己去加载这个类,而是把这个请求委派给父加载器去完成,每个层次的类加载器都是如此处理,所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时(没有找类),子加载器才会尝试自己加载。
双亲委派模型本质上就是委托代理,虽然简单,却使Java类随着类加载器一起具备了优先级层次,例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器加载这个类,最终都是委派给启动类加载器进行加载,最终Object类在程序中的各种类加载器环境中都是同一个类。也就是说,如果你尝试编写一个与rJDK基础类库中已有类重名的Java类,类似java.lang.Object,你会发现,虽然可以正常编译,但永远无法被加载运行。
功能健全的Web容器的类加载器需要支持以下几点:
服务器的类库要与Web应用程序类库互相隔离,互不影响
同一个服务器上两个Web程序所使用的Java类库实现互相独立隔离
同一个服务器上了两个Web应用程序所使用的Java类库可以被共享
支持JSP的热替换(HotSwap),修改JSP文件不需要重启服务器
基于以上需求,下图为Tomcat类加载器的委派关系:
Common ClassLoader:Tomcat的基本类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp应用程序访问
Catalina ClassLoader:Tomcat容器私有的类加载器,加载路径中的class仅对Tomcat容器本身可见,Webapp应用程序不可访问
Shared ClassLoader:各个Webapp应用程序共享的类加载器,加载路径中的class对于所有的webapp可见
Webapp ClassLoader:各个 Webapp私有的类加载器,加载路径中的class仅对当前webapp可见
JasperLoader:记载方位仅为JSP文件所编译出的一个.class文件,当容器检测到JSP文件被修改时,互替换掉目前的JasperLoader实例,通过新建一个JSP类加载器来实现JSP文件的HotSwap功能
/**
* Initialize daemon.
* @throws Exception Fatal initialization error
*/
public void init() throws Exception {
initClassLoaders(); //初始化类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader); //设置当前线程类加载器为catalinaLoader
SecurityClassLoad.securityClassLoad(catalinaLoader); //使用Java SecurityManager初始化
// 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.getConstructor().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; //WebappLoader的parentLoader
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
commLoader、catalinaLoader和sharedLoader的初始化是在Tomcat容器运行的最初进行的,调用的方法为org.apache.catalina.startup.Bootstrap.init()
initClassLoaders初始化commLoader、catalinaLoader和sharedLoader
设置当前线程的类加载器为catalinaLoader,实现catalinaLoader为容器自身的私有类加载的目的
当使用Java SecurityManager时进行一些初始化工作
将sharedLoader向后传递,以作为webappLoader的parent
private void initClassLoaders() {
try {
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();
}
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方法中,对commLoader、catalinaLoader和sharedLoader进行初始化
commLoader、catalinaLoader和sharedLoader均通过createClassLoader创建
createClassLoader方法的第二参数为父类加载器,所以catalinaLoader、sharedLoader的父类加载器为commLoader
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<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// 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));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
获取类加载器相应的加载路径,路径的配置信息默认在conf/catalina.properties中,也可以通过环境变量catalina.config指定配置文件
类加载路径支持:.jar、*.jar和目录三种形式
当没有配置类加载器对应的加载路径时,则直接返回父类加载器。Tomcat9中catalinaLoader和sharedLoader均没有进行配置,故这两个加载器均为commonLoader
以下为Tomat9的conf/catalina.properties默认的加载路径配置:
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
/**
* 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 (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
...省略其他代码...
}
webappLoader的初始化在StandardContext中进行,这里创建WebappLoader
//重新实现ClassLoader
private String loaderClass = ParallelWebappClassLoader.class.getName();
@Override
protected void startInternal() throws LifecycleException {
...省略其他代码...
// Construct a class loader based on our current repositories list
try {
classLoader = createClassLoader(); //创建webappLoader
classLoader.setResources(context.getResources());
classLoader.setDelegate(this.delegate); //是否双亲委派
...省略其他代码...
}
/**
* Create associated classLoader.
*/
private WebappClassLoaderBase createClassLoader()
throws Exception {
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoaderBase classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = context.getParentClassLoader();
}
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args); //反射创建类加载器
return classLoader;
}
在WebappLoader的startInternal()方法调用createClassLoader()创建webappLoader
createClassLoader()通过反射创建ParallelWebappClassLoader
public class ParallelWebappClassLoader extends WebappClassLoaderBase{
...省略其他代码...
}
public abstract class WebappClassLoaderBase extends URLClassLoader
implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
...省略其他代码...
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
// (0)检查之前加载过的本地缓存,如果缓存存在,则取缓存中的类
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// (0.1)检查之前加载过的本地缓存,如果缓存存在,则取缓存中的类
// GraalVM直接返回null,查询缓存时,会校验name的合法性
clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
//(0.2) 通过系统加载器加载类,放置webapp中覆写Java SE的基础类
//此处的类加载器应为Bootstrap ClassLoader
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
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) 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);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
// (1) 当设置双亲委派或者加载类的名称为Tomcat容器内部的类,则委托给父类加载器加载
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) 在应用类加载路径进行加载
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) 委托父类加载器加载
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);
}
...省略其他代码...
}
ParallelWebappClassLoader继承了WebappClassLoaderBase,而WebappClassLoaderBase继承了URLClassLoader,并覆写了loadClass()方法,相当于实现了自己的类加载器
loadClass()方法显示,Tomcat类加载体系打破了双亲委派模型,其加载过程为
从缓存中查询,如果已加载过,则直接返回缓存中的class
通过系统类加载器加载(Bootstrap ClassLoader),已避免应用程序中覆写JavaSE基础类
判断是否设置为双亲委派,或者classname为特定路径下的,则委托给父类加载器夹杂
通过应用程序类加载路径加载,加载通过WebResourceRoot进行
当双亲委派标记为假时,最终委托给父类加载器加载
其中必须通过父类加载加载的类名称通过filter()方法判断:
protected boolean filter(String name, boolean isClassName) {
if (name == null)
return false;
char ch;
if (name.startsWith("javax")) {
/* 5 == length("javax") */
if (name.length() == 5) {
return false;
}
ch = name.charAt(5);
if (isClassName && ch == '.') {
/* 6 == length("javax.") */
if (name.startsWith("servlet.jsp.jstl.", 6)) {
return false;
}
if (name.startsWith("el.", 6) ||
name.startsWith("servlet.", 6) ||
name.startsWith("websocket.", 6) ||
name.startsWith("security.auth.message.", 6)) {
return true;
}
} else if (!isClassName && ch == '/') {
/* 6 == length("javax/") */
if (name.startsWith("servlet/jsp/jstl/", 6)) {
return false;
}
if (name.startsWith("el/", 6) ||
name.startsWith("servlet/", 6) ||
name.startsWith("websocket/", 6) ||
name.startsWith("security/auth/message/", 6)) {
return true;
}
}
} else if (name.startsWith("org")) {
/* 3 == length("org") */
if (name.length() == 3) {
return false;
}
ch = name.charAt(3);
if (isClassName && ch == '.') {
/* 4 == length("org.") */
if (name.startsWith("apache.", 4)) {
/* 11 == length("org.apache.") */
if (name.startsWith("tomcat.jdbc.", 11)) {
return false;
}
if (name.startsWith("el.", 11) ||
name.startsWith("catalina.", 11) ||
name.startsWith("jasper.", 11) ||
name.startsWith("juli.", 11) ||
name.startsWith("tomcat.", 11) ||
name.startsWith("naming.", 11) ||
name.startsWith("coyote.", 11)) {
return true;
}
}
} else if (!isClassName && ch == '/') {
/* 4 == length("org/") */
if (name.startsWith("apache/", 4)) {
/* 11 == length("org/apache/") */
if (name.startsWith("tomcat/jdbc/", 11)) {
return false;
}
if (name.startsWith("el/", 11) ||
name.startsWith("catalina/", 11) ||
name.startsWith("jasper/", 11) ||
name.startsWith("juli/", 11) ||
name.startsWith("tomcat/", 11) ||
name.startsWith("naming/", 11) ||
name.startsWith("coyote/", 11)) {
return true;
}
}
}
}
return false;
}
public class StandardRoot extends LifecycleMBeanBase implements WebResourceRoot {
...省略其他代码...
private final List<WebResourceSet> preResources = new ArrayList<>();
private WebResourceSet main;
private final List<WebResourceSet> classResources = new ArrayList<>();
private final List<WebResourceSet> jarResources = new ArrayList<>(); //对应WEB-INF/lib
private final List<WebResourceSet> postResources = new ArrayList<>();
private final List<WebResourceSet> mainResources = new ArrayList<>(); //对应应用webapp路径
private final List<List<WebResourceSet>> allResources = new ArrayList<>();
{
allResources.add(preResources);
allResources.add(mainResources);
allResources.add(classResources);
allResources.add(jarResources);
allResources.add(postResources);
}
...省略其他代码...
}
StandardRoot 在容器创建时进行初始化,其实现了接口WebResourceRoot ,用于加载Webapp类库。
以上代码可以看出,在Tomcat中Webapp类加载顺序为:
preResources->mainResources->classResources->jarResources->postResources
其中主要用到的资源路径为:
mainResources:对应应用目录下WEB-INF/classes
classResources:对应应用目录下WEB-INF/lib
所以在开发时,可以在src目录下覆盖lib包中的类,因为WEB-INF/classes会有限WEB-INF/lib进行加载。通常我们可以将依赖的第三方类库的源代码复制到src目录经,通过Tomcat运行进行debug,这对于排查第三方类库的问题很有帮助。
public ClassLoader getJspLoader() {
if( jspLoader == null ) {
jspLoader = new JasperLoader
(new URL[] {baseUrl},
getClassLoader(),
rctxt.getPermissionCollection());
}
return jspLoader;
}
public void clearJspLoader() {
jspLoader = null;
}
在Tomcat的conf/web.xml中,指定了处理JSP的Servlet:org.apache.jasper.servlet.JspServlet,在处理JSP页面的请求时,Tomcat会检测相应的JSP文件是否发生的修改,如果修改则会清理JSP的编译文件,然后先生成java文件,在编译为class文件
jspLoader为加载JSP的转化成的Servlet的类加载器,在Tomcat检测到jsp文件发生变化时都会重新生成
jspLoader的父类加载器为webapp的classloader,父类加载器的初始在org.apache.jasper.compiler.JspRuntimeContext的构造方法中,详见如下代码
public JspRuntimeContext(ServletContext context, Options options) {
..省略其他代码
// Get the parent class loader
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = this.getClass().getClassLoader();
}
parentClassLoader = loader;
...省略其他代码...
}
以上是“Tomcat9中类加载体系是怎么样的”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:http://blog.itpub.net/69956102/viewspace-2666482/