设为首页 收藏本站
查看: 529|回复: 0

[经验分享] How Tomcat Works(十一)

[复制链接]

尚未签到

发表于 2015-8-12 07:24:36 | 显示全部楼层 |阅读模式
  本文接下来分析tomcat的类载入器,tomcat需要实现一个自定义的载入器,而不能使用系统类载入器
  (1)限制serlvet访问当前运行的java虚拟机中环境变量CLASSPATH指明的路径下的所有类和库,而只允许载入WEB-INF/class目录及其子目录下的类,和从部署的库到WEB-INF/lib目录载入类
  (2)提供自动重载的功能,即当WEB-INF/class目录或WEB-INF/lib目录下的类发生变化时,Web应用程序会重新载入这些类
  我们先来回顾一下java的类载入器,当我们创建java类的实例时,都必须先将类载入到内存中,java虚拟机使用类载入器来载入需要的类
  JVM使用三种类型的类载入器来载入所需要的类,分别为引导类载入器(bootstrap class loader)、扩展类载入器(extensions class loader)和系统类载入器(system class loader)


  • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
  JVM采用一种代理模型的类载入机制,可以解决类载入过程中的安全性问题;每当系统需要载入一个类的时候,会首先调用系统类载入器,而系统类载入器将载入类的任务委派给其父载入器,即扩展类载入器,同样扩展类载入器将载入类的任务委派给其父载入器,即引导类载入器;因此引导类载入器会首先执行载入某个类的任务,如果引导类载入器找不到需要载入的类,那么扩展类载入器尝试载入该类,如果扩展类载入器也找不到该类,就轮到系统类载入器继续执行载入任务。如果系统类载入器还是找不到该类,则会抛出java.lang.ClassNotFoundException异常
  Tomcat中的载入器是指Web应用程序载入器,而不仅仅指类载入器,载入器必须实现org.apache.catalina.Loader接口



public interface Loader {

public ClassLoader getClassLoader();

public Container getContainer();

public void setContainer(Container container);

public DefaultContext getDefaultContext();

public void setDefaultContext(DefaultContext defaultContext);   

public boolean getDelegate();

public void setDelegate(boolean delegate);

public String getInfo();

public boolean getReloadable();

public void setReloadable(boolean reloadable);

public void addPropertyChangeListener(PropertyChangeListener listener);

public void addRepository(String repository);

public String[] findRepositories();

public boolean modified();

public void removePropertyChangeListener(PropertyChangeListener listener);
}
  下面我们来具体来分析tomcat容器中 Web应用程序载入器的具体实现,即org.apache.catalina.loader.WebappLoader类实现了上面的Loader接口,负责载入Web应用程序中所使用到的类。
  WebappLoader类会创建org.apache.catalina.loader.WebappClassLoader类的实例作为其类载入器;
  同时WebappLoader类也实现了org.apache.catalina.Lifecycle接口,可以由其相关联的容器来启动或关闭;
  此外,WebappLoader类还实现了java.lang.Runnable接口,通过一个线程不断地调用其类载入器的modified()方法。如果modified()方法返回true, WebappLoader的实例会通知其关联的servlet容器(在这里是Context类的实例),然后由Context实例来完成类的重新载入。
  当调用WebappLoader类的start()方法时,会完成以下几项重要工作:
  (1)创建一个类载入器
  (2)设置仓库
  (3)设置类路径
  (4)设置访问权限
  (5)启动一个新线程来支持自动重载
  WebappLoader类会调用其私有方法createClassLoader()方法来创建默认的类载入器



/**
* Create associated classLoader.
*/
private WebappClassLoader createClassLoader()
throws Exception {
Class clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;
if (parentClassLoader == null) {
// Will cause a ClassCast is the class does not extend WCL, but
// this is on purpose (the exception will be caught and rethrown)
classLoader = (WebappClassLoader) clazz.newInstance();
} else {
Class[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);
}
return classLoader;
}
  String loaderClass 的默认值为org.apache.catalina.loader.WebappClassLoader
  启动方法后面的设置仓库、设置类路径、设置访问权限等都与类载入器的初始化相关
  WebappLoader类支持自动重载功能,如果WEB-INF/class目录或WEB-INF/lib目录下的某些类被重新编译了,那么这些类会自动重新载入,而无需重启tomcat。WebappLoader类使用一个线程周期性地检查每个资源的时间戳,我们可以调用setCheckInterval()方法设置间隔时间



