https://tomcat.apache.org/download-80.cgi
解压、新建catalina-home目录,同时将目录中的conf和webapps文件夹复制到catalina-home目录中
源码原始目录
新建后
需要通过Maven组织文件,因此需要在根目录下创建目录中新建pom.xml文件:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.apache.tomcatgroupId>
<artifactId>Tomcat8.5artifactId>
<name>Tomcat8.5name>
<version>8.5version>
<build>
<finalName>Tomcat8.5finalName>
<sourceDirectory>javasourceDirectory>
<testSourceDirectory>testtestSourceDirectory>
<resources>
<resource>
<directory>javadirectory>
resource>
resources>
<testResources>
<testResource>
<directory>testdirectory>
testResource>
testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>2.3version>
<configuration>
<encoding>UTF-8encoding>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.easymockgroupId>
<artifactId>easymockartifactId>
<version>3.4version>
dependency>
<dependency>
<groupId>antgroupId>
<artifactId>antartifactId>
<version>1.7.0version>
dependency>
<dependency>
<groupId>wsdl4jgroupId>
<artifactId>wsdl4jartifactId>
<version>1.6.2version>
dependency>
<dependency>
<groupId>javax.xmlgroupId>
<artifactId>jaxrpcartifactId>
<version>1.1version>
dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compilergroupId>
<artifactId>ecjartifactId>
<version>4.5.1version>
dependency>
dependencies>
project>
如果测试代码报错,注释即可, 例如 : util.TestCookieFilter
tomcat启动类: org.apache.catalina.startup.Bootstrap
添加VM options
-Dcatalina.home=catalina-home
-Dcatalina.base=catalina-home
-Djava.endorsed.dirs=catalina-home/endorsed
-Djava.io.tmpdir=catalina-home/temp
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=catalina-home/conf/logging.properties
启动报错
E:\idea-workspace\tomcat\apache-tomcat-8.5.42-src\.idea\ant.xml
加载项目配置失败: cannot parse file E:\idea-workspace\tomcat\apache-tomcat-8.5.42-src\.idea\ant.xml: Unexpected End-of-input in prolog
at [row,col {unknown-source}]: [1,1]
我的原因是没有使用jdk8编译, 修改后可以启动了
访问 http://localhost:8080
报错
原因是我们直接启动org.apache.catalina.startup.Bootstrap的时候没有加载org.apache.jasper.servlet.JasperInitializer,从而无法编译JSP。
解决办法是在tomcat的源码org.apache.catalina.startup.ContextConfig中的configureStart函数中手动将JSP解析器初始化:
添加代码:context.addServletContainerInitializer(new JasperInitializer(), null);
添加到这个方法org.apache.catalina.startup.ContextConfig#configureStart
再次访问
catalina 目录
catalina 包含所有的 Servlet 容器实现,以及涉及到安全、会话、集群、部署管理 Servlet 容器的各个方面,同时,它还包含了启动入口。
coyote 目录
coyote 是 Tomcat 链接器框架的名称,是 Tomcat 服务器提供的客户端访问的外部接口,客户端通过 Coyote 与服务器建立链接、发送请求并接收响应。
El 目录 提供 java 表达式语言
Jasper 模块 提供 JSP 引擎
Naming 模块 提供 JNDI 的服务
Juli 提供服务器日志的服务
tomcat 提供外部调用的接口 api
Tomcat 上可以部署多个项目
Tomcat 的一般部署,可以通过多种方式启动一个 Tomcat 部署多个项目,那么 Tomcat 在设计时会遇到什么挑战呢?
Tomcat 运行时需要加载哪些类
Tomcat 中的多个项目可能存在相同的类
Tomcat 中面临类加载的挑战
类加载: 主要是将.class 文件中的二进制字节读入到 JVM 中
我们可以看到因为这个定义,所以并没有规定一定是要磁盘加载文件,可以通过网络,内存什么的加载。只要是二进制流字节数据,JVM 就认。
类加载过程:
1. 通过类的全限定名获取该类的二进制字节流;
2. 将字节流所代表的静态结构转化为方法区的运行时数据结构
3. 在内存中生成一个该类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
**定义:**JVM 设计者把“类加载”这个动作放到 java 虚拟机外部去实现,以便让应用程序决定如何获取所需要的类。实现这个动作的代码模块成为“类加载器”
类与类加载器
对于任何一个类,都需要由加载它的类加载器和这个类来确定其在 JVM 中的唯一性。也就是说,两个类来源于同一个 Class 文件,并且被同一个类加载器加载,这两个类才相等。
注意:这里所谓的“相等”,一般使用 instanceof 关键字做判断。
启动类加载器:该加载器使用 C++语言实现,属于虚拟机自身的一部分。
启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中并且能被虚拟机识别的类库加载到 JVM 内存中,如果名称不符合的类库即使在 lib 目录中也不会被加载。该类加载器无法被 java 程序直接引用
拓展类加载器与应用程序类加载器:另一部分就是所有其它的类加载器,这些类加载器是由 Java 语言实现,独立于 JVM 外部,并且全部继承抽象类 java.lang.ClassLoader。
扩展类加载器(Extension ClassLoader):该加载器主要负责加载 JAVA_HOME\lib\ext 目录中的类库,开发者可以使用扩展加载器。
应用程序类加载器(Application ClassLoader):该列加载器也称为系统加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者可以直接使 用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器
双亲委派
略
sun.misc.Launcher.AppClassLoader
AppClassLoader是Launcher的一个静态内部类
核心代码
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
// loadClass是核心
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
return super.loadClass(var1, var2); // 调用父类的loadClass
}
}
static {
ClassLoader.registerAsParallelCapable();
}
}
注意这个super.loadClass(var1, var2)不是调用 java.net.FactoryURLClassLoader#loadClass
public final Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First check if we have permission to access the package. This
// should go away once we've added support for exported packages.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
int i = name.lastIndexOf('.');
if (i != -1) {
sm.checkPackageAccess(name.substring(0, i));
}
}
return super.loadClass(name, resolve);
}
// 虽然这个方法也在URLClassLoader类文件里面, 但是URLClassLoader和FactoryURLClassLoader只是被雪写在了统一
// .class文件里面的类FactoryURLClassLoader是URLClassLoader的子类, 很明显它也调用了super.loadClass(name, resolve);
所以我们要看的super.loadClass(name, resolve);是这个方法
java.lang.ClassLoader#loadClass(java.lang.String, boolean)
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 委派过程
if (parent != null) { // 如果有父加载器
// (注意和父类区分,父类是静态代码概念, 父实例是一种属性的组合关系)实例
c = parent.loadClass(name, false);
} else { // 如果没有父加载器的实例, 就使用启动类加载器(c++实现, jvm的一部分),
// 所以默认每个类肯定会有一个父加载器实例
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 没有打印任何, 因为有些类加载不到很正常, 避免不必要的干扰
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) { // 委派加载失败, 只能自己想办法加载了
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); // 这是一个钩子方法, 如果你重写了这个方法,回调自己的类实现
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c); // 解析
}
return c;
}
}
很显然, sun.misc.Launcher.AppClassLoader
自己重写findClass(name);
所以会调用 java.net.URLClassLoader#findClass
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res); // 将字节流转换成方法区运行时数据结构
// jvm中有了class对象了
} 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;
}
private Class<?> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
URL url = res.getCodeSourceURL();
if (i != -1) {
String pkgname = name.substring(0, i);
// Check if package already loaded.
Manifest man = res.getManifest();
definePackageInternal(pkgname, man, url);
}
// Now read the class bytes and define the class
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs); // 这里
} else {
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, b, 0, b.length, cs); // 这里
}
}
看看URLClassLoader注释:jar和目录
SecureClassLoader主要实现了defineClass功能
java.security.SecureClassLoader#defineClass(java.lang.String, byte[], int, int, java.security.CodeSource)
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
隔离性
Web 应用类库相互隔离,避免依赖库或者应用包相互影响,比如有两个 Web 应用,一个采用了 Spring 4,一个采用了 Spring 5,而如果如果采用同一个类加载器,那么 Web 应用将会由于 jar 包覆盖而无法启动成功。
这个地方每个人理解不同,可能就很难想象出来为什么会有问题, 我们很容易理解成应用类加载器不就能解决这个问题吗
假设app1和app2, 有两个版本的两个类com.app.A
如果我们使用双亲委派, 那么最终肯定是app1的应用类加载器加载app1应用下的,app2的应用类加载器加载app2应用下的
这句话看似没有毛病, 其实有好几处问题
我们所谓的app1应用加载器和app2的应用加载器在同一个JVM中本来就是一个东西(即: sun.misc.Launcher.AppClassLoader
的实例)
通过分析, 发现问题就在于我们要让"app1的类加载器"和"app2的类加载器"变成jvm中的两个类加载器, 并且不在委派给AppClassLoader加载即可, Tomcat就是这么干的
灵活性
因为隔离性,所以 Web 应用之间的类加载器相互独立,如果一个 Web 应用重新部署时,该应用的类加载器重新加载,同时不会影响其他 web 应用。
比如:不需要重启 Tomcat 的创建 xml 文件的类加载,
还有 context 元素中的 reloadable 字段:如果设置为 true 的话,Tomcat 将检测该项目是否变更,当检测到变更时,自动重新加载 Web 应用。
性能
由于每一个 Web 应用都有一个类加载器,所以在 Web 应用在加载类时,不会搜索其他 Web 应用包含的 Jar 包,性能自然高于只有一个类加载的情况。
这些和热部署很类似
Tomcat 提供 3 个基础类加载器(common、catalina、shared)和 Web 应用类加载器。
3 个基础类加载器的加载路径在 catalina.properties 配置,默认情况下,3 个基础类加载器的实例都是一个。 createClassLoader 调用 ClassLoaderFactory 属于一种工厂模式,并且都是使用 URLClassLoader
apache-tomcat-8.5.42-src\conf\catalina.properties
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
org.apache.catalina.startup.Bootstrap#initClassLoaders
//初始化三个类加载器以及确定父子关系
private void initClassLoaders() {
try {
// commonLoader的加载路径为common.loader
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,默认为空,父类加载器为commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
// 加载路径为shared.loader,默认为空,父类加载器为commonLoader
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader"); // 加后缀
// catalinaLoader与sharedLoader的加载路径均为空,所以直接返回commonLoader对象,默认3者为同一个对象
if ((value == null) || (value.equals("")))
return parent; // 如果是common进来,parent==null但是 vale不为空,所以不会进来
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value); // 获取存储地址
for (String repository : repositoryPaths) {
// 检查一个jar URL仓库
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(
new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// 本地仓库
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));
}
}
// 使用ClassLoaderFactory工厂模式创建
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
默认情况三个是一个实例,但是可以通过修改配置创建 3 个不同的类加载机制,使它们各司其职。
举个例子:如果我们不想实现自己的会话存储方案,并且这个方案依赖了一些第三方包,我们不希望这些包对 Web 应用可见,因此我们可以配置 server.loader,创建独立的 Catalina 类加载器。
共享性:
Tomcat 通过 Common 类加载器实现了 Jar 包在应用服务器与 Web 应用之间的共享(自然也包含web应用之间的共享),
通过 Shared 类加载器实现了 Jar 包在 Web 应用之间的共享
通过 Catalina 类加载器加载服务器依赖的类。
public void init() throws Exception {
System.out.println("Bootsrap--init()");
//类加载器初始化
initClassLoaders();
//设置线程上下文类加载器来解决有可能的ClassNotFoundException问题
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// 加载启动类Catalina并调用其process()方法
if (log.isDebugEnabled())
log.debug("Loading startup class");
// catalinaLoader去加载catalina
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 设置共享扩展类加载器sharedLoader
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];
//这里把shared加载器传递给catalina
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues); // 调用这个方法 setParentClassLoader
catalinaDaemon = startupInstance;
}
因为类加载需要做很多事情,比如读取字节数组(加载)、验证、解析、初始化等。而 Java 提供的 URLClassLoader 类能够方便的将 Jar、Class 或者网络资源加 载到内存中。而 Tomcat 中则用一个工厂类,ClassLoaderFactory 把创建类加载器的细节进行封装,可以通过它方便的创建自定义类加载器
private void initClassLoaders() {
try {
// commonLoader的加载路径为common.loader
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,默认为空,父类加载器为commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
// 加载路径为shared.loader,默认为空,父类加载器为commonLoader
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader"); // 加后缀
// catalinaLoader与sharedLoader的加载路径均为空,所以直接返回commonLoader对象,默认3者为同一个对象
...
return ClassLoaderFactory.createClassLoader(repositories, parent); // 工厂
}
org.apache.catalina.startup.ClassLoaderFactory#createClassLoader(List
public static ClassLoader createClassLoader(List<Repository> repositories,
final ClassLoader parent)
throws Exception {
if (log.isDebugEnabled())
log.debug("Creating new class loader");
// Construct the "class path" for this class loader
Set<URL> set = new LinkedHashSet<>();
if (repositories != null) {
for (Repository repository : repositories) {
if (repository.getType() == RepositoryType.URL) {
...
}
}
}
// Construct the class loader itself
final URL[] array = set.toArray(new URL[set.size()]);
if (log.isDebugEnabled())
for (int i = 0; i < array.length; i++) {
log.debug(" location " + i + " is " + array[i]);
}
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}
// org.apache.catalina.startup.ClassLoaderFactory.RepositoryType
public enum RepositoryType {
DIR, //表示整个目录下的资源,包括所有Class、jar包以及其他类型资源
GLOB,//表示整个目录下所有的Jar包资源,仅仅是.jar后缀的资源
JAR, //表示单个Jar包资源
URL //表示从URL上获取的Jar包资源
}
使用加载器工厂的好处
ClassLoadFactory 有一个内部 Repository,它就是表示资源的类,资源的类型用一个 RepositoryType 的枚举表示。
如果在检查 jar 包的时候,如果有检查的 URL 地址的如果检查有异常就忽略掉,可以确保部分类加载正确。
每一个运行线程中有一个成员 ContextClassLoader,用于在运行时动态载入其他类,当程序中没有显示声明由哪个类加载器去加载哪个类(比如 new 出一 个类时),将默认由当前线程类加载器加载,所以一般系统默认的 ContextClassLoad 是系统类加载器。
public void init() throws Exception {
System.out.println("Bootsrap--init()");
//类加载器初始化
initClassLoaders();
//设置线程上下文类加载器来解决有可能的ClassNotFoundException问题
Thread.currentThread().setContextClassLoader(catalinaLoader);
一般在实际的系统上,使用线程上下文类加载器,可以设置不同的加载方式,这个也是 Java 灵活的类加载方式的体现,也可以很轻松的打破双亲委派模式,同时也会避免类加载的异常。
每个 web 应用会对一个 Context 节点,在 JVM 中就会对应一个 org.apache.catalina.core.StandardContext 对象,而每一个 StandardContext 对象内部都 一个加载器实例 loader 实例变量。这个 loader 实际上是 WebappLoader 对象。而每一个 WebappLoader 对象内部关联了一个 classLoader 变量(就这这个类的定义中,可以看到该变量的类型是 org.apache.catalina.loader.WebappClassLoader)。
所以,这里一个 web 应用会对应一个 StandardContext 一个 WebappLoader 一个 WebappClassLoader 。
一个 web 应用对应着一个 StandardContext 实例,每个 web 应用都拥有独立 web 应用类加载器(ParallelWebappClassLoader(不同版本使用的可能不是同一个)),这个类加载器在 StandardContext.startInternal()中被构造了出来。
org.apache.catalina.core.StandardContext#startInternal
protected synchronized void startInternal() throws LifecycleException {
...
//Webapp类加载的设置
//这里的获取和设置类加载
if (getLoader() == null) { // 每个Context都会创建一个自己的WebappLoader实例, 这样每一个webapp都会
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader); // 这里使用了读写锁方式
}
...
} // 所以前面的 commonLoader, catalinaLoader, sharedLoader 都在Bootstrap.class里面, 整个tomcat里面只会创建一个实例
注意这里:设置加载器和获取加载器都使用了读写锁机制,确保多线程情况下对共享资源的访问不会出现问题。
同时因为 Tomcat 的生命周期管理,必定会调用 WebappLoader.java 的 startInternal()方法,该方法中 new 出WebappClassLoader
public class WebappClassLoader extends WebappClassLoaderBase
public abstract class WebappClassLoaderBase extends URLClassLoader
org.apache.catalina.loader.WebappLoader
// WebappClassLoaderBase抽象类两个实现 WebappClassLoader 和 ParallelWebappClassLoader
// 这里使用的ParallelWebappClassLoader
private String loaderClass = ParallelWebappClassLoader.class.getName();
org.apache.catalina.loader.WebappLoader#startInternal
protected void startInternal() throws LifecycleException {
...
// Construct a class loader based on our current repositories list
try {
classLoader = createClassLoader();
classLoader.setResources(context.getResources());
classLoader.setDelegate(this.delegate);
}
...
}
当配置信息中有 reloadable 的属性,并且值为 true 时,Tomcat 怎么去完成这个过程呢?
<Context pathname="/" docBase="/app/app1" reloadable="true"/>
据 Tomcat 的启动流程,我们分析下 Context 的初始化 start 方法,Context 只是一个接口,具体实现类是 StandardContext,我们分析下 startInternal 方法(此方法由之前的抽像骨架类中的 start 方法触发)
有一个线程启动的方法 threadStart()
// org.apache.catalina.core.StandardContext#startInternal
protected synchronized void startInternal() throws LifecycleException {
...
}
//Webapp类加载的设置
//这里的获取和设置类加载
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
...
try{
// 启动ContainerBackgroundProcessor线程(容器后台线程)
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
...
}
// org.apache.catalina.core.ContainerBase#threadStart
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName); // 这里
thread.setDaemon(true);
thread.start();
}
// org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor
protected class ContainerBackgroundProcessor implements Runnable {
@Override
public void run() {
Throwable t = null;
String unexpectedDeathMessage = sm.getString(
"containerBase.backgroundProcess.unexpectedThreadDeath",
Thread.currentThread().getName());
try {
while (!threadDone) {
try {
Thread.sleep(backgroundProcessorDelay * 1000L); // 这个值初始值是-1, sleep传入的值必须是0~Log.MAX_VALUE
// @Override 这个方法进行了赋值操作
// public void setBackgroundProcessorDelay(int delay) {
// backgroundProcessorDelay = delay;
// }
} catch (InterruptedException e) {
// Ignore
}
if (!threadDone) {
processChildren(ContainerBase.this); // 这里
}
}
} catch (RuntimeException|Error e) {
t = e;
throw e;
} finally {
if (!threadDone) {
log.error(unexpectedDeathMessage, t);
}
}
}
...
protected void processChildren(Container container) {
ClassLoader originalClassLoader = null;
try {
if (container instanceof Context) {
Loader loader = ((Context) container).getLoader();
// Loader will be null for FailedContext instances
if (loader == null) {
return;
}
// Ensure background processing for Contexts and Wrappers
// is performed under the web app's class loader
originalClassLoader = ((Context) container).bind(false, null);
}
container.backgroundProcess(); // 容器的后台任务
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i]);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("Exception invoking periodic operation: ", t);
} finally {
if (container instanceof Context) {
((Context) container).unbind(false, originalClassLoader);
}
}
}
}
org.apache.catalina.core.StandardContext#backgroundProcess
public void backgroundProcess() {
if (!getState().isAvailable())
return;
//删减后的↓↓↓↓↓↓↓ 逐个调用内部相关的backgroundProcess()方法
Loader loader = getLoader();
if (loader != null) {
try {
loader.backgroundProcess(); // WebappLoader
} catch (Exception e) {
log.warn(sm.getString(
"standardContext.backgroundProcess.loader", loader), e);
}
}
...
super.backgroundProcess();
}
org.apache.catalina.loader.WebappLoader#backgroundProcess
public void backgroundProcess() {
if (reloadable && modified()) {
try {
//设置线程类加载器的类加载器为WebappClassloader
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader());
if (context != null) {
//终要执行的重新加载的方法:StandardContext类的reload():
context.reload();
}
} finally {
if (context != null && context.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
}
}
先看看官方代码注释
显然, 是用来加载jsp文件的, 说白了就是将*.jsp, 解析加载成*_servlet.class然后以运行时数据结构加载jvm中
所以加载肯定是核心的代码: org.apache.jasper.servlet.JasperLoader#loadClass(String, boolean)
代码注释:
@Override
public synchronized Class<?> loadClass(final String name, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = null;
// (0) Check our previously loaded class cache
clazz = findLoadedClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (.5) Permission to access this class when using a 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);
}
}
}
// public static final String JSP_PACKAGE_NAME =
// System.getProperty("org.apache.jasper.Constants.JSP_PACKAGE_NAME", "org.apache.jsp");
// 这是我感觉和其他类加载不同的一个地方, 对特殊包名进行了一些过滤
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;
}
return findClass(name);
}