/**
* The background thread that checks for session timeouts and shutdown.
*/
public void run() {
if (debug >= 1)
log("BACKGROUND THREAD Starting");
// Loop until the termination semaphore is set
while (!threadDone) {
// Wait for our check interval
            threadSleep();
if (!started)
break;
try {
// Perform our modification check
if (!classLoader.modified())
continue;
} catch (Exception e) {
log(sm.getString("webappLoader.failModifiedCheck"), e);
continue;
}
// Handle a need for reloading
            notifyContext();
break;
}
if (debug >= 1)
log("BACKGROUND THREAD Stopping");
}
  在上面run()方法里面的循环中,首先使线程休眠一段时间,然后调用 WebappLoader实例的类载入器的 modified()方法检查已经载入的类是否被修改,肉没有修改则重新执行循环;否则调用私有方法notifyContext(),通知与WebappLoader实例相关联的Context容器重新载入相关类



/**
* Notify our Context that a reload is appropriate.
*/
private void notifyContext() {
WebappContextNotifier notifier = new WebappContextNotifier();
(new Thread(notifier)).start();
}
  上面方法中的WebappContextNotifier为内部类,实现了Runnable接口



/**
* Private thread class to notify our associated Context that we have
* recognized the need for a reload.
*/
protected class WebappContextNotifier implements Runnable {
/**
* Perform the requested notification.
*/
public void run() {
((Context) container).reload();
}
}
  下面我们来分析web应用程序中负责载入类的类载入器org.apache.catalina.loader.WebappClassLoader,该类继承自java.net.URLClassLoader,同时实现了org.apache.catalina.loader.Reloader接口



public interface Reloader {

public void addRepository(String repository);

public String[] findRepositories();

public boolean modified();
}
  该接口用来与仓库操作相关,同时检查web应用程序中的某个servlet或相关的类是否被修改
  每个由WebappClassLoader载入的类,都视为资源,由org.apache.catalina.loader.ResourceEntry类的实例表示,存储了类的相关信息



public class ResourceEntry {

public long lastModified = -1;

public byte[] binaryContent = null;

public Class loadedClass = null;

public URL source = null;

public URL codeBase = null;

public Manifest manifest = null;

public Certificate[] certificates = null;
}
  在WebappClassLoader类中,所有已经缓存的类存储在名为resourceEntries的HashMap类型的变量中,而载入失败的类被存储在另一个名为notFoundResources的HashMap类型的变量中
  下面是WebappClassLoader的loadClass()方法的具体实现



public Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (debug >= 2)
log("loadClass(" + name + ", " + resolve + ")");
Class clazz = null;
// Don't load classes if class loader is stopped
if (!started) {
log("Lifecycle error : CL stopped");
throw new ClassNotFoundException(name);
}
// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
if (debug >= 3)
log("  Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.1) Check our previously loaded class cache
clazz = findLoadedClass(name);
if (clazz != null) {
if (debug >= 3)
log("  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 {
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;
System.out.println(error);
se.printStackTrace();
log(error);
throw new ClassNotFoundException(error);
}
}
}
boolean delegateLoad = delegate || filter(name);
// (1) Delegate to our parent if requested
if (delegateLoad) {
if (debug >= 3)
log("  Delegating to parent classloader");
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = loader.loadClass(name);
if (clazz != null) {
if (debug >= 3)
log("  Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
}
// (2) Search local repositories
if (debug >= 3)
log("  Searching local repositories");
try {
clazz = findClass(name);
if (clazz != null) {
if (debug >= 3)
log("  Loading class from local repository");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
// (3) Delegate to parent unconditionally
if (!delegateLoad) {
if (debug >= 3)
log("  Delegating to parent classloader");
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = loader.loadClass(name);
if (clazz != null) {
if (debug >= 3)
log("  Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
}
// This class was not found
throw new ClassNotFoundException(name);
}
  在WebappClassLoader实例载入类时,首先是检查缓存,然后再载入指定类(如果设置了安全管理。在代理载入器载入前还要检测类型的安全)
  ---------------------------------------------------------------------------
  本系列How Tomcat Works系本人原创
  转载请注明出处 博客园 刺猬的温驯
  本人邮箱: chenying998179#163.com (#改为@)
  本文链接http://www.iyunv.com/chenying99/p/3237389.html

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-97621-1-1.html 上篇帖子: Tomcat部署遇到的问题 下篇帖子: tomcat监听activemq jms配置
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